/ 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>