• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const DEFAULT_METHOD = 'SVG';
2
3const worker = new Worker('worker.js');
4
5const svgObjectElement = document.getElementById('svg');
6document.getElementById('svg').addEventListener('load', () => {
7
8    const svgElement = svgObjectElement.contentDocument;
9    const svgData = svgToPathStringAndFillColorPairs(svgElement);
10
11    // Send svgData and transfer an offscreenCanvas to the worker for Path2D and CanvasKit rendering
12    const path2dCanvas =
13        document.getElementById('Path2D-canvas').transferControlToOffscreen();
14    worker.postMessage({
15        svgData: svgData,
16        offscreenCanvas: path2dCanvas,
17        type: 'Path2D'
18    }, [path2dCanvas]);
19    const canvasKitCanvas =
20        document.getElementById('CanvasKit-canvas').transferControlToOffscreen();
21    worker.postMessage({
22        svgData: svgData,
23        offscreenCanvas: canvasKitCanvas,
24        type: 'CanvasKit'
25    }, [canvasKitCanvas]);
26
27    // The Canvas2D and CanvasKit rendering methods are executed in a web worker to avoid blocking
28    // the main thread. The SVG rendering method is executed in the main thread. SVG rendering is
29    // not in a worker because it is not possible - the DOM cannot be accessed from a web worker.
30    const svgAnimator = new Animator();
31    svgAnimator.renderer = new SVGRenderer(svgObjectElement);
32    switchRenderMethodCallback(DEFAULT_METHOD)();
33
34    // Listen to framerate reports from the worker, and update framerate text
35    worker.addEventListener('message', ({ data: {renderMethod, framesCount, totalFramesMs} }) => {
36        const fps = fpsFromFramesInfo(framesCount, totalFramesMs);
37        let textEl;
38        if (renderMethod === 'Path2D') {
39            textEl = document.getElementById('Path2D-fps');
40        }
41        if (renderMethod === 'CanvasKit') {
42            textEl = document.getElementById('CanvasKit-fps');
43        }
44        textEl.innerText = `${fps.toFixed(2)} fps over ${framesCount} frames`;
45    });
46    // Update framerate text every second
47    setInterval(() => {
48        if (svgAnimator.framesCount > 0) {
49            const fps = fpsFromFramesInfo(svgAnimator.framesCount, svgAnimator.totalFramesMs);
50            document.getElementById('SVG-fps').innerText =
51                `${fps.toFixed(2)} fps over ${svgAnimator.framesCount} frames`;
52        }
53    }, 1000);
54
55    document.getElementById('SVG-input')
56        .addEventListener('click', switchRenderMethodCallback('SVG'));
57    document.getElementById('Path2D-input')
58        .addEventListener('click', switchRenderMethodCallback('Path2D'));
59    document.getElementById('CanvasKit-input')
60        .addEventListener('click', switchRenderMethodCallback('CanvasKit'));
61
62    function switchRenderMethodCallback(switchMethod) {
63        return () => {
64            // Hide all renderer elements and stop svgAnimator
65            document.getElementById('CanvasKit-canvas').style.visibility = 'hidden';
66            document.getElementById('Path2D-canvas').style.visibility = 'hidden';
67            for (const svgEl of svgAnimator.renderer.svgElArray) {
68                svgEl.style.visibility = 'hidden';
69            }
70            svgAnimator.stop();
71
72            // Show only the active renderer element
73            if (switchMethod === 'SVG') {
74                svgAnimator.start();
75                for (const svgEl of svgAnimator.renderer.svgElArray) {
76                    svgEl.style.visibility = 'visible';
77                }
78            }
79            if (switchMethod === 'CanvasKit') {
80                document.getElementById('CanvasKit-canvas').style.visibility = 'visible';
81            }
82            if (switchMethod === 'Path2D') {
83                document.getElementById('Path2D-canvas').style.visibility = 'visible';
84            }
85            worker.postMessage({ switchMethod });
86        };
87    }
88});
89// Add .data after the load listener so that the listener always fires an event
90svgObjectElement.data = 'garbage.svg';
91
92const EMPTY_SVG_PATH_STRING = 'M 0 0';
93const COLOR_WHITE = '#000000';
94function svgToPathStringAndFillColorPairs(svgElement) {
95    const pathElements = Array.from(svgElement.getElementsByTagName('path'));
96    return pathElements.map((path) => [
97        path.getAttribute('d') ?? EMPTY_SVG_PATH_STRING,
98        path.getAttribute('fill') ?? COLOR_WHITE
99    ]);
100}
101
102const MS_IN_A_SECOND = 1000;
103function fpsFromFramesInfo(framesCount, totalFramesMs) {
104    const averageFrameTime = totalFramesMs / framesCount;
105    return (1 / averageFrameTime) * MS_IN_A_SECOND;
106}
107