From 3130323676eebbbc55b4ddcbc2dc53078894f8fa Mon Sep 17 00:00:00 2001 From: sockmaster27 <61235930+sockmaster27@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:30:13 +0100 Subject: [PATCH] Fix resize observer for WebKit --- packages/svader/src/lib/BaseShader.svelte | 57 +++++--------- .../src/lib/devicePixelResizeObserver.js | 76 +++++++++++++++++++ 2 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 packages/svader/src/lib/devicePixelResizeObserver.js diff --git a/packages/svader/src/lib/BaseShader.svelte b/packages/svader/src/lib/BaseShader.svelte index efbfe22..05f7ce2 100644 --- a/packages/svader/src/lib/BaseShader.svelte +++ b/packages/svader/src/lib/BaseShader.svelte @@ -2,6 +2,7 @@ import { pixelScale, clamp } from "./utils.js"; import { intersectionObserver } from "./intersectionObserver.js"; import { onDestroy, onMount } from "svelte"; + import { devicePixelResizeObserver } from "./devicePixelResizeObserver.js"; /** * The width of the canvas element. @@ -75,36 +76,6 @@ /** @type {HTMLCanvasElement} */ export let canvasElement; - /** - * Size of the containing element in physical device pixels, as reported by `devicePixelContentBoxSize`. - * - * @type {ResizeObserverSize[]} - */ - let containerSize; - /** - * Width of the containing element in physical device pixels. - */ - $: containerWidth = containerSize?.[0]?.inlineSize; - /** - * Height of the containing element in physical device pixels. - */ - $: containerHeight = containerSize?.[0]?.blockSize; - - /** - * Size of the canvas in physical device pixelss, as reported by `devicePixelContentBoxSize`. - * - * @type {ResizeObserverSize[]} - */ - let canvasSize; - /** - * Width of the canvas in physical device pixels. - */ - $: canvasWidth = canvasSize?.[0]?.inlineSize; - /** - * Height of the canvas in physical device pixels. - */ - $: canvasHeight = canvasSize?.[0]?.blockSize; - /** * The handle returned by {@linkcode requestAnimationFrame}. * `null` if no render pass has been requested. @@ -147,12 +118,14 @@ } /** - * @param {number} canvasWidth - * @param {number} canvasHeight + * @param {import("./devicePixelResizeObserver.js").DevicePixelResizeEvent} event */ - function updateCanvasSizeInner(canvasWidth, canvasHeight) { + function updateCanvasSizeInner(event) { // Resizing must happen right before the next render pass. renderCallbacks.push(() => { + const canvasWidth = event.detail.width; + const canvasHeight = event.detail.height; + canvasElement.width = canvasWidth; canvasElement.height = canvasHeight; @@ -162,11 +135,15 @@ requestRender(); } - $: if (canvasWidth !== undefined && canvasHeight !== undefined) - updateCanvasSizeInner(canvasWidth, canvasHeight); + /** + * @param {import("./devicePixelResizeObserver.js").DevicePixelResizeEvent} event + */ + function updateContainerSizeInner(event) { + const canvasWidth = event.detail.width; + const canvasHeight = event.detail.height; - $: if (containerWidth !== undefined && containerHeight !== undefined) - updateContainerSize(containerWidth, containerHeight); + updateContainerSize(canvasWidth, canvasHeight); + } $: if (offsetX !== undefined && offsetY !== undefined) updateOffset(offsetX, offsetY); @@ -209,7 +186,8 @@ {#if canRender}
} A + * @typedef {import("svelte/action").ActionReturn} ActionReturn + */ + +/** + * @typedef {CustomEvent<{ width: number, height: number }>} DevicePixelResizeEvent + * @typedef {{ "on:devicepixelresize"?: (e: DevicePixelResizeEvent) => void }} Attributes + */ + +/** + * Check if "devicePixelContentBoxSize" is supported (it's not in Safari). + * + * @returns {Promise} + */ +async function checkDevicePixelContentBox() { + return new Promise(resolve => { + const observer = new ResizeObserver(entries => { + resolve( + entries.every(entry => "devicePixelContentBoxSize" in entry), + ); + observer.disconnect(); + }); + observer.observe(document.body, { box: "device-pixel-content-box" }); + }).catch(() => false); +} +const hasDevicePixelContentBoxPromise = checkDevicePixelContentBox(); +let hasDevicePixelContentBox = false; + +/** + * Observe all changes to the device-pixel-content-box of a node. + * + * @param {Element} node + * @returns {ActionReturn} + */ +export function devicePixelResizeObserver(node) { + let observer = new ResizeObserver(callback); + + /** @param {ResizeObserverEntry[]} entries */ + function callback(entries) { + const entry = entries[0]; + + const detail = hasDevicePixelContentBox + ? { + width: entry.devicePixelContentBoxSize[0].inlineSize, + height: entry.devicePixelContentBoxSize[0].blockSize, + } + : { + // Not perfect, but it's the best we can do in this case. + width: + entry.contentBoxSize[0].inlineSize * + window.devicePixelRatio, + height: + entry.contentBoxSize[0].blockSize * + window.devicePixelRatio, + }; + + node.dispatchEvent(new CustomEvent("devicepixelresize", { detail })); + } + + hasDevicePixelContentBoxPromise.then(r => { + hasDevicePixelContentBox = r; + observer.observe(node, { + box: hasDevicePixelContentBox + ? "device-pixel-content-box" + : "content-box", + }); + }); + + return { + destroy() { + observer.disconnect(); + }, + }; +}