/ thread_canvas.html
thread_canvas.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> 6 7 <title>Canvas Thread — Space.js</title> 8 9 <link rel="preconnect" href="https://fonts.gstatic.com"> 10 <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono"> 11 <link rel="stylesheet" href="assets/css/style.css"> 12 13 <script type="module"> 14 import { Thread, ticker } from './src/index.js'; 15 16 // Based on https://codepen.io/zepha/pen/VpXvBJ 17 18 class CanvasNoise { 19 constructor(params) { 20 const defaults = { 21 width: 1, 22 height: 1, 23 tileSize: 250, 24 monochrome: true 25 }; 26 27 this.params = Object.assign(defaults, params); 28 29 this.initCanvas(); 30 } 31 32 initCanvas() { 33 this.canvas = this.params.canvas; 34 this.canvas.width = this.params.width; 35 this.canvas.height = this.params.height; 36 this.context = this.canvas.getContext('2d'); 37 38 this.tile = typeof window === 'undefined' ? new OffscreenCanvas(this.params.tileSize, this.params.tileSize) : document.createElement('canvas'); 39 this.tile.width = this.params.tileSize; 40 this.tile.height = this.params.tileSize; 41 this.tileContext = this.tile.getContext('2d'); 42 } 43 44 // Public methods 45 46 resize = (width, height, dpr) => { 47 this.canvas.width = Math.round(width * dpr); 48 this.canvas.height = Math.round(height * dpr); 49 50 this.tile.width = Math.round(this.params.tileSize * dpr); 51 this.tile.height = Math.round(this.params.tileSize * dpr); 52 53 this.width = this.canvas.width / this.tile.width + 1; // One extra tile for row offset 54 this.height = this.canvas.height / this.tile.height; 55 56 this.update(); 57 }; 58 59 update = () => { 60 const pixels = new ImageData(this.tile.width, this.tile.height); 61 62 for (let i = 0, l = pixels.data.length; i < l; i += 4) { 63 const rand = 255 * Math.random(); 64 65 pixels.data[i] = this.params.monochrome ? rand : 255 * Math.random(); 66 pixels.data[i + 1] = this.params.monochrome ? rand : 255 * Math.random(); 67 pixels.data[i + 2] = this.params.monochrome ? rand : 255 * Math.random(); 68 pixels.data[i + 3] = 255; 69 } 70 71 this.tileContext.putImageData(pixels, 0, 0); 72 73 for (let x = 0, xl = this.width; x < xl; x++) { 74 for (let y = 0, yl = this.height; y < yl; y++) { 75 this.context.drawImage(this.tile, x * this.tile.width - (y % 2 === 0 ? this.tile.width / 2 : 0), y * this.tile.height, this.tile.width, this.tile.height); 76 } 77 } 78 }; 79 } 80 81 class CanvasNoiseThread { 82 constructor() { 83 this.addListeners(); 84 } 85 86 addListeners() { 87 addEventListener('message', this.onMessage); 88 ticker.start(); 89 } 90 91 // Event handlers 92 93 onMessage = ({ data }) => { 94 this[data.message.fn].call(this, data.message); 95 }; 96 97 onUpdate = () => { 98 this.noise.update(); 99 }; 100 101 // Public methods 102 103 init = ({ params }) => { 104 this.noise = new CanvasNoise(params); 105 }; 106 107 resize = ({ width, height, dpr }) => { 108 this.noise.resize(width, height, dpr); 109 }; 110 111 start = ({ fps }) => { 112 ticker.add(this.onUpdate, fps); 113 }; 114 115 stop = () => { 116 ticker.remove(this.onUpdate); 117 }; 118 } 119 120 class CanvasNoiseController { 121 static init(params) { 122 this.params = params; 123 124 this.initThread(); 125 } 126 127 static initThread() { 128 this.thread = new Thread({ 129 imports: [ 130 ['../src/index.js', 'ticker'] 131 ], 132 classes: [CanvasNoise], 133 controller: [CanvasNoiseThread, 'void init', 'void resize', 'void start', 'void stop'] 134 }); 135 136 this.element = this.params.canvas; 137 this.params.canvas = this.element.transferControlToOffscreen(); 138 139 this.thread.init({ params: this.params, buffer: [this.params.canvas] }); 140 } 141 142 // Public methods 143 144 static resize = (width, height, dpr) => { 145 this.thread.resize({ width, height, dpr }); 146 }; 147 148 static start = () => { 149 this.thread.start({ fps: 20 }); 150 }; 151 152 static stop = () => { 153 this.thread.stop(); 154 }; 155 } 156 157 class App { 158 static async init() { 159 this.initCanvas(); 160 this.initControllers(); 161 162 this.addListeners(); 163 this.onResize(); 164 } 165 166 static initCanvas() { 167 this.canvas = document.createElement('canvas'); 168 document.body.appendChild(this.canvas); 169 } 170 171 static initControllers() { 172 CanvasNoiseController.init({ canvas: this.canvas }); 173 } 174 175 static addListeners() { 176 window.addEventListener('resize', this.onResize); 177 window.addEventListener('load', this.onLoad); 178 } 179 180 // Event handlers 181 182 static onResize = () => { 183 const width = document.documentElement.clientWidth; 184 const height = document.documentElement.clientHeight; 185 const dpr = window.devicePixelRatio; 186 187 this.canvas.style.width = `${width}px`; 188 this.canvas.style.height = `${height}px`; 189 190 CanvasNoiseController.resize(width, height, dpr); 191 }; 192 193 static onLoad = () => { 194 CanvasNoiseController.start(); 195 }; 196 } 197 198 App.init(); 199 </script> 200 </head> 201 <body> 202 </body> 203 </html>