Use OffscreenCanvas in Web Worker to generate images
Context
My personal project PixZle lets anyone fill in a 32x32 pixel tile to participate in the creation of a potentially infinite image.
The project's home page displays the entire image, and allows users to move around it using mouse or gesture movements. The large image is generated as the user browses
as the user moves through it. To do this, I use an HTMLCanvas on which I draw the necessary tiles. ImageData are
generated on the fly. If this task is performed on the main thread, the user's UI will freeze/lag. That's why I used a WebWorker to do this.
As Remix is for me one of the best current DX Frameworks, PixZle was created with it. To make it easier for me to work with WebWorkers,
vite is used to generate bundles. vite:worker does the job of managing the WebWorker.
Demo
Before we get to the technical side, here's a quick demonstration. Move your mouse cursor or finger inside the rectangle below.
With the worker activated, a red square will follow your path and the movement of it will be fluid. Otherwise, the UI will freeze and the experience will be uninteresting.
The image generated to fill the background is 4800x4800px!
✨ Don't overdo it or your CPU will burn out ✨
Implementation
We'll now go through the various parts of the implementation. Please note that in order to generate ImageData from a Worker,
the web browser must support OffscreenCanvas.
Generate Tile
First, here's the function I created to generate the background image. This function receives a number of tiles x and y
to generate and returns a randomized rgba ImageData.
Web worker
The web worker code is very simple. It declares an onmessage function that will be called by the component and returns
the image generated using the above function.
Component
There are a number of important points to note about the component.
The import of PixelWorker suffixed with ?worker vite:worker 🤩
The instantiation of the worker in a useEffect, as Remix's SSR would not be able to do this.
The worker terminate declared in the useEffect cleanup.
Conclusion
In order to keep the code as small as possible in this post,
I've deliberately removed the parts of the component that handle compatibility checks. If you implement this type of solution for yourself, don't forget to integrate them.
Don't hesitate to send me your comments / criticisms on Twitter|X
And don't forget to create your 32x32 pixels tile on PixZle