OffscreenCanvas 是一个实验中的新特性,主要用于提升 Canvas 2D/3D 绘图的渲染性能和使用体验。OffscreenCanvas 的 API 很简单,但是要真正掌握好如何使用。

OffscreenCanvas和canvas都是渲染图形的对象。 不同的是canvas只能在window环境下使用,而OffscreenCanvas即可以在window环境下使用,也可以在web worker中使用,这让不影响浏览器主线程的离屏渲染成为可能。

与之关联的还有ImageBitmap对象和ImageBitmapRenderingContext。

ImageBitmap

ImageBitmap对象表示能够被绘制到 canvas上的位图图像,具有低延迟的特性。 ImageBitmap提供了一种异步且高资源利用率的方式来为WebGL的渲染准备基础结构。ImageBitmap可以通过createImageBitmap函数来创建,它可以从多种图像源生成。 还可以通过OffscreenCanvas.transferToImageBitmap函数生成。

属性

ImageBitmap.height 只读无符号长整型数值,表示ImageData的CSS像素单位的高度。ImageBitmap.width 只读无符号长整型数值, 表示ImageData的CSS像素单位的宽度。

函数

ImageBitmap.close()释放ImageBitmap所相关联的所有图形资源。

createImageBitmap

createImageBitmap 用于创建ImageBitmap对象。该函数存在 windows 和 workers 中。它接受各种不同的图像来源, 并返回一个Promise, resolve为ImageBitmap。

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

更多相关的内容,可以参考:https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap

创建OffscreenCanvas

有两种方式可以创建OffscreenCanvas,一种是通过OffscreenCanvas的构造函数直接创建。比如下面的示例代码:

var offscreen = new OffscreenCanvas(width, height); // width 、height表示宽高。

另外一种方式,是使用canvas的transferControlToOffscreen函数获取一个OffscreenCanvas对象,绘制该OffscreenCanvas对象,同时会绘制canvas对象。比如如下代码:

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会报错。

OffscreenCanvas.transferToImageBitmap函数

通过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]);
}
  • 在主线程中,获取canvas对象,然后生成worker对象,并把绘制命令传递给worker。
  • 在worker线程中,创建一个OffscreenCanvas,然后执行绘制命令,绘制完成后,通过transferToImageBitmap函数创建imageBitmap对象,并通过postMessage把imageBitmap对象传递给主线中。
  • 主线程接收到imageBitmap对象之后,把imageBitmap绘制到canvas对象上。

最终的绘制效果如下:

把绘制放到web worker中的好处是,绘制的过程不阻塞主线程的运行。 读者可以自行运行代码查看,在绘制过程过程中,界面可以交互, 比如可以选择下拉框。

ImageBitmapRenderingContext

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函数

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;}
}
  • draw 函数用于绘制一个填充的圆形
  • animate 用于动画,其不断改变圆形的半径

另外还有一个函数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高级进阶 https://xiaozhuanlan.com/canvas,相关知识会在专栏中介绍。

欢迎关注公众号“ITman彪叔”。彪叔,拥有10多年开发经验,现任公司系统架构师、技术总监、技术培训师、职业规划师。在计算机图形学、WebGL、前端可视化方面有深入研究。对程序员思维能力训练和培训、程序员职业规划有浓厚兴趣。

OffscreenCanvas-离屏canvas使用说明相关推荐

  1. 小程序离屏canvas(createOffscreenCanvas)生成推广海报

    小程序离屏canvas(createOffscreenCanvas)生成推广海报 离屏canvas调用wx.canvasToTempFilePath报错Failed to execute 'drawI ...

  2. 使用说明_无线投屏器使用说明教程

    原标题:无线投屏器使用说明教程 无线投屏器使用说明教程 本文主要教大家怎么使用无线投屏器.分别包含Windows电脑.Macos.安卓手机/平板.苹果手机/平板等设备通过无线投屏器进行无线分享. 无线 ...

  3. threejs学习(十一)非全屏canvas下,射线拾取坐标错位问题

    问题 在非全屏状态下,使用射线进行模型拾取,如果按照官方例子来做,会出现屏幕坐标转换三维坐标有误差,导致拾取模型错误: 非全屏canvas 解决方案 需要重新计算鼠标点击坐标位置 spriteClic ...

  4. 用离屏canvas制作仿Prezi页面转场动画特效

    用离屏canvas制作网站页面特效.仿Prezi转场特效 首屏效果 页面切换转场效果 · 首屏圆形循环扩散效果 首屏效果的难点在于圆形循环扩散动画和贝塞尔曲线的绘制.圆形逐渐扩大的动画并不难,关键在于 ...

  5. 大屏LCD12864_stc8a8k使用说明

    1.实验室有几个大屏LCD12864,不知道是不是带中文字库,这里不用中文,本身就屏幕不够大,所以之类显示英文或者字符,能显示更加多的内容. 2.为了节约管脚,这里使用了串行的方式进行通信和数据传输. ...

  6. 地牢围攻2 宽屏补丁 使用说明

    补丁下载地址:http://www.widescreengamingforum.com/wiki/index.php/Dungeon_Siege_2_Camera_Fix 设置方法: 1. 进入目录 ...

  7. DTVideo UE录屏插件使用说明

    本插件可以在蓝图中简单调用即可时时录制游戏画面并保存为MP4文件. 插件只有2个蓝图函数开放使用,这两个函数已经可以满足使用需求. This plug-in can be simply called ...

  8. 对webwork的理解与使用

    什么是web work js采用的是单线程模型,所有任务只能在一个线程上完成,一次只能做一件事.前面的任务没做完,后面的任务只能等着.随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大 ...

  9. canvas离屏技术与放大镜实现

    教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步>>> (原文)canvas 离屏技术与放大镜实现. 更多讨论或者错误提交,也请移步. 利用 canvas 除了可以 ...

最新文章

  1. POJ 3694 (tarjan缩点+LCA+并查集)
  2. Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据
  3. 图测试题部分总结.ing
  4. 淘宝如何保障业务稳定性——诺亚(Noah)自适应流控
  5. Python 炫技操作:条件语句的七种写法,你都知道吗?
  6. Vue2 模板template的四种写法总结
  7. 2016计算机视觉应用专题研究报告
  8. java类变量 访问_在Java类中对变量或方法的访问有哪些访问控制的关键字?他们各有什么作用,举例说明。...
  9. imagesc和pcolor区别
  10. Python语音识别终极指南(收藏)
  11. 我的服务器开发之路-服务器SSH默认端口22和ftp默认端口21修改
  12. 2019腾讯校招客户端方向面经(已拿offer)
  13. Python全年天数查询源代码
  14. libcurl返回DNS无法解析的问题
  15. 云桌面-ThinVirt3-EXP操作手册(一)云桌面系统安装
  16. html5怎么写副标题,论文指导:论文题目副标题怎么写
  17. 七年级上学期计算机测试题,七年级英语上学期期末测试题及答案
  18. 线性代数 --- 线性代数基本定理上(四个基本子空间的维数,行秩=列秩)
  19. 我的面试经历(天津零氪科技)
  20. 电路与单片机以及嵌入式仿真

热门文章

  1. 熵,哈夫曼编码,二进制
  2. 网桥工作原理及题目分析
  3. JavaScript_ES5和ES6
  4. git branch 相关命令
  5. 气象站有哪几种类型?常见有以下四种型号!
  6. 【总结】GitHub的使用
  7. Git更换关联的远端分支
  8. 在MATLAB中创建函数
  9. java class.getmethod_java 反射使用 Class.getMethod 应注意
  10. Qt使用lambda表达式实现无参信号与有参槽的连接