UNPKG

maplibre-gl

Version:

BSD licensed community fork of mapbox-gl, a WebGL interactive maps library

100 lines (88 loc) 4.35 kB
import {AbortError} from './abort_error.ts'; import {offscreenCanvasSupported} from './offscreen_canvas_supported.ts'; import {isOffscreenCanvasDistorted} from './offscreen_canvas_distorted.ts'; import {subscribe} from './util.ts'; let linkEl; let reducedMotionQuery: MediaQueryList; let reducedMotionOverride: boolean | undefined; /** */ export const browser = { /** * Schedules a callback to be invoked on the next animation frame. * @param abortController - Controller to abort the scheduled frame. * @param fn - Callback to invoke with the paint start timestamp. * @param reject - Callback to invoke if the frame is aborted. * @param targetWindow - Optional window to use for requestAnimationFrame. * When the map is rendered in a popup window or iframe, pass the owning * window to ensure animation frames continue even when the main window * is not focused. */ frame(abortController: AbortController, fn: (paintStartTimestamp: number) => void, reject: (error: Error) => void, targetWindow?: Window): void { const win = targetWindow || window; const frameId = win.requestAnimationFrame((paintStartTimestamp)=>{ unsubscribe(); fn(paintStartTimestamp); }); const {unsubscribe} = subscribe(abortController.signal, 'abort', () => { unsubscribe(); win.cancelAnimationFrame(frameId); reject(new AbortError(abortController.signal.reason)); }, false); }, /** * Returns a promise that resolves on the next animation frame. * @param abortController - Controller to abort the scheduled frame. * @param targetWindow - Optional window to use for requestAnimationFrame. * @see {@link browser.frame} */ frameAsync(abortController: AbortController, targetWindow?: Window): Promise<number> { return new Promise((resolve, reject) => { this.frame(abortController, resolve, reject, targetWindow); }); }, getImageData(img: HTMLImageElement | ImageBitmap, padding: number = 0): ImageData { const context = this.getImageCanvasContext(img); return context.getImageData(-padding, -padding, img.width + 2 * padding, img.height + 2 * padding); }, /** * Draws an image into a 2D canvas whose pixels can be read back cheaply. An `OffscreenCanvas` * is used where the browser has one that reads back faithfully: drawing a GPU-backed * `ImageBitmap` into a document canvas opened with `willReadFrequently` stalls the main thread * for tens of milliseconds on GPU-accelerated browsers, an `OffscreenCanvas` does not. */ getImageCanvasContext(img: HTMLImageElement | ImageBitmap): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D { let context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null; if (offscreenCanvasSupported() && !isOffscreenCanvasDistorted()) { context = new OffscreenCanvas(img.width, img.height).getContext('2d', {willReadFrequently: true}); } else { const canvas = window.document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; context = canvas.getContext('2d', {willReadFrequently: true}); } if (!context) { throw new Error('failed to create canvas 2d context'); } context.drawImage(img, 0, 0, img.width, img.height); return context; }, resolveURL(path: string): string { linkEl ||= document.createElement('a'); linkEl.href = path; return linkEl.href; }, get hardwareConcurrency(): number { return (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) || 4; }, get prefersReducedMotion(): boolean { if (reducedMotionOverride !== undefined) return reducedMotionOverride; // In case your test crashes when checking matchMedia, call setMatchMedia from 'src/util/test/util' if (!matchMedia) return false; //Lazily initialize media query reducedMotionQuery ??= matchMedia('(prefers-reduced-motion: reduce)'); return reducedMotionQuery.matches; }, set prefersReducedMotion(value: boolean) { reducedMotionOverride = value; } };