1 const DEFAULT_METHOD = 'SVG';
3 const worker = new Worker('worker.js');
5 const svgObjectElement = document.getElementById('svg');
6 document.getElementById('svg').addEventListener('load', () => {
8 const svgElement = svgObjectElement.contentDocument;
9 const svgData = svgToPathStringAndFillColorPairs(svgElement);
11 // Send svgData and transfer an offscreenCanvas to the worker for Path2D and CanvasKit rendering
13 document.getElementById('Path2D-canvas').transferControlToOffscreen();
16 offscreenCanvas: path2dCanvas,
19 const canvasKitCanvas =
20 document.getElementById('CanvasKit-canvas').transferControlToOffscreen();
23 offscreenCanvas: canvasKitCanvas,
25 }, [canvasKitCanvas]);
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)();
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);
38 if (renderMethod === 'Path2D') {
39 textEl = document.getElementById('Path2D-fps');
41 if (renderMethod === 'CanvasKit') {
42 textEl = document.getElementById('CanvasKit-fps');
44 textEl.innerText = `${fps.toFixed(2)} fps over ${framesCount} frames`;
46 // Update framerate text every second
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`;
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'));
62 function switchRenderMethodCallback(switchMethod) {
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';
72 // Show only the active renderer element
73 if (switchMethod === 'SVG') {
75 for (const svgEl of svgAnimator.renderer.svgElArray) {
76 svgEl.style.visibility = 'visible';
79 if (switchMethod === 'CanvasKit') {
80 document.getElementById('CanvasKit-canvas').style.visibility = 'visible';
82 if (switchMethod === 'Path2D') {
83 document.getElementById('Path2D-canvas').style.visibility = 'visible';
85 worker.postMessage({ switchMethod });
89 // Add .data after the load listener so that the listener always fires an event
90 svgObjectElement.data = 'garbage.svg';
92 const EMPTY_SVG_PATH_STRING = 'M 0 0';
93 const COLOR_WHITE = '#000000';
94 function 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
102 const MS_IN_A_SECOND = 1000;
103 function fpsFromFramesInfo(framesCount, totalFramesMs) {
104 const averageFrameTime = totalFramesMs / framesCount;
105 return (1 / averageFrameTime) * MS_IN_A_SECOND;