1// Returns an [x, y] point on a circle, with given origin and radius, at a given angle 2// counter-clockwise from the positive horizontal axis. 3function circleCoordinates(origin, radius, radians) { 4 return [ 5 origin[0] + Math.cos(radians) * radius, 6 origin[1] + Math.sin(radians) * radius 7 ]; 8} 9 10// Animator handles calling and stopping requestAnimationFrame and keeping track of framerate. 11class Animator { 12 framesCount = 0; 13 totalFramesMs = 0; 14 animating = false; 15 renderer = null; 16 17 start() { 18 if (this.animating === false) { 19 this.animating = true; 20 this.framesCount = 0; 21 const frameStartMs = performance.now(); 22 23 const drawFrame = () => { 24 if (this.animating && this.renderer) { 25 requestAnimationFrame(drawFrame); 26 this.framesCount++; 27 28 const [x, y] = circleCoordinates([-70, -70], 50, this.framesCount/100); 29 this.renderer.render(x, y); 30 31 const frameTimeMs = performance.now() - frameStartMs; 32 this.totalFramesMs = frameTimeMs; 33 } 34 }; 35 requestAnimationFrame(drawFrame); 36 } 37 } 38 39 stop() { 40 this.animating = false; 41 } 42} 43 44// The following three renderers draw a repeating pattern of paths. 45// The approximate height and width of this repeated pattern is given by PATTERN_BOUNDS: 46const PATTERN_BOUNDS = 600; 47// And the spacing of the pattern (distance between repeated paths) is given by PATTERN_SPACING: 48const PATTERN_SPACING = 70; 49 50class SVGRenderer { 51 constructor(svgObjectElement) { 52 this.svgObjectElement = svgObjectElement; 53 this.svgElArray = []; 54 // Create an SVG element for every position in the pattern 55 for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) { 56 for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) { 57 const clonedSVG = svgObjectElement.cloneNode(true); 58 this.svgElArray.push(clonedSVG); 59 svgObjectElement.parentElement.appendChild(clonedSVG); 60 } 61 } 62 } 63 64 render(x, y) { 65 let i = 0; 66 for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) { 67 for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) { 68 this.svgElArray[i].style.transform = `translate(${x + xo}px, ${y + yo}px)`; 69 i++; 70 } 71 } 72 } 73} 74 75class Path2dRenderer { 76 constructor(svgData, offscreenCanvas) { 77 this.data = svgData.map(([pathString, fillColor]) => [new Path2D(pathString), fillColor]); 78 79 this.ctx = offscreenCanvas.getContext('2d'); 80 } 81 82 render(x, y) { 83 const ctx = this.ctx; 84 85 ctx.clearRect(0, 0, 500, 500); 86 87 for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) { 88 for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) { 89 ctx.save(); 90 ctx.translate(x + xo, y + yo); 91 92 for (const [path, fillColor] of this.data) { 93 ctx.fillStyle = fillColor; 94 ctx.fill(path); 95 } 96 ctx.restore(); 97 } 98 } 99 } 100} 101 102class CanvasKitRenderer { 103 constructor(svgData, offscreenCanvas, CanvasKit) { 104 this.CanvasKit = CanvasKit; 105 this.data = svgData.map(([pathString, fillColor]) => [ 106 CanvasKit.Path.MakeFromSVGString(pathString), 107 CanvasKit.parseColorString(fillColor) 108 ]); 109 110 this.surface = CanvasKit.MakeWebGLCanvasSurface(offscreenCanvas, null); 111 if (!this.surface) { 112 throw 'Could not make canvas surface'; 113 } 114 this.canvas = this.surface.getCanvas(); 115 116 this.paint = new CanvasKit.Paint(); 117 this.paint.setAntiAlias(true); 118 this.paint.setStyle(CanvasKit.PaintStyle.Fill); 119 } 120 121 render(x, y) { 122 const canvas = this.canvas; 123 124 canvas.clear(this.CanvasKit.WHITE); 125 126 for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) { 127 for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) { 128 canvas.save(); 129 canvas.translate(x + xo, y + yo); 130 131 for (const [path, color] of this.data) { 132 this.paint.setColor(color); 133 canvas.drawPath(path, this.paint); 134 } 135 canvas.restore(); 136 } 137 } 138 this.surface.flush(); 139 } 140} 141