Mirror: A frag-canvas custom element to apply Shadertoy fragment shaders to a canvas or image/video element
1let intersectionObserver: IntersectionObserver | undefined;
2const intersectionListeners = new Map<Element, (isVisible: boolean) => void>();
3const getIntersectionObserver = () =>
4 intersectionObserver ||
5 (intersectionObserver = new IntersectionObserver(entries => {
6 for (const entry of entries) {
7 const listener = intersectionListeners.get(entry.target);
8 if (listener) {
9 listener(entry.isIntersecting);
10 }
11 }
12 }));
13
14export function trackVisibility(
15 element: Element,
16 onChange: (isVisible: boolean) => void
17): () => void {
18 const observer = getIntersectionObserver();
19 intersectionListeners.set(element, onChange);
20 observer.observe(element);
21 return () => {
22 observer.unobserve(element);
23 intersectionListeners.delete(element);
24 };
25}
26
27let resizeObserver: ResizeObserver | undefined;
28const resizeListeners = new Map<
29 Element,
30 (box: { inlineSize: number; blockSize: number }) => void
31>();
32const getResizeObserver = () =>
33 resizeObserver ||
34 (resizeObserver = new ResizeObserver(entries => {
35 for (const entry of entries) {
36 const listener = resizeListeners.get(entry.target);
37 if (listener) listener(entry.devicePixelContentBoxSize[0]);
38 }
39 }));
40
41export function trackResizes(
42 element: Element,
43 onChange: (box: { inlineSize: number; blockSize: number }) => void
44): () => void {
45 const observer = getResizeObserver();
46 resizeListeners.set(element, onChange);
47 observer.observe(element, { box: 'device-pixel-content-box' });
48 return () => {
49 resizeListeners.delete(element);
50 };
51}
52
53export function trackTextUpdates(
54 element: Element,
55 onChange: () => void
56): () => void {
57 const observer = new MutationObserver(onChange);
58 observer.observe(element, { subtree: true, characterData: true });
59 return () => {
60 observer.disconnect();
61 };
62}