svader
Version:
Create GPU-rendered Svelte components
85 lines (76 loc) • 2.66 kB
JavaScript
/**
* @template T
* @template {Record<string, any>} A
* @typedef {import("svelte/action").ActionReturn<T, A>} ActionReturn<T, A>
*/
/**
* @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<boolean>}
*/
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<void, Attributes>}
*/
export function devicePixelResizeObserver(node) {
let observer = new ResizeObserver(callback);
/** @param {ResizeObserverEntry[]} entries */
function callback(entries) {
const entry = entries[0];
let detail;
if (hasDevicePixelContentBox) {
detail = {
width: entry.devicePixelContentBoxSize[0].inlineSize,
height: entry.devicePixelContentBoxSize[0].blockSize,
};
} else if (entry.contentBoxSize) {
// Not perfect, but it's the best we can do in this case.
detail = {
width:
entry.contentBoxSize[0].inlineSize *
window.devicePixelRatio,
height:
entry.contentBoxSize[0].blockSize * window.devicePixelRatio,
};
} else {
// Fallback for older browsers without contentBoxSize support
detail = {
width: entry.contentRect.width * window.devicePixelRatio,
height: entry.contentRect.height * 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();
},
};
}