Now you don’t have to draw images in your main thread because of the off-screen Canvas!

Canvas is a very popular representation and an entry point to WebGL. It can draw graphics, pictures, display animations, and even process video content. It is often used to create cool user interfaces in rich media Web applications or to create online (Web) games.

It is very flexible, which means that content drawn on Canvas can be programmed. For example, 🌰, JavaScript provides a family of apis for Canvas. These give the Canvas great flexibility.

But at the same time, on some modern Web sites, script parsing runs are one of the biggest problems in achieving smooth user feedback. Since Canvas calculation and rendering and user operation response all happen in the same thread, calculation operation in animation (sometimes time-consuming) will cause App lag and reduce user experience.

Fortunately, OffscreenCanvas can solve this problem very well!

So far, Canvas’s drawing capabilities have been tied to the < Canvas > tag, which means that the Canvas API and DOM are coupled. As its name suggests, OffscreenCanvas decouples the DOM from the Canvas API by moving the Canvas off the screen.

Due to this decoupling, The renderings of Canvas and DOM are completely separate and a bit faster than normal Canvas, just because the two (Canvas and DOM) are not synchronized. But more importantly, by separating the two, Canvas can be used in Web workers, even if there is no DOM in Web workers. This gives more possibilities to Canvas.

Use OffscreenCanvas in Worker

Workers is a Web version of a thread — it allows you to run your code behind the scenes. Putting part of your code into Worker gives your main thread more free time, which can improve your user experience. Just like there is no DOM, there is no Canvas API in workers until now.

While Offscreen Anvas does not rely on DOM, Canvas API can be replaced by some method in Worker. Here is how I used OffscreenCanvas in Worker to calculate gradient colors 🌰 :

// file: worker.js

function getGradientColor(percent) {
    const canvas = new OffscreenCanvas(100, 1);
    const ctx = canvas.getContext('2d');
    const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
    gradient.addColorStop(0, 'red');
    gradient.addColorStop(1, 'blue');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, ctx.canvas.width, 1);
    const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
    const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
    return rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[]);
}

getGradientColor(40);  // rgba(152, 0, 104, 255 )


Copy the code

Do not block the main thread

When we move a lot of computation to the Worker, we can release resources on the main thread, which is interesting. We can use the map the regular Canvas to OffscreenCanvas transferControlToOffscreen method instance. All operations that are then applied to jobs Canvas will be automatically rendered on the source Canvas.

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);


Copy the code

OffscreenCanvas are [transferable](developer.mozilla.org/en-US/docs/…) . In addition to specifying it as one of the fields in passing information, it needs to be passed as the second parameter in postMessage (the method that passes information to the Worker) so that it can be used in the Worker thread’s context.

In 🌰 below, “complex calculations” occur when the color theme changes, a calculation that can take milliseconds even on a high-performance desktop. You can choose to run the animation on the main thread or on the Worker. On the main thread, you won’t be able to interact with buttons when complex computations start running – the thread is blocked. However, under Worker, the response of UI is not affected.

Demo

It is also another way of interpreting: the busy main thread does not affect the animation running on the Worker either. So even if the main thread is very busy, you can use this feature to avoid dropping frames and ensure smooth animation:

Demo

The example above shows that on a normal Canvas, the animation is blocked when busy tasks are added to the main thread, while the Worker-based OffscreenCanvas plays smoothly.

Use with popular libraries

Thanks to the Screencanvas API, which is generally compatible with the normal Canvas element API, you can easily use it gradually and also use some of the best graphics libraries/frameworks in the community.

For example 🌰, you can check for features, if available, by specifying the canvas configuration item in the render constructor, and then implementing the functionality used with three.js:

const canvasEl = document.querySelector("canvas");
const canvas = ('OffscreenCanvas' in window) ? canvasEl.transferControlToOffscreen() : canvasEl;
canvas.style = { width: 0, height: 0 }
const renderer = new THREE.WebGLRenderer({ canvas: canvas });


Copy the code

The problem with the above example is that three. js requires the Canvas to have style.width and style.height properties. Offscreen Kanvas is completely separate from DOM and does not have these properties. So you’ll need to provide these attributes yourself, either by removing them from the three.js logic or by writing your own logic that relates these values to the initial Canvas size.

Here is a demo running a basic three.js animation:

Demo

However, keep in mind that some OF the DOM-related apis are not readily available in Worker, so you may need more workarounds if you want to use more advanced three.js features such as textures. Check out the Google I/O 2017 video for some ideas that have already been tried.

The commit() method shown in the example in this video is not recommended. Please switch to the worker. RequestAnimationFrame.

conclusion

If you use a lot of graphics and painting, Jobs Animation vas can improve the performance of your APP. It enables Worker to handle canvas rendering, making your APP better use of multi-core system.

OffscreenCanvas is now available in Chrome 69 without enabling flag. It is also being implemented by Firefox. Because its API is so similar to a normal Canvas element, you can easily detect its features and use it step by step without breaking the logic of your existing APP or library. It has a performance advantage in any situation that involves graphical computation and animation presentation and is not DOM dependent (that is, not much on the DOM API).

Other resources

  • W3c spec

  • chromestatus.com entry