OffscreenCanvas is an experimental new feature designed to improve the rendering performance and experience of Canvas 2D/3D drawing. The apis for Apps are simple, but it’s important to really learn how to use them.

Both OffscreenCanvas and Canvas are objects for rendering graphics. The difference is that Canvas can only be used in the Window environment, while OffscreenCanvas can be used in both the Window environment and the Web worker, which makes it possible to render off-screen without affecting the main thread of the browser.

Associated with it and ImageBitmap object and ImageBitmapRenderingContext.

ImageBitmap

An ImageBitmap object represents a bitmap image that can be drawn to a canvas with low latency. ImageBitmap provides an asynchronous and resource-efficient way to prepare the infrastructure for WebGL rendering. ImageBitmap can be created using the createImageBitmap function, which can be generated from a variety of image sources. Can also through OffscreenCanvas. TransferToImageBitmap function.

attribute

Imagebitmap. height Read-only, unsigned, long integer that represents the height of the CSS pixel of ImageData. Imagebitmap. width Read-only unsigned long integer number representing the CSS pixel width of ImageData.

function

Imagebitmap.close () frees all graphic resources associated with an ImageBitmap.

createImageBitmap

CreateImageBitmap Is used to create an ImageBitmap object. This function exists in Windows and Workers. It accepts a variety of different image sources and returns a Promise, resolve as ImageBitmap.

createImageBitmap(image[, options]).then(function(response) { ... });
createImageBitmap(image, sx, sy, sw, sh[, options]).then(function(response) { ... });Copy the code

For more information, please refer to developer.mozilla.org/zh-CN/docs/…

Create OffscreenCanvas

There are two ways to create OffscreenCanvas, either directly through the constructor of OffscreenCanvas. For example:

var offscreen = new OffscreenCanvas(width, height); // width and height indicate the width and height.Copy the code

In a different way, is to use canvas transferControlToOffscreen function to obtain a OffscreenCanvas object, draw the OffscreenCanvas object, at the same time will draw the canvas object. For example:

var canvas = document.getElementById('canvas'); //var ctx = canvas.getContext('2d'); var offscreen = canvas.transferControlToOffscreen(); // canvas.getContext('2d'); / / complainsCopy the code

The code above code first for page elements canvas object, then calls the canvas object transferControlToOffscreen function creates a OffscreenCanvas object offscreen, and the control to the offscreen.

It is important to note that the canvas object invokes the function transferControlToOffscreen after handing over control, can’t get drawing context, call canvas. GetContext (‘ 2 d) complains; The same principle, if the canvas has access to the draw context, call transferControlToOffscreen complains.

OffscreenCanvas transferToImageBitmap function

The transferToImageBitmap function creates an ImageBitmap object from the draw content of the OffscreenCanvas object. This object can be used to draw to other canvas.

For example, a common use is to put a time-consuming drawing on the OffscreenCanvas object under the Web worker. After the drawing is completed, an ImageBitmap object is created and passed to the page end, and the ImageBitmap object is drawn on the page end.

Here is the sample code in the main thread:

var worker2 = null,canvasBitmap, ctxBitmap; function init() { canvasBitmap = document.getElementById('canvas-bitmap'); ctxBitmap = canvasBitmap.getContext('2d'); worker2 = new Worker('./bitmap_worker.js'); worker2.postMessage({msg:'init'}); Function (e) {ctxBitmap. DrawImage (e.data.imagebitmap,0,0); } } function redraw() { ctxBitmap.clearRect(0, 0, canvasBitmap.width, canvasBitmap.height) worker2.postMessage({msg:'draw'}); }Copy the code

Worker thread:

var offscreen,ctx; onmessage = function (e) { if(e.data.msg == 'init'){ init(); draw(); }else if(e.data.msg == 'draw'){ draw(); } } function init() { offscreen = new OffscreenCanvas(512, 512); ctx = offscreen.getContext("2d"); } the function the draw () {CTX. ClearRect (0, 0, offscreen. Width, offscreen. Height); for(var i = 0; i < 10000; i ++){ for(var j = 0; j < 1000; J + +) {CTX. FillRect (I * 3, j * 3,2,2); } } var imageBitmap = offscreen.transferToImageBitmap(); postMessage({imageBitmap:imageBitmap},[imageBitmap]); }Copy the code
  • In the main thread, get the Canvas object, then generate the worker object and pass the draw command to the worker.
  • In the worker thread, create an OffscreenCanvas and execute the drawing command. After drawing, create an imageBitmap object through transferToImageBitmap function. The imageBitmap object is passed to the main line via postMessage.
  • After receiving the imageBitmap object, the main thread draws the imageBitmap onto the Canvas object.

The final drawing looks like this:

The advantage of putting drawing into the Web worker is that the drawing process does not block the main thread. The reader can run the code to see for himself, and during the drawing process, the interface can be interactive, such as selecting drop-down boxes.

ImageBitmapRenderingContext

ImageBitmapRenderingContext interface is canvas rendering context, it is only available using a given ImageBitmap replace the function of the canvas. Its context ID (HTMLCanvasElement. GetContext () or OffscreenCanvas. GetContext () the first parameter) is “bitmaprenderer”. This interface can be used with the window context and worker context.

methods

ImageBitmapRenderingContext. TransferFromImageBitmap function used in the corresponding canvas “rendering context” is shown in the given ImageBitmap object. Ownership of the ImageBitmap is transferred to the canvas.

In the previous example, you can make the following changes:

function init() { ... ctxBitmap = canvasBitmap.getContext('bitmaprenderer'); . worker2.onmessage = function (e) { ctxBitmap.transferFromImageBitmap(e.data.imageBitmap); }}Copy the code

First of all, the rendering context id to “bitmaprenderer”, return to the frontal ctxBitmap is a ImageBitmapRenderingContext object. Then, when rendering the ImageBitmap object, change the drawImage function to the transferFromImageBitmap function.

The final rendering looks exactly like the one shown above.

TransferControlToOffscreen function

TransferControlToOffscreen function through the page to create a OffscreenCanvas canvas object. Why do this when you can create OffscreenCanvas objects from the constructor? The reason is this: looking at the previous example, we created an OffscreenCanvas object in the worker thread and drew and then fetched the ImageBitmap object, passing the ImageBitmap to the page through the Web Worker communication.

And if using canvas. TransferControlToOffscreen generated OffscreenCanvas object, no longer need to pass through web worker communication the effect of drawing, generate OffscreenCanvas object, The drawing of the OffscreenCanvas object is automatically displayed on top of the Canvas element. This has a self-evident advantage over Web worker communication.

Through transferControlToOffscreen function creates OffscreenCanvas object has two major functions:

  • Avoid a large number of calculations blocking the main thread during drawing
  • Avoid main thread heavy task blocking drawing

We will use an example to illustrate the above conclusion.

First, we write a Circle class. This class is used to draw a Circle, and can animate it to change the radius of the Circle:

class Circle { constructor(ctx){ this.ctx = ctx; this.r = 0; this.rMax = 50; this.color = 'black'; this.bindAnimate = this.animate.bind(this); } draw(){ this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.ctx.canvas.width/2,this.ctx.canvas.height/2,this.r,0,Math.PI*2); this.ctx.fill(); } animate(){ this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); this.r = this.r + 1; if(this.r > this.rMax){ this.r = 0; } this.draw(); requestAnimationFrame(this.bindAnimate); } changeColor(){ fibonacci(41); if(this.color == 'black'){ this.color = 'blue'; }else{ this.color = 'black'; } this.r = 0; }}Copy the code
  • The draw function is used to draw a filled circle
  • Animate is used for animation, which continually changes the radius of a circle

There is also a function called changeColor that changes the color of the drawing, which changes between black and blue. In this example, to simulate a time-consuming operation, Fibonacci is called in changeColor. Fibonacci is used to calculate the Fibonacci sequence. When the incoming value is 41, the calculation is heavy and the main thread will block for a while. Here’s Fibonacci’s definition:

function fibonacci(num) {
  return (num <= 1) ? 1 : fibonacci(num - 1) + fibonacci(num - 2);
}Copy the code

Then, we define two Canvases, one for the normal Canvas application and one for rendering the off-screen drawn content:

<canvas id="canvas-window" width="300" height="400" style="background: white; left: 10px; top: 20px; position: relative;" ></canvas> <canvas id="canvas-worker" width="300" height="400" style="background: white; left: 10px; top: 20px; position: relative;" ></canvas>Copy the code

For the first canvas, we directly draw the circle with changing radius on it continuously:

 var canvasInWindow = document.getElementById('canvas-window');
    var ctx = canvasInWindow.getContext('2d');
    var circle = new Circle(ctx);
    circle.animate();
    canvasInWindow.addEventListener('click', function () {
      circle.changeColor();
    });Copy the code

Add a ‘click’ event to the canvas, and when clicked, call the changeColor function of the Circle class.

For the second canvas, we use webworker, the first to use transferControlToOffscreen function creates OffscreenCanvas object offscreen, then create a worker object, And send offscreen to the worker thread:

var canvasInWorker = document.getElementById('canvas-worker'); // var ctxInWorkder = canvasInWorker.getContext('2d'); var offscreen = canvasInWorker.transferControlToOffscreen(); var worker = new Worker('./worker.js'); worker.postMessage({ msg: 'start', canvas: offscreen }, [offscreen]); canvasInWorker.addEventListener('click', function () { worker.postMessage({msg:'changeColor'}); }); // canvasInWorker.getContext('2d'); / / complainsCopy the code

The canvas also adds a ‘click’ event, which sends a changeColor command to the worker thread when clicked.

Then, let’s look at the contents of the worker.js thread:

var offscreen = null,ctx,circle; onmessage = function (e) { var data = e.data; if(data.msg == 'start'){ offscreen = data.canvas; ctx = offscreen.getContext('2d'); circle = new Circle(ctx); circle.animate(); } else if (data.msg == 'changeColor' && circle) { circle.changeColor(); } } function fibonacci(num) { return (num <= 1) ? 1 : fibonacci(num - 1) + fibonacci(num - 2); } class Circle { constructor(ctx) { this.ctx = ctx; this.r = 0; this.rMax = 50; this.color = 'black'; this.bindAnimate = this.animate.bind(this); } draw() { this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.ctx.canvas.width / 2, this.ctx.canvas.height / 2, this.r, 0, Math.PI * 2); this.ctx.fill(); } animate() { this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); this.r = this.r + 1; if (this.r > this.rMax) { this.r = 0; } this.draw(); requestAnimationFrame(this.bindAnimate); } changeColor() { fibonacci(41); if (this.color == 'black') { this.color = 'blue'; } else { this.color = 'black'; } this.r = 0; }}Copy the code

In worker.js, the same Circle class and Fibonacci function are defined. In the onMessage function, the message from the page side is received. When the start command is received, the OffscreenCanvas object offscreen is drawn with a circular animation. When changeColor is received, the changeColor function of the Circle class is called.

The reader can see that after the graph is drawn in the worker thread, it is not transmitted to the page end, and its content will be automatically displayed to the broken canvas of the page. The final display looks like the following:

You can see that both canvas are animating. The difference is that when clicked, the heavy changeColor function will be called. The canvas on the page will block the main thread, while the canvas off the screen will not block the main thread, as shown below:

In addition to not blocking the main thread, off-screen OffscreenCanvas objects are not blocked by the main thread’s heavy tasks, such as adding a button to the page and calling a time-consuming task:

<button id='heavyTask' style="position: absolute; display:inline; left: 100px;" onclick="heavyTask()">heavyTask</button>Copy the code

The Fibonacci function is used to simulate time-consuming tasks:

function heavyTask() {
   fibonacci(41);
}Copy the code

When the button is clicked, the page canvas stops animating, but the off-screen canvas does not stop animating:

If readers are not familiar with canvas related knowledge, it is recommended to learn relevant knowledge, and interested readers are also recommended to subscribe to the column (the content of this article is extracted from the column) : Canvas Advanced and Advanced xiaozhuanlan.com/canvas, and relevant knowledge will be introduced in the column.

Welcome to the public account “ITman Biao Shu”. Biao Shu, with more than 10 years of development experience, is now the company’s system architect, technical director, technical trainer, career planner. In-depth research in computer graphics, WebGL, front-end visualization. Strong interest in programmer thinking ability training and training, programmer career planning.