OffscreenCanvas 是一個實驗中的新特性,主要用於提高 Canvas 2D/3D 繪圖的渲染性能和使用體驗。OffscreenCanvas 的 API 很簡單,可是要真正掌握好如何使用。前端
OffscreenCanvas和canvas都是渲染圖形的對象。 不一樣的是canvas只能在window環境下使用,而OffscreenCanvas便可以在window環境下使用,也能夠在web worker中使用,這讓不影響瀏覽器主線程的離屏渲染成爲可能。程序員
與之關聯的還有ImageBitmap對象和ImageBitmapRenderingContext。web
ImageBitmap對象表示可以被繪製到 canvas上的位圖圖像,具備低延遲的特性。 ImageBitmap提供了一種異步且高資源利用率的方式來爲WebGL的渲染準備基礎結構。ImageBitmap能夠經過createImageBitmap函數來建立,它能夠從多種圖像源生成。 還能夠經過OffscreenCanvas.transferToImageBitmap函數生成。canvas
ImageBitmap.height 只讀無符號長整型數值,表示ImageData的CSS像素單位的高度。ImageBitmap.width 只讀無符號長整型數值, 表示ImageData的CSS像素單位的寬度。windows
ImageBitmap.close()釋放ImageBitmap所相關聯的全部圖形資源。瀏覽器
createImageBitmap 用於建立ImageBitmap對象。該函數存在 windows 和 workers 中。它接受各類不一樣的圖像來源, 並返回一個Promise, resolve爲ImageBitmap。架構
createImageBitmap(image[, options]).then(function(response) { ... });
createImageBitmap(image, sx, sy, sw, sh[, options]).then(function(response) { ... });複製代碼
更多相關的內容,能夠參考:developer.mozilla.org/zh-CN/docs/…異步
有兩種方式能夠建立OffscreenCanvas,一種是經過OffscreenCanvas的構造函數直接建立。好比下面的示例代碼:函數
var offscreen = new OffscreenCanvas(width, height); // width 、height表示寬高。複製代碼
另一種方式,是使用canvas的transferControlToOffscreen函數獲取一個OffscreenCanvas對象,繪製該OffscreenCanvas對象,同時會繪製canvas對象。好比以下代碼:post
var canvas = document.getElementById('canvas');
//var ctx = canvas.getContext('2d');
var offscreen = canvas.transferControlToOffscreen();
// canvas.getContext('2d'); // 會報錯複製代碼
上面的代碼代碼首先獲取網頁元素canvas對象,而後調用canvas對象的transferControlToOffscreen函數建立一個OffscreenCanvas對象offscreen,並把控制權交給offscreen。
須要注意的是,canvas對象調用了函數transferControlToOffscreen移交控制權以後,不能再獲取繪製上下文,調用canvas.getContext('2d')會報錯; 一樣的原理,若是canvas已經獲取的繪製上下文,調用transferControlToOffscreen會報錯。
經過transferToImageBitmap函數能夠從OffscreenCanvas對象的繪製內容建立一個ImageBitmap對象。該對象能夠用於到其餘canvas的繪製。
好比一個常見的使用是,把一個比較耗費時間的繪製放到web worker下的OffscreenCanvas對象上進行,繪製完成後,建立一個ImageBitmap對象,並把該對象傳遞給頁面端,在頁面端繪製ImageBitmap對象。
下面是示例代碼,主線程中:
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'});
worker2.onmessage = function (e) {
ctxBitmap.drawImage(e.data.imageBitmap,0,0);
}
}
function redraw() {
ctxBitmap.clearRect(0, 0, canvasBitmap.width, canvasBitmap.height)
worker2.postMessage({msg:'draw'});
}複製代碼
worker線程中:
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");
}
function 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]);
}複製代碼
最終的繪製效果以下:
把繪製放到web worker中的好處是,繪製的過程不阻塞主線程的運行。 讀者能夠自行運行代碼查看,在繪製過程過程當中,界面能夠交互, 好比能夠選擇下拉框。
ImageBitmapRenderingContext接口是 canvas 的渲染上下文,它只提供使用給定 ImageBitmap 替換 canvas 的功能。它的上下文 ID (HTMLCanvasElement.getContext() 或 OffscreenCanvas.getContext() 的第一個參數) 是 "bitmaprenderer"。這個接口可用於 window context 和 worker context.
ImageBitmapRenderingContext.transferFromImageBitmap函數用於在與此「渲染上下文」對應的 canvas 中顯示給定的 ImageBitmap對象。 ImageBitmap 的全部權被轉移到畫布上。
在前面的例子中,能夠作以下修改:
function init() {
...
ctxBitmap = canvasBitmap.getContext('bitmaprenderer');
...
worker2.onmessage = function (e) {
ctxBitmap.transferFromImageBitmap(e.data.imageBitmap);
}
}複製代碼
首先,把獲取渲染上下文的id改爲「bitmaprenderer」,返回額ctxBitmap是一個ImageBitmapRenderingContext對象。而後,在渲染ImageBitmap對象的時候,把drawImage函數改成transferFromImageBitmap函數。
最終渲染效果和上圖顯示同樣。
transferControlToOffscreen函數能夠經過頁面的canvas對象來建立一個OffscreenCanvas。 既然能夠經過構造函數建立OffscreenCanvas對象,爲啥還須要這樣操做。 緣由是這樣的:咱們看前面一個示例,咱們在worker線程中建立OffscreenCanvas對象並繪製而後獲取ImageBitmap對象,經過web worker通訊把ImageBitmap傳遞給頁面。
而若是經過canvas.transferControlToOffscreen生成的OffscreenCanvas對象,不須要再經過web worker通訊來傳遞繪製的效果,生成了OffscreenCanvas對象以後,OffscreenCanvas對象的繪製會自動在canvas元素上面顯示出來。這相對於web worker通訊有着不言而喻的優點。
經過transferControlToOffscreen函數建立的OffscreenCanvas對象有兩大功能:
下面咱們將會經過示例來講明以上結論。
首先,咱們寫一個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;
}
}複製代碼
另外還有一個函數changeColor,表示改變繪製的顏色,其會在黑色和藍色之間不斷變化,本示例中,爲了模擬比較耗時的操做,在changeColor函數中,調用了下fibonacci函數,fibonacci函數用於計算斐波那契數列,當傳入值是41的時候,計算量較大,主線程會把阻塞一段時間。下面是fibonacci的定義:
function fibonacci(num) {
return (num <= 1) ? 1 : fibonacci(num - 1) + fibonacci(num - 2);
}複製代碼
而後,咱們定義兩個canvas,一個用於普通的canvas應用,一個用於呈現離屏繪製的內容:
<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>複製代碼
對於第一個canvas,咱們直接在其上不斷繪製半徑變化的圓形:
var canvasInWindow = document.getElementById('canvas-window');
var ctx = canvasInWindow.getContext('2d');
var circle = new Circle(ctx);
circle.animate();
canvasInWindow.addEventListener('click', function () {
circle.changeColor();
});複製代碼
並在該canvas上添加‘click’事件,當點擊時,調用Circle類的changeColor函數。
對於第二個canvas,咱們使用webworker,首先使用transferControlToOffscreen函數建立OffscreenCanvas對象offscreen,而後建立worker對象,並把offscreen發送給worker線程:
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'); // 會報錯複製代碼
該canvas上一樣添加‘click’事件,當點擊時,發送changeColor的命令給worker線程。
而後,咱們看下worker.js線程的內容:
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;
}
}複製代碼
在worker.js中,定義了一個一樣的Circle類和fibonacci函數。 在onmessage函數中,接受頁面端傳遞來的信息,當接受到start命令時,在接收到的OffscreenCanvas對象offscreen上繪製圓形的動畫。當接受到changeColor命令時,調用Circle類的changeColor函數。
讀者能夠看出,在worker線程中繪製了圖形以後,並無傳遞給頁面端,其內容會自動顯示給頁面的斷的canvas。 最終顯示的效果以下圖:
能夠看到兩個canvas都在繪製動畫。區別在於,單擊的時候,都會調用比較重的changeColor函數,頁面端的canvas會阻塞主線程,而離屏的canvas不會阻塞主線程,演示以下:
除了不阻塞主線程以外,離屏的OffscreenCanvas對象也不會被主線程的重任務阻塞,好比咱們在頁面添加一個button,調用一個耗時的任務:
<button id='heavyTask' style="position: absolute;display:inline;left: 100px;" onclick="heavyTask()">heavyTask</button>複製代碼
其實耗時的任務仍是用了fibonacci函數來模擬:
function heavyTask() {
fibonacci(41);
}複製代碼
當點擊按鈕的時候,頁面的canvas會中止動畫,而離屏的canvas不會中止動畫:
若是讀者不清楚canvas相關知識點,建議學習相關知識,也推薦有興趣讀者,訂閱專欄(本文內容就摘取自專欄):
Canvas高級進階 xiaozhuanlan.com/canvas,相關知識會在專欄中介紹。
歡迎關注公衆號「ITman彪叔」。彪叔,擁有10多年開發經驗,現任公司系統架構師、技術總監、技術培訓師、職業規劃師。在計算機圖形學、WebGL、前端可視化方面有深刻研究。對程序員思惟能力訓練和培訓、程序員職業規劃有濃厚興趣。