• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 28138031111

25 Jun 2026 12:13AM UTC coverage: 88.906% (-0.03%) from 88.94%
28138031111

push

github

web-flow
fix: double screen resize (#3779)

* fix: double screen resize

* fix lint

* fix tests

* update mock

* fix lint

6878 of 9023 branches covered (76.23%)

2 of 3 new or added lines in 1 file covered. (66.67%)

5 existing lines in 1 file now uncovered.

15515 of 17451 relevant lines covered (88.91%)

24865.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.05
/src/engine/screen.ts
1
import { vec, Vector } from './math/vector';
2
import { Logger } from './util/log';
3
import type { Camera } from './camera';
4
import type { BrowserEvents } from './util/browser';
5
import { BoundingBox } from './collision/index';
6
import type { ExcaliburGraphicsContext } from './graphics/context/excalibur-graphics-context';
7
import { getPosition } from './util/util';
8
import { ExcaliburGraphicsContextWebGL } from './graphics/context/excalibur-graphics-context-webgl';
9
import { ExcaliburGraphicsContext2DCanvas } from './graphics/context/excalibur-graphics-context-2d-canvas';
10
import { EventEmitter } from './event-emitter';
11

12
/**
13
 * Enum representing the different display modes available to Excalibur.
14
 */
15
export enum DisplayMode {
254✔
16
  /**
17
   * Default, use a specified resolution for the game. Like 800x600 pixels for example.
18
   */
19
  Fixed = 'Fixed',
254✔
20

21
  /**
22
   * Fit the aspect ratio given by the game resolution within the container at all times will fill any gaps with canvas.
23
   * The displayed area outside the aspect ratio is not guaranteed to be on the screen, only the {@apilink Screen.contentArea}
24
   * is guaranteed to be on screen.
25
   */
26
  FitContainerAndFill = 'FitContainerAndFill',
254✔
27

28
  /**
29
   * Fit the aspect ratio given by the game resolution the screen at all times will fill the screen.
30
   * This displayed area outside the aspect ratio is not guaranteed to be on the screen, only the {@apilink Screen.contentArea}
31
   * is guaranteed to be on screen.
32
   */
33
  FitScreenAndFill = 'FitScreenAndFill',
254✔
34

35
  /**
36
   * Fit the viewport to the parent element maintaining aspect ratio given by the game resolution, but zooms in to avoid the black bars
37
   * (letterbox) that would otherwise be present in {@apilink FitContainer}.
38
   *
39
   * **warning** This will clip some drawable area from the user because of the zoom,
40
   * use {@apilink Screen.contentArea} to know the safe to draw area.
41
   */
42
  FitContainerAndZoom = 'FitContainerAndZoom',
254✔
43

44
  /**
45
   * Fit the viewport to the device screen maintaining aspect ratio given by the game resolution, but zooms in to avoid the black bars
46
   * (letterbox) that would otherwise be present in {@apilink FitScreen}.
47
   *
48
   * **warning** This will clip some drawable area from the user because of the zoom,
49
   * use {@apilink Screen.contentArea} to know the safe to draw area.
50
   */
51
  FitScreenAndZoom = 'FitScreenAndZoom',
254✔
52

53
  /**
54
   * Fit to screen using as much space as possible while maintaining aspect ratio and resolution.
55
   * This is not the same as {@apilink Screen.enterFullscreen} but behaves in a similar way maintaining aspect ratio.
56
   *
57
   * You may want to center your game here is an example
58
   * ```html
59
   * <!-- html -->
60
   * <body>
61
   * <main>
62
   *   <canvas id="game"></canvas>
63
   * </main>
64
   * </body>
65
   * ```
66
   *
67
   * ```css
68
   * // css
69
   * main {
70
   *   display: flex;
71
   *   align-items: center;
72
   *   justify-content: center;
73
   *   height: 100%;
74
   *   width: 100%;
75
   * }
76
   * ```
77
   */
78
  FitScreen = 'FitScreen',
254✔
79

80
  /**
81
   * Fill the entire screen's css width/height for the game resolution dynamically. This means the resolution of the game will
82
   * change dynamically as the window is resized. This is not the same as {@apilink Screen.enterFullscreen}
83
   */
84
  FillScreen = 'FillScreen',
254✔
85

86
  /**
87
   * Fit to parent element width/height using as much space as possible while maintaining aspect ratio and resolution.
88
   */
89
  FitContainer = 'FitContainer',
254✔
90

91
  /**
92
   * Use the parent DOM container's css width/height for the game resolution dynamically
93
   */
94
  FillContainer = 'FillContainer'
254✔
95
}
96

97
/**
98
 * Convenience class for quick resolutions
99
 * Mostly sourced from https://emulation.gametechwiki.com/index.php/Resolution
100
 */
101
export class Resolution {
102
  /* istanbul ignore next */
103
  public static get SVGA(): Resolution {
104
    return { width: 800, height: 600 };
105
  }
106

107
  /* istanbul ignore next */
108
  public static get Standard(): Resolution {
109
    return { width: 1920, height: 1080 };
110
  }
111

112
  /* istanbul ignore next */
113
  public static get Atari2600(): Resolution {
114
    return { width: 160, height: 192 };
115
  }
116

117
  /* istanbul ignore next */
118
  public static get GameBoy(): Resolution {
119
    return { width: 160, height: 144 };
120
  }
121

122
  /* istanbul ignore next */
123
  public static get GameBoyAdvance(): Resolution {
124
    return { width: 240, height: 160 };
125
  }
126

127
  /* istanbul ignore next */
128
  public static get NintendoDS(): Resolution {
129
    return { width: 256, height: 192 };
130
  }
131

132
  /* istanbul ignore next */
133
  public static get NES(): Resolution {
134
    return { width: 256, height: 224 };
135
  }
136

137
  /* istanbul ignore next */
138
  public static get SNES(): Resolution {
139
    return { width: 256, height: 244 };
140
  }
141
}
142

143
export type ViewportUnit = 'pixel' | 'percent';
144

145
export interface Resolution {
146
  width: number;
147
  height: number;
148
}
149

150
export interface ViewportDimension {
151
  widthUnit?: ViewportUnit;
152
  heightUnit?: ViewportUnit;
153
  width: number;
154
  height: number;
155
}
156

157
export interface ScreenOptions {
158
  /**
159
   * Canvas element to build a screen on
160
   */
161
  canvas: HTMLCanvasElement;
162

163
  /**
164
   * Graphics context for the screen
165
   */
166
  context: ExcaliburGraphicsContext;
167

168
  /**
169
   * Browser abstraction
170
   */
171
  browser: BrowserEvents;
172
  /**
173
   * Optionally set antialiasing, defaults to true. If set to true, images will be smoothed
174
   */
175
  antialiasing?: boolean;
176

177
  /**
178
   * Optionally set the image rendering CSS hint on the canvas element, default is auto
179
   */
180
  canvasImageRendering?: 'auto' | 'pixelated';
181
  /**
182
   * Optionally override the pixel ratio to use for the screen, otherwise calculated automatically from the browser
183
   */
184
  pixelRatio?: number;
185
  /**
186
   * Optionally specify the actual pixel resolution in width/height pixels (also known as logical resolution), by default the
187
   * resolution will be the same as the viewport. Resolution will be overridden by {@apilink DisplayMode.FillContainer} and
188
   * {@apilink DisplayMode.FillScreen}.
189
   */
190
  resolution?: Resolution;
191
  /**
192
   * Visual viewport size in css pixel, if resolution is not specified it will be the same as the viewport
193
   */
194
  viewport: ViewportDimension;
195
  /**
196
   * Set the display mode of the screen, by default DisplayMode.Fixed.
197
   */
198
  displayMode?: DisplayMode;
199
}
200

201
/**
202
 * Fires when the screen resizes, useful if you have logic that needs to be aware of resolution/viewport constraints
203
 */
204
export interface ScreenResizeEvent {
205
  /**
206
   * Current viewport in css pixels of the screen
207
   */
208
  viewport: ViewportDimension;
209
  /**
210
   * Current resolution in world pixels of the screen
211
   */
212
  resolution: Resolution;
213
}
214

215
/**
216
 * Fires when the pixel ratio changes, useful to know if you've moved to a hidpi screen or back
217
 */
218
export interface PixelRatioChangeEvent {
219
  /**
220
   * Current pixel ratio of the screen
221
   */
222
  pixelRatio: number;
223
}
224

225
/**
226
 * Fires when the browser fullscreen api is successfully engaged or disengaged
227
 */
228
export interface FullScreenChangeEvent {
229
  /**
230
   * Current fullscreen state
231
   */
232
  fullscreen: boolean;
233
}
234

235
/**
236
 * Built in events supported by all entities
237
 */
238
export interface ScreenEvents {
239
  /**
240
   * Fires when the screen resizes, useful if you have logic that needs to be aware of resolution/viewport constraints
241
   */
242
  resize: ScreenResizeEvent;
243
  /**
244
   * Fires when the pixel ratio changes, useful to know if you've moved to a hidpi screen or back
245
   */
246
  pixelratio: PixelRatioChangeEvent;
247
  /**
248
   * Fires when the browser fullscreen api is successfully engaged or disengaged
249
   */
250
  fullscreen: FullScreenChangeEvent;
251
}
252

253
export const ScreenEvents = {
254✔
254
  ScreenResize: 'resize',
255
  PixelRatioChange: 'pixelratio',
256
  FullScreenChange: 'fullscreen'
257
} as const;
258

259
/**
260
 * The Screen handles all aspects of interacting with the screen for Excalibur.
261
 */
262
export class Screen {
263
  public graphicsContext: ExcaliburGraphicsContext;
264
  /**
265
   * Listen to screen events {@apilink ScreenEvents}
266
   */
267
  public events = new EventEmitter<ScreenEvents>();
822✔
268
  private _canvas: HTMLCanvasElement;
269
  private _antialiasing: boolean = true;
822✔
270
  private _canvasImageRendering: 'auto' | 'pixelated' = 'auto';
822✔
271
  private _contentResolution: Resolution;
272
  private _browser: BrowserEvents;
273
  private _camera: Camera;
274
  private _resolution: Resolution;
275
  private _resolutionStack: Resolution[] = [];
822✔
276
  private _viewport: ViewportDimension;
277
  private _viewportStack: ViewportDimension[] = [];
822✔
278
  private _pixelRatioOverride: number | null = null;
822✔
279
  private _displayMode: DisplayMode;
280
  private _isFullscreen = false;
822✔
281
  private _mediaQueryList: MediaQueryList;
282
  private _isDisposed = false;
822✔
283
  private _logger = Logger.getInstance();
822✔
284
  private _resizeObserver: ResizeObserver;
285

286
  constructor(options: ScreenOptions) {
287
    this.viewport = options.viewport;
822✔
288
    this.resolution = options.resolution ?? { ...this.viewport };
822✔
289
    this._contentResolution = this.resolution;
822✔
290
    this._displayMode = options.displayMode ?? DisplayMode.Fixed;
822✔
291
    this._canvas = options.canvas;
822✔
292
    this.graphicsContext = options.context;
822✔
293
    this._antialiasing = options.antialiasing ?? this._antialiasing;
822✔
294
    this._canvasImageRendering = options.canvasImageRendering ?? this._canvasImageRendering;
822✔
295
    this._browser = options.browser;
822✔
296
    this._pixelRatioOverride = options.pixelRatio;
822✔
297

298
    this._applyDisplayMode();
822✔
299

300
    this._listenForPixelRatio();
822✔
301

302
    this._canvas.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
822✔
303
    this.applyResolutionAndViewport();
822✔
304
  }
305

306
  private _listenForPixelRatio() {
307
    if (this._mediaQueryList && !this._mediaQueryList.addEventListener) {
822!
308
      // Safari <=13.1 workaround, remove any existing handlers
309
      this._mediaQueryList.removeListener(this._pixelRatioChangeHandler);
×
310
    }
311
    this._mediaQueryList = this._browser.window.nativeComponent.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
822✔
312

313
    // Safari <=13.1 workaround
314
    if (this._mediaQueryList.addEventListener) {
822!
315
      this._mediaQueryList.addEventListener('change', this._pixelRatioChangeHandler, { once: true });
822✔
316
    } else {
317
      this._mediaQueryList.addListener(this._pixelRatioChangeHandler);
×
318
    }
319
  }
320

321
  public dispose(): void {
322
    if (!this._isDisposed) {
764!
323
      // Clean up handlers
324
      this._isDisposed = true;
764✔
325
      this.events.clear();
764✔
326
      this._browser.window.off('resize', this._resizeHandler);
764✔
327
      this._browser.window.clear();
764✔
328
      if (this._resizeObserver) {
764!
329
        this._resizeObserver.disconnect();
×
330
      }
331
      if (!(this.parent instanceof Window)) {
764!
NEW
332
        this.parent.removeEventListener('resize', this._resizeHandler);
×
333
      }
334
      // Safari <=13.1 workaround
335
      if (this._mediaQueryList.removeEventListener) {
764!
336
        this._mediaQueryList.removeEventListener('change', this._pixelRatioChangeHandler);
764✔
337
      } else {
338
        this._mediaQueryList.removeListener(this._pixelRatioChangeHandler);
×
339
      }
340
      this._canvas.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
764✔
341
      this._canvas = null;
764✔
342
    }
343
  }
344

345
  private _fullscreenChangeHandler = () => {
822✔
346
    if (this._isDisposed) {
2!
347
      return;
×
348
    }
349
    this._isFullscreen = !this._isFullscreen;
2✔
350
    this._logger.debug('Fullscreen Change', this._isFullscreen);
2✔
351
    this.events.emit('fullscreen', {
2✔
352
      fullscreen: this.isFullscreen
353
    } satisfies FullScreenChangeEvent);
354
  };
355

356
  private _pixelRatioChangeHandler = () => {
822✔
357
    if (this._isDisposed) {
×
358
      return;
×
359
    }
360
    this._logger.debug('Pixel Ratio Change', window.devicePixelRatio);
×
361
    this._listenForPixelRatio();
×
362
    this._devicePixelRatio = this._calculateDevicePixelRatio();
×
363
    this.applyResolutionAndViewport();
×
364

365
    this.events.emit('pixelratio', {
×
366
      pixelRatio: this.pixelRatio
367
    } satisfies PixelRatioChangeEvent);
368
  };
369

370
  private _resizeHandler = () => {
822✔
371
    if (this._isDisposed) {
82✔
372
      return;
28✔
373
    }
374
    const parent = this.parent;
54✔
375
    this._logger.debug('View port resized');
54✔
376
    this._setResolutionAndViewportByDisplayMode(parent);
54✔
377
    this.applyResolutionAndViewport();
54✔
378

379
    // Emit resize event
380
    this.events.emit('resize', {
54✔
381
      resolution: this.resolution,
382
      viewport: this.viewport
383
    } satisfies ScreenResizeEvent);
384
  };
385

386
  private _calculateDevicePixelRatio() {
387
    if (window.devicePixelRatio < 1) {
822!
388
      return 1;
×
389
    }
390

391
    const devicePixelRatio = window.devicePixelRatio || 1;
822!
392

393
    return devicePixelRatio;
822✔
394
  }
395

396
  // Asking the window.devicePixelRatio is expensive we do it once
397
  private _devicePixelRatio = this._calculateDevicePixelRatio();
822✔
398

399
  /**
400
   * Returns the computed pixel ratio, first using any override, then the device pixel ratio
401
   */
402
  public get pixelRatio(): number {
403
    if (this._pixelRatioOverride) {
5,388✔
404
      return this._pixelRatioOverride;
4,971✔
405
    }
406

407
    return this._devicePixelRatio;
417✔
408
  }
409

410
  /**
411
   * This calculates the ratio between excalibur pixels and the HTML pixels.
412
   *
413
   * This is useful for scaling HTML UI so that it matches your game.
414
   */
415
  public get worldToPagePixelRatio(): number {
416
    if (this._canvas) {
924!
417
      const pageOrigin = this.worldToPageCoordinates(Vector.Zero);
924✔
418
      const pageDistance = this.worldToPageCoordinates(vec(1, 0)).sub(pageOrigin);
924✔
419
      const pixelConversion = pageDistance.x;
924✔
420
      return pixelConversion;
924✔
421
    } else {
422
      return 1;
×
423
    }
424
  }
425

426
  /**
427
   * Get or set the pixel ratio override
428
   *
429
   * You will need to call applyResolutionAndViewport() affect change on the screen
430
   */
431
  public get pixelRatioOverride(): number | undefined {
432
    return this._pixelRatioOverride;
2✔
433
  }
434

435
  public set pixelRatioOverride(value: number | undefined) {
436
    this._pixelRatioOverride = value;
1✔
437
  }
438

439
  public get isHiDpi() {
440
    return this.pixelRatio !== 1;
3✔
441
  }
442

443
  public get displayMode(): DisplayMode {
444
    return this._displayMode;
9,487✔
445
  }
446

447
  public get canvas(): HTMLCanvasElement {
448
    return this._canvas;
1,877✔
449
  }
450

451
  public get parent(): HTMLElement | Window {
452
    switch (this.displayMode) {
2,478!
453
      case DisplayMode.FillContainer:
454
      case DisplayMode.FitContainer:
455
      case DisplayMode.FitContainerAndFill:
456
      case DisplayMode.FitContainerAndZoom:
457
        return this.canvas.parentElement || document.body;
40!
458
      default:
459
        return window;
2,438✔
460
    }
461
  }
462

463
  public get resolution(): Resolution {
464
    return this._resolution;
28,277✔
465
  }
466

467
  public set resolution(resolution: Resolution) {
468
    this._resolution = resolution;
892✔
469
  }
470

471
  /**
472
   * Returns screen dimensions in pixels or percentage
473
   */
474
  public get viewport(): ViewportDimension {
475
    if (this._viewport) {
8,087!
476
      return this._viewport;
8,087✔
477
    }
478
    return this._resolution;
×
479
  }
480

481
  public set viewport(viewport: ViewportDimension) {
482
    this._viewport = viewport;
914✔
483
  }
484

485
  public get aspectRatio() {
486
    return this._resolution.width / this._resolution.height;
56✔
487
  }
488

489
  public get scaledWidth() {
490
    return this._resolution.width * this.pixelRatio;
1,734✔
491
  }
492

493
  public get scaledHeight() {
494
    return this._resolution.height * this.pixelRatio;
1,734✔
495
  }
496

497
  public setCurrentCamera(camera: Camera) {
498
    this._camera = camera;
715✔
499
  }
500

501
  public pushResolutionAndViewport() {
502
    this._resolutionStack.push(this.resolution);
17✔
503
    this._viewportStack.push(this.viewport);
17✔
504

505
    this.resolution = { ...this.resolution };
17✔
506
    this.viewport = { ...this.viewport };
17✔
507
  }
508

509
  public peekViewport(): ViewportDimension {
510
    return this._viewportStack[this._viewportStack.length - 1];
×
511
  }
512

513
  public peekResolution(): Resolution {
514
    return this._resolutionStack[this._resolutionStack.length - 1];
5✔
515
  }
516

517
  public popResolutionAndViewport() {
518
    if (this._resolutionStack.length && this._viewportStack.length) {
15!
519
      this.resolution = this._resolutionStack.pop();
15✔
520
      this.viewport = this._viewportStack.pop();
15✔
521
    }
522
  }
523

524
  public applyResolutionAndViewport() {
525
    if (this.graphicsContext instanceof ExcaliburGraphicsContextWebGL) {
921✔
526
      const scaledResolutionSupported = this.graphicsContext.checkIfResolutionSupported({
808✔
527
        width: this.scaledWidth,
528
        height: this.scaledHeight
529
      });
530
      if (!scaledResolutionSupported) {
808✔
531
        this._logger.warnOnce(
2✔
532
          `The currently configured resolution (${this.resolution.width}x${this.resolution.height}) and pixel ratio (${this.pixelRatio})` +
533
            ' are too large for the platform WebGL implementation, this may work but cause WebGL rendering to behave oddly.' +
534
            ' Try reducing the resolution or disabling Hi DPI scaling to avoid this' +
535
            ' (read more here https://excaliburjs.com/docs/screens#understanding-viewport--resolution).'
536
        );
537

538
        // Attempt to recover if the user hasn't configured a specific ratio for up scaling
539
        if (!this.pixelRatioOverride) {
2✔
540
          let currentPixelRatio = Math.max(1, this.pixelRatio - 0.5);
1✔
541
          let newResolutionSupported = false;
1✔
542
          while (currentPixelRatio > 1 && !newResolutionSupported) {
1✔
543
            currentPixelRatio = Math.max(1, currentPixelRatio - 0.5);
1✔
544
            const width = this._resolution.width * currentPixelRatio;
1✔
545
            const height = this._resolution.height * currentPixelRatio;
1✔
546
            newResolutionSupported = this.graphicsContext.checkIfResolutionSupported({ width, height });
1✔
547
          }
548
          this.pixelRatioOverride = currentPixelRatio;
1✔
549
          this._logger.warnOnce(
1✔
550
            'Scaled resolution too big attempted recovery!' +
551
              ` Pixel ratio was automatically reduced to (${this.pixelRatio}) to avoid 4k texture limit.` +
552
              ' Setting `ex.Engine({pixelRatio: ...}) will override any automatic recalculation, do so at your own risk.` ' +
553
              ' (read more here https://excaliburjs.com/docs/screens#understanding-viewport--resolution).'
554
          );
555
        }
556
      }
557
    }
558

559
    this._canvas.width = this.scaledWidth;
921✔
560
    this._canvas.height = this.scaledHeight;
921✔
561

562
    if (this._canvasImageRendering === 'auto') {
921✔
563
      this._canvas.style.imageRendering = 'auto';
133✔
564
    } else {
565
      this._canvas.style.imageRendering = 'pixelated';
788✔
566
      // Fall back to 'crisp-edges' if 'pixelated' is not supported
567
      // Currently for firefox https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
568
      if (this._canvas.style.imageRendering === '') {
788✔
569
        this._canvas.style.imageRendering = 'crisp-edges';
2✔
570
      }
571
    }
572

573
    const widthUnit = this.viewport.widthUnit === 'percent' ? '%' : 'px';
921✔
574
    const heightUnit = this.viewport.heightUnit === 'percent' ? '%' : 'px';
921✔
575

576
    this._canvas.style.width = this.viewport.width + widthUnit;
921✔
577
    this._canvas.style.height = this.viewport.height + heightUnit;
921✔
578

579
    // After messing with the canvas width/height the graphics context is invalidated and needs to have some properties reset
580
    this.graphicsContext.updateViewport(this.resolution);
921✔
581
    this.graphicsContext.resetTransform();
921✔
582
    this.graphicsContext.smoothing = this._antialiasing;
921✔
583
    if (this.graphicsContext instanceof ExcaliburGraphicsContext2DCanvas) {
921✔
584
      this.graphicsContext.scale(this.pixelRatio, this.pixelRatio);
113✔
585
    }
586
    // Add the excalibur world pixel to page pixel
587
    document.documentElement.style.setProperty('--ex-pixel-ratio', this.worldToPagePixelRatio.toString());
921✔
588
  }
589

590
  /**
591
   * Get or set screen antialiasing,
592
   *
593
   * If true smoothing is applied
594
   */
595
  public get antialiasing() {
596
    return this._antialiasing;
847✔
597
  }
598

599
  /**
600
   * Get or set screen antialiasing
601
   */
602
  public set antialiasing(isSmooth: boolean) {
603
    this._antialiasing = isSmooth;
1,673✔
604
    this.graphicsContext.smoothing = this._antialiasing;
1,673✔
605
  }
606

607
  /**
608
   * Returns true if excalibur is fullscreen using the browser fullscreen api
609
   * @deprecated use isFullscreen()
610
   */
611
  public get isFullScreen() {
612
    return this._isFullscreen;
5✔
613
  }
614

615
  /**
616
   * Returns true if excalibur is fullscreen using the browser fullscreen api
617
   */
618
  public get isFullscreen() {
619
    return this._isFullscreen;
2✔
620
  }
621

622
  /**
623
   * Requests to go fullscreen using the browser fullscreen api, requires user interaction to be successful.
624
   * For example, wire this to a user click handler.
625
   *
626
   * Optionally specify a target element id to go fullscreen, by default the game canvas is used
627
   * @param elementId
628
   * @deprecated use enterFullscreen(...)
629
   */
630
  public goFullScreen(elementId?: string): Promise<void> {
631
    return this.enterFullscreen(elementId);
×
632
  }
633

634
  /**
635
   * Requests to enter fullscreen using the browser fullscreen api, requires user interaction to be successful.
636
   * For example, wire this to a user click handler.
637
   *
638
   * Optionally specify a target element id to go fullscreen, by default the game canvas is used
639
   * @param elementId
640
   */
641
  public enterFullscreen(elementId?: string): Promise<void> {
642
    if (elementId) {
2✔
643
      const maybeElement = document.getElementById(elementId);
1✔
644
      // workaround for safari partial support
645
      if (maybeElement?.requestFullscreen || (maybeElement as any)?.webkitRequestFullscreen) {
1!
646
        if (!maybeElement.getAttribute('ex-fullscreen-listener')) {
1!
647
          maybeElement.setAttribute('ex-fullscreen-listener', 'true');
1✔
648
          maybeElement.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
1✔
649
        }
650
        if (maybeElement.requestFullscreen) {
1!
651
          return maybeElement.requestFullscreen() ?? Promise.resolve();
1!
652
        } else if ((maybeElement as any).webkitRequestFullscreen) {
×
653
          return (maybeElement as any).webkitRequestFullscreen() ?? Promise.resolve();
×
654
        }
655
      }
656
    }
657
    if (this._canvas?.requestFullscreen) {
1!
658
      return this._canvas?.requestFullscreen() ?? Promise.resolve();
1!
659
    } else if ((this._canvas as any).webkitRequestFullscreen) {
×
660
      return (this._canvas as any).webkitRequestFullscreen() ?? Promise.resolve();
×
661
    }
662
    this._logger.warnOnce('Could not go fullscreen, is this an iPhone? Currently Apple does not support fullscreen on iPhones');
×
663
    return Promise.resolve();
×
664
  }
665

666
  /**
667
   * Requests to exit fullscreen using the browser fullscreen api
668
   * @deprecated use exitFullscreen()
669
   */
670
  public exitFullScreen(): Promise<void> {
671
    return this.exitFullscreen();
×
672
  }
673

674
  public exitFullscreen(): Promise<void> {
675
    return document.exitFullscreen();
×
676
  }
677

678
  private _viewportToPixels(viewport: ViewportDimension) {
679
    return {
3,329✔
680
      width: viewport.widthUnit === 'percent' ? this.canvas.offsetWidth : viewport.width,
3,329✔
681
      height: viewport.heightUnit === 'percent' ? this.canvas.offsetHeight : viewport.height
3,329✔
682
    } satisfies ViewportDimension;
683
  }
684

685
  /**
686
   * Takes a coordinate in normal html page space, for example from a pointer move event, and translates it to
687
   * Excalibur screen space.
688
   *
689
   * Excalibur screen space starts at the top left (0, 0) corner of the viewport, and extends to the
690
   * bottom right corner (resolutionX, resolutionY). When using *AndFill suffixed display modes screen space
691
   * (0, 0) is the top left of the safe content area bounding box not the viewport.
692
   * @param point
693
   */
694
  public pageToScreenCoordinates(point: Vector): Vector {
695
    let newX = point.x;
1,447✔
696
    let newY = point.y;
1,447✔
697

698
    if (!this._isFullscreen) {
1,447✔
699
      newX -= getPosition(this._canvas).x;
1,445✔
700
      newY -= getPosition(this._canvas).y;
1,445✔
701
    }
702

703
    const viewport = this._viewportToPixels(this.viewport);
1,447✔
704

705
    // if fullscreen api on it centers with black bars
706
    // we need to adjust the screen to world coordinates in this case
707
    if (this._isFullscreen) {
1,447✔
708
      if (window.innerWidth / this.aspectRatio < window.innerHeight) {
2✔
709
        const screenHeight = window.innerWidth / this.aspectRatio;
1✔
710
        const screenMarginY = (window.innerHeight - screenHeight) / 2;
1✔
711
        newY = ((newY - screenMarginY) / screenHeight) * viewport.height;
1✔
712
        newX = (newX / window.innerWidth) * viewport.width;
1✔
713
      } else {
714
        const screenWidth = window.innerHeight * this.aspectRatio;
1✔
715
        const screenMarginX = (window.innerWidth - screenWidth) / 2;
1✔
716
        newX = ((newX - screenMarginX) / screenWidth) * viewport.width;
1✔
717
        newY = (newY / window.innerHeight) * viewport.height;
1✔
718
      }
719
    }
720

721
    newX = (newX / viewport.width) * this.resolution.width;
1,447✔
722
    newY = (newY / viewport.height) * this.resolution.height;
1,447✔
723

724
    // offset by content area
725
    newX = newX - this.contentArea.left;
1,447✔
726
    newY = newY - this.contentArea.top;
1,447✔
727

728
    return new Vector(newX, newY);
1,447✔
729
  }
730

731
  /**
732
   * Takes a coordinate in Excalibur screen space, and translates it to normal html page space. For example,
733
   * this is where html elements might live if you want to position them relative to Excalibur.
734
   *
735
   * Excalibur screen space starts at the top left (0, 0) corner of the viewport, and extends to the
736
   * bottom right corner (resolutionX, resolutionY)
737
   * @param point
738
   */
739
  public screenToPageCoordinates(point: Vector): Vector {
740
    let newX = point.x;
1,882✔
741
    let newY = point.y;
1,882✔
742

743
    // no need to offset by content area, drawing is already offset by this
744

745
    const viewport = this._viewportToPixels(this.viewport);
1,882✔
746

747
    newX = (newX / this.resolution.width) * viewport.width;
1,882✔
748
    newY = (newY / this.resolution.height) * viewport.height;
1,882✔
749

750
    if (this._isFullscreen) {
1,882✔
751
      if (window.innerWidth / this.aspectRatio < window.innerHeight) {
2✔
752
        const screenHeight = window.innerWidth / this.aspectRatio;
1✔
753
        const screenMarginY = (window.innerHeight - screenHeight) / 2;
1✔
754
        newY = (newY / viewport.height) * screenHeight + screenMarginY;
1✔
755
        newX = (newX / viewport.width) * window.innerWidth;
1✔
756
      } else {
757
        const screenWidth = window.innerHeight * this.aspectRatio;
1✔
758
        const screenMarginX = (window.innerWidth - screenWidth) / 2;
1✔
759
        newX = (newX / viewport.width) * screenWidth + screenMarginX;
1✔
760
        newY = (newY / viewport.height) * window.innerHeight;
1✔
761
      }
762
    }
763

764
    if (!this._isFullscreen) {
1,882✔
765
      newX += getPosition(this._canvas).x;
1,880✔
766
      newY += getPosition(this._canvas).y;
1,880✔
767
    }
768

769
    return new Vector(newX, newY);
1,882✔
770
  }
771

772
  /**
773
   * Takes a coordinate in Excalibur screen space, and translates it to Excalibur world space.
774
   *
775
   * World space is where {@apilink Entity | `entities`} in Excalibur live by default {@apilink CoordPlane.World}
776
   * and extends infinitely out relative from the {@apilink Camera}.
777
   * @param point  Screen coordinate to convert
778
   */
779
  public screenToWorldCoordinates(point: Vector): Vector {
780
    // offset by content area
781
    point = point.add(vec(this.contentArea.left, this.contentArea.top));
1,455✔
782

783
    // the only difference between screen & world is the camera transform
784
    if (this._camera) {
1,455✔
785
      return this._camera.inverse.multiply(point);
1,448✔
786
    }
787
    // fallback to center screen camera
788
    return point.sub(vec(this.resolution.width / 2, this.resolution.height / 2));
7✔
789
  }
790

791
  /**
792
   * Takes a coordinate in Excalibur world space, and translates it to Excalibur screen space.
793
   *
794
   * Screen space is where {@apilink ScreenElement | `screen elements`} and {@apilink Entity | `entities`} with {@apilink CoordPlane.Screen} live.
795
   * @param point  World coordinate to convert
796
   */
797
  public worldToScreenCoordinates(point: Vector): Vector {
798
    if (this._camera) {
1,889✔
799
      return this._camera.transform.multiply(point);
42✔
800
    }
801

802
    // fallback to center screen camera
803
    return point.add(vec(this.resolution.width / 2, this.resolution.height / 2));
1,847✔
804
  }
805

806
  public pageToWorldCoordinates(point: Vector): Vector {
807
    const screen = this.pageToScreenCoordinates(point);
1✔
808
    return this.screenToWorldCoordinates(screen);
1✔
809
  }
810

811
  public worldToPageCoordinates(point: Vector): Vector {
812
    const screen = this.worldToScreenCoordinates(point);
1,875✔
813
    return this.screenToPageCoordinates(screen);
1,875✔
814
  }
815

816
  /**
817
   * Returns a BoundingBox of the top left corner of the screen
818
   * and the bottom right corner of the screen.
819
   *
820
   * World bounds are in world coordinates, useful for culling objects offscreen that are in world space
821
   */
822
  public getWorldBounds(): BoundingBox {
823
    const bounds = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Half)
973✔
824
      .scale(vec(1 / this._camera.zoom, 1 / this._camera.zoom))
825
      .rotate(this._camera.rotation)
826
      .translate(this._camera.drawPos);
827
    return bounds;
973✔
828
  }
829

830
  /**
831
   * Returns a BoundingBox of the top left corner of the screen and the bottom right corner of the screen.
832
   *
833
   * Screen bounds are in screen coordinates, useful for culling objects offscreen that are in screen space
834
   */
835
  public getScreenBounds(): BoundingBox {
836
    const bounds = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero, Vector.Zero);
×
837
    return bounds;
×
838
  }
839

840
  /**
841
   * The width of the game canvas in pixels (physical width component of the
842
   * resolution of the canvas element)
843
   */
844
  public get canvasWidth(): number {
845
    return this.canvas.width;
837✔
846
  }
847

848
  /**
849
   * Returns half width of the game canvas in pixels (half physical width component)
850
   */
851
  public get halfCanvasWidth(): number {
852
    return this.canvas.width / 2;
3✔
853
  }
854

855
  /**
856
   * The height of the game canvas in pixels, (physical height component of
857
   * the resolution of the canvas element)
858
   */
859
  public get canvasHeight(): number {
860
    return this.canvas.height;
837✔
861
  }
862

863
  /**
864
   * Returns half height of the game canvas in pixels (half physical height component)
865
   */
866
  public get halfCanvasHeight(): number {
867
    return this.canvas.height / 2;
3✔
868
  }
869

870
  /**
871
   * Returns the width of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
872
   */
873
  public get drawWidth(): number {
874
    if (this._camera) {
1,969✔
875
      return this.resolution.width / this._camera.zoom;
1,859✔
876
    }
877
    return this.resolution.width;
110✔
878
  }
879

880
  /**
881
   * Returns the width of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
882
   */
883
  public get width(): number {
884
    if (this._camera) {
×
885
      return this.resolution.width / this._camera.zoom;
×
886
    }
887
    return this.resolution.width;
×
888
  }
889

890
  /**
891
   * Returns half the width of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
892
   */
893
  public get halfDrawWidth(): number {
894
    return this.drawWidth / 2;
1,964✔
895
  }
896

897
  /**
898
   * Returns the height of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
899
   */
900
  public get drawHeight(): number {
901
    if (this._camera) {
1,969✔
902
      return this.resolution.height / this._camera.zoom;
1,859✔
903
    }
904
    return this.resolution.height;
110✔
905
  }
906

907
  public get height(): number {
908
    if (this._camera) {
×
909
      return this.resolution.height / this._camera.zoom;
×
910
    }
911
    return this.resolution.height;
×
912
  }
913

914
  /**
915
   * Returns half the height of the engine's visible drawing surface in pixels including zoom and device pixel ratio.
916
   */
917
  public get halfDrawHeight(): number {
918
    return this.drawHeight / 2;
1,964✔
919
  }
920

921
  /**
922
   * Returns screen center coordinates including zoom and device pixel ratio.
923
   */
924
  public get center(): Vector {
925
    return vec(this.halfDrawWidth, this.halfDrawHeight);
16✔
926
  }
927

928
  /**
929
   * Returns the content area in screen space where it is safe to place content
930
   */
931
  public get contentArea(): BoundingBox {
932
    return this._contentArea;
6,643✔
933
  }
934

935
  /**
936
   * Returns the unsafe area in screen space, this is the full screen and some space may not be onscreen.
937
   */
938
  public get unsafeArea(): BoundingBox {
939
    return this._unsafeArea;
10✔
940
  }
941

942
  private _contentArea: BoundingBox = new BoundingBox();
822✔
943
  private _unsafeArea: BoundingBox = new BoundingBox();
822✔
944

945
  private _computeFit() {
946
    document.body.style.margin = '0px';
22✔
947
    document.body.style.overflow = 'hidden';
22✔
948
    const aspect = this.aspectRatio;
22✔
949
    let adjustedWidth = 0;
22✔
950
    let adjustedHeight = 0;
22✔
951
    if (window.innerWidth / aspect < window.innerHeight) {
22✔
952
      adjustedWidth = window.innerWidth;
11✔
953
      adjustedHeight = window.innerWidth / aspect;
11✔
954
    } else {
955
      adjustedWidth = window.innerHeight * aspect;
11✔
956
      adjustedHeight = window.innerHeight;
11✔
957
    }
958

959
    this.viewport = {
22✔
960
      width: adjustedWidth,
961
      height: adjustedHeight
962
    };
963
    this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
22✔
964
    this._unsafeArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
22✔
965
    this.events.emit('resize', {
22✔
966
      resolution: this.resolution,
967
      viewport: this.viewport
968
    } satisfies ScreenResizeEvent);
969
  }
970

971
  private _computeFitScreenAndFill() {
972
    document.body.style.margin = '0px';
11✔
973
    document.body.style.overflow = 'hidden';
11✔
974
    const vw = window.innerWidth;
11✔
975
    const vh = window.innerHeight;
11✔
976
    this._computeFitAndFill(vw, vh);
11✔
977
    this.events.emit('resize', {
11✔
978
      resolution: this.resolution,
979
      viewport: this.viewport
980
    } satisfies ScreenResizeEvent);
981
  }
982

983
  private _computeFitContainerAndFill() {
984
    this.canvas.style.width = '100%';
6✔
985
    this.canvas.style.height = '100%';
6✔
986

987
    this._computeFitAndFill(this.canvas.offsetWidth, this.canvas.offsetHeight, {
6✔
988
      width: 100,
989
      widthUnit: 'percent',
990
      height: 100,
991
      heightUnit: 'percent'
992
    });
993
    this.events.emit('resize', {
6✔
994
      resolution: this.resolution,
995
      viewport: this.viewport
996
    } satisfies ScreenResizeEvent);
997
  }
998

999
  private _computeFitAndFill(vw: number, vh: number, viewport?: ViewportDimension) {
1000
    this.viewport = viewport ?? {
17✔
1001
      width: vw,
1002
      height: vh
1003
    };
1004
    // if the current screen aspectRatio is less than the original aspectRatio
1005
    if (vw / vh <= this._contentResolution.width / this._contentResolution.height) {
17✔
1006
      // compute new resolution to match the original aspect ratio
1007
      this.resolution = {
5✔
1008
        width: (vw * this._contentResolution.width) / vw,
1009
        height: (((vw * this._contentResolution.width) / vw) * vh) / vw
1010
      };
1011
      const clip = (this.resolution.height - this._contentResolution.height) / 2;
5✔
1012
      this._contentArea = new BoundingBox({
5✔
1013
        top: clip,
1014
        left: 0,
1015
        right: this._contentResolution.width,
1016
        bottom: this.resolution.height - clip
1017
      });
1018
      this._unsafeArea = new BoundingBox({
5✔
1019
        top: -clip,
1020
        left: 0,
1021
        right: this._contentResolution.width,
1022
        bottom: this.resolution.height + clip
1023
      });
1024
    } else {
1025
      this.resolution = {
12✔
1026
        width: (((vh * this._contentResolution.height) / vh) * vw) / vh,
1027
        height: (vh * this._contentResolution.height) / vh
1028
      };
1029
      const clip = (this.resolution.width - this._contentResolution.width) / 2;
12✔
1030
      this._contentArea = new BoundingBox({
12✔
1031
        top: 0,
1032
        left: clip,
1033
        right: this.resolution.width - clip,
1034
        bottom: this._contentResolution.height
1035
      });
1036
      this._unsafeArea = new BoundingBox({
12✔
1037
        top: 0,
1038
        left: -clip,
1039
        right: this.resolution.width + clip,
1040
        bottom: this._contentResolution.height
1041
      });
1042
    }
1043
  }
1044

1045
  private _computeFitScreenAndZoom() {
1046
    document.body.style.margin = '0px';
7✔
1047
    document.body.style.overflow = 'hidden';
7✔
1048
    this.canvas.style.position = 'absolute';
7✔
1049

1050
    const vw = window.innerWidth;
7✔
1051
    const vh = window.innerHeight;
7✔
1052

1053
    this._computeFitAndZoom(vw, vh);
7✔
1054
    this.events.emit('resize', {
7✔
1055
      resolution: this.resolution,
1056
      viewport: this.viewport
1057
    } satisfies ScreenResizeEvent);
1058
  }
1059

1060
  private _computeFitContainerAndZoom() {
1061
    this.canvas.style.width = '100%';
6✔
1062
    this.canvas.style.height = '100%';
6✔
1063
    this.canvas.style.position = 'relative';
6✔
1064
    const parent = this.canvas.parentElement;
6✔
1065
    parent.style.overflow = 'hidden';
6✔
1066
    const { offsetWidth: vw, offsetHeight: vh } = this.canvas;
6✔
1067

1068
    this._computeFitAndZoom(vw, vh);
6✔
1069
    this.events.emit('resize', {
6✔
1070
      resolution: this.resolution,
1071
      viewport: this.viewport
1072
    } satisfies ScreenResizeEvent);
1073
  }
1074

1075
  private _computeFitAndZoom(vw: number, vh: number) {
1076
    const aspect = this.aspectRatio;
13✔
1077
    let adjustedWidth = 0;
13✔
1078
    let adjustedHeight = 0;
13✔
1079
    if (vw / aspect < vh) {
13✔
1080
      adjustedWidth = vw;
3✔
1081
      adjustedHeight = vw / aspect;
3✔
1082
    } else {
1083
      adjustedWidth = vh * aspect;
10✔
1084
      adjustedHeight = vh;
10✔
1085
    }
1086

1087
    const scaleX = vw / adjustedWidth;
13✔
1088
    const scaleY = vh / adjustedHeight;
13✔
1089

1090
    const maxScaleFactor = Math.max(scaleX, scaleY);
13✔
1091

1092
    const zoomedWidth = adjustedWidth * maxScaleFactor;
13✔
1093
    const zoomedHeight = adjustedHeight * maxScaleFactor;
13✔
1094

1095
    // Center zoomed dimension if bigger than the screen
1096
    if (zoomedWidth > vw) {
13✔
1097
      this.canvas.style.left = -(zoomedWidth - vw) / 2 + 'px';
3✔
1098
    } else {
1099
      this.canvas.style.left = '';
10✔
1100
    }
1101

1102
    if (zoomedHeight > vh) {
13✔
1103
      this.canvas.style.top = -(zoomedHeight - vh) / 2 + 'px';
8✔
1104
    } else {
1105
      this.canvas.style.top = '';
5✔
1106
    }
1107

1108
    this.viewport = {
13✔
1109
      width: zoomedWidth,
1110
      height: zoomedHeight
1111
    };
1112

1113
    const bounds = BoundingBox.fromDimension(this.viewport.width, this.viewport.height, Vector.Zero);
13✔
1114
    // return safe area
1115
    if (this.viewport.width > vw) {
13✔
1116
      const clip = ((this.viewport.width - vw) / this.viewport.width) * this.resolution.width;
3✔
1117
      bounds.top = 0;
3✔
1118
      bounds.left = clip / 2;
3✔
1119
      bounds.right = this.resolution.width - clip / 2;
3✔
1120
      bounds.bottom = this.resolution.height;
3✔
1121
    }
1122

1123
    if (this.viewport.height > vh) {
13✔
1124
      const clip = ((this.viewport.height - vh) / this.viewport.height) * this.resolution.height;
8✔
1125
      bounds.top = clip / 2;
8✔
1126
      bounds.left = 0;
8✔
1127
      bounds.bottom = this.resolution.height - clip / 2;
8✔
1128
      bounds.right = this.resolution.width;
8✔
1129
    }
1130
    this._contentArea = bounds;
13✔
1131
  }
1132

1133
  private _computeFitContainer() {
1134
    const aspect = this.aspectRatio;
6✔
1135
    let adjustedWidth = 0;
6✔
1136
    let adjustedHeight = 0;
6✔
1137
    let widthUnit: ViewportUnit = 'pixel';
6✔
1138
    let heightUnit: ViewportUnit = 'pixel';
6✔
1139
    const parent = this.canvas.parentElement;
6✔
1140
    if (parent.clientWidth / aspect < parent.clientHeight) {
6✔
1141
      this.canvas.style.width = '100%';
1✔
1142
      adjustedWidth = 100;
1✔
1143
      widthUnit = 'percent';
1✔
1144
      adjustedHeight = this.canvas.offsetWidth / aspect;
1✔
1145
    } else {
1146
      this.canvas.style.height = '100%';
5✔
1147
      adjustedHeight = 100;
5✔
1148
      heightUnit = 'percent';
5✔
1149
      adjustedWidth = this.canvas.offsetHeight * aspect;
5✔
1150
    }
1151

1152
    this.viewport = {
6✔
1153
      width: adjustedWidth,
1154
      widthUnit,
1155
      height: adjustedHeight,
1156
      heightUnit
1157
    };
1158
    this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
6✔
1159
    this.events.emit('resize', {
6✔
1160
      resolution: this.resolution,
1161
      viewport: this.viewport
1162
    } satisfies ScreenResizeEvent);
1163
  }
1164

1165
  private _applyDisplayMode() {
1166
    this._setResolutionAndViewportByDisplayMode(this.parent);
822✔
1167

1168
    // watch resizing
1169
    if (this.parent instanceof Window) {
822✔
1170
      this._browser.window.on('resize', this._resizeHandler);
816✔
1171
    } else {
1172
      this._resizeObserver = new ResizeObserver(() => {
6✔
1173
        this._resizeHandler();
6✔
1174
      });
1175
      this._resizeObserver.observe(this.parent);
6✔
1176
      this.parent.addEventListener('resize', this._resizeHandler);
6✔
1177
    }
1178
  }
1179

1180
  /**
1181
   * Sets the resolution and viewport based on the selected display mode.
1182
   */
1183
  private _setResolutionAndViewportByDisplayMode(parent: HTMLElement | Window) {
1184
    if (this.displayMode === DisplayMode.FillContainer) {
876!
1185
      this.canvas.style.width = '100%';
×
1186
      this.canvas.style.height = '100%';
×
1187
      this.viewport = {
×
1188
        width: 100,
1189
        widthUnit: 'percent',
1190
        height: 100,
1191
        heightUnit: 'percent'
1192
      };
1193
      this.resolution = {
×
1194
        width: this.canvas.offsetWidth,
1195
        height: this.canvas.offsetHeight
1196
      };
1197
    }
1198

1199
    if (this.displayMode === DisplayMode.FillScreen) {
876✔
1200
      document.body.style.margin = '0px';
2✔
1201
      document.body.style.overflow = 'hidden';
2✔
1202
      this.resolution = {
2✔
1203
        width: (<Window>parent).innerWidth,
1204
        height: (<Window>parent).innerHeight
1205
      };
1206

1207
      this.viewport = this.resolution;
2✔
1208
    }
1209

1210
    this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero);
876✔
1211

1212
    if (this.displayMode === DisplayMode.FitScreen) {
876✔
1213
      this._computeFit();
22✔
1214
    }
1215

1216
    if (this.displayMode === DisplayMode.FitContainer) {
876✔
1217
      this._computeFitContainer();
6✔
1218
    }
1219

1220
    if (this.displayMode === DisplayMode.FitScreenAndFill) {
876✔
1221
      this._computeFitScreenAndFill();
11✔
1222
    }
1223

1224
    if (this.displayMode === DisplayMode.FitContainerAndFill) {
876✔
1225
      this._computeFitContainerAndFill();
6✔
1226
    }
1227

1228
    if (this.displayMode === DisplayMode.FitScreenAndZoom) {
876✔
1229
      this._computeFitScreenAndZoom();
7✔
1230
    }
1231

1232
    if (this.displayMode === DisplayMode.FitContainerAndZoom) {
876✔
1233
      this._computeFitContainerAndZoom();
6✔
1234
    }
1235
  }
1236
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc