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。

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

更多相关的内容,可以参考:

https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap

创建OffscreenCanvas

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

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

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

  1. var canvas = document.getElementById('canvas');
  2. //var ctx = canvas.getContext('2d');
  3. var offscreen = canvas.transferControlToOffscreen();
  4. // 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对象。

下面是示例代码,主线程中:

  1. var worker2 = null,canvasBitmap, ctxBitmap;
  2. function init() {
  3. canvasBitmap = document.getElementById('canvas-bitmap');
  4. ctxBitmap = canvasBitmap.getContext('2d');
  5. worker2 = new Worker('./bitmap_worker.js');
  6. worker2.postMessage({msg:'init'});
  7. worker2.onmessage = function (e) {
  8. ctxBitmap.drawImage(e.data.imageBitmap,0,0);
  9. }
  10. }
  11. function redraw() {
  12. ctxBitmap.clearRect(0, 0, canvasBitmap.width, canvasBitmap.height)
  13. worker2.postMessage({msg:'draw'});
  14. }

worker线程中:


  1. var offscreen,ctx;
  2. onmessage = function (e) {
  3. if(e.data.msg == 'init'){
  4. init();
  5. draw();
  6. }else if(e.data.msg == 'draw'){
  7. draw();
  8. }
  9. }
  10. function init() {
  11. offscreen = new OffscreenCanvas(512, 512);
  12. ctx = offscreen.getContext("2d");
  13. }
  14. function draw() {
  15. ctx.clearRect(0,0,offscreen.width,offscreen.height);
  16. for(var i = 0;i < 10000;i ++){
  17. for(var j = 0;j < 1000;j ++){
  18. ctx.fillRect(i*3,j*3,2,2);
  19. }
  20. }
  21. var imageBitmap = offscreen.transferToImageBitmap();
  22. postMessage({imageBitmap:imageBitmap},[imageBitmap]);
  23. }
  • 在主线程中,获取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 的所有权被转移到画布上。

在前面的例子中,可以做如下修改:

  1. function init() {
  2. ...
  3. ctxBitmap = canvasBitmap.getContext('bitmaprenderer');
  4. ...
  5. worker2.onmessage = function (e) {
  6. ctxBitmap.transferFromImageBitmap(e.data.imageBitmap);
  7. }
  8. }

首先,把获取渲染上下文的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类,这个类的作用主要是用于绘制一个圆,并且可以启动动画,不断的改变圆的半径大小:

  1. class Circle {
  2. constructor(ctx){
  3. this.ctx = ctx;
  4. this.r = 0;
  5. this.rMax = 50;
  6. this.color = 'black';
  7. this.bindAnimate = this.animate.bind(this);
  8. }
  9. draw(){
  10. this.ctx.fillStyle = this.color;
  11. this.ctx.beginPath();
  12. this.ctx.arc(this.ctx.canvas.width/2,this.ctx.canvas.height/2,this.r,0,Math.PI*2);
  13. this.ctx.fill();
  14. }
  15. animate(){
  16. this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
  17. this.r = this.r + 1;
  18. if(this.r > this.rMax){
  19. this.r = 0;
  20. }
  21. this.draw();
  22. requestAnimationFrame(this.bindAnimate);
  23. }
  24. changeColor(){
  25. fibonacci(41);
  26. if(this.color == 'black'){
  27. this.color = 'blue';
  28. }else{
  29. this.color = 'black';
  30. }
  31. this.r = 0;
  32. }
  33. }
  • draw 函数用于绘制一个填充的圆形
  • animate 用于动画,其不断改变圆形的半径

另外还有一个函数changeColor,表示改变绘制的颜色,其会在黑色和蓝色之间不断变化,本示例中,为了模拟比较耗时的操作,在changeColor函数中,调用了下fibonacci函数,fibonacci函数用于计算斐波那契数列,当传入值是41的时候,计算量较大,主线程会把阻塞一段时间。下面是fibonacci的定义:

  1. function fibonacci(num) {
  2. return (num <= 1) ? 1 : fibonacci(num - 1) + fibonacci(num - 2);
  3. }

然后,我们定义两个canvas,一个用于普通的canvas应用,一个用于呈现离屏绘制的内容:

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

对于第一个canvas,我们直接在其上不断绘制半径变化的圆形:

  1. var canvasInWindow = document.getElementById('canvas-window');
  2. var ctx = canvasInWindow.getContext('2d');
  3. var circle = new Circle(ctx);
  4. circle.animate();
  5. canvasInWindow.addEventListener('click', function () {
  6. circle.changeColor();
  7. });

并在该canvas上添加‘click’事件,当点击时,调用Circle类的changeColor函数。

对于第二个canvas,我们使用webworker,首先使用transferControlToOffscreen函数创建OffscreenCanvas对象offscreen,然后创建worker对象,并把offscreen发送给worker线程:

  1. var canvasInWorker = document.getElementById('canvas-worker');
  2. // var ctxInWorkder = canvasInWorker.getContext('2d');
  3. var offscreen = canvasInWorker.transferControlToOffscreen();
  4. var worker = new Worker('./worker.js');
  5. worker.postMessage({ msg: 'start', canvas: offscreen }, [offscreen]);
  6. canvasInWorker.addEventListener('click', function () {
  7. worker.postMessage({msg:'changeColor'});
  8. });
  9. // canvasInWorker.getContext('2d'); // 会报错

该canvas上同样添加‘click’事件,当点击时,发送changeColor的命令给worker线程。

然后,我们看下worker.js线程的内容:

  1. var offscreen = null,ctx,circle;
  2. onmessage = function (e) {
  3. var data = e.data;
  4. if(data.msg == 'start'){
  5. offscreen = data.canvas;
  6. ctx = offscreen.getContext('2d');
  7. circle = new Circle(ctx);
  8. circle.animate();
  9. } else if (data.msg == 'changeColor' && circle) {
  10. circle.changeColor();
  11. }
  12. }
  13. function fibonacci(num) {
  14. return (num <= 1) ? 1 : fibonacci(num - 1) + fibonacci(num - 2);
  15. }
  16. class Circle {
  17. constructor(ctx) {
  18. this.ctx = ctx;
  19. this.r = 0;
  20. this.rMax = 50;
  21. this.color = 'black';
  22. this.bindAnimate = this.animate.bind(this);
  23. }
  24. draw() {
  25. this.ctx.fillStyle = this.color;
  26. this.ctx.beginPath();
  27. this.ctx.arc(this.ctx.canvas.width / 2, this.ctx.canvas.height / 2, this.r, 0, Math.PI * 2);
  28. this.ctx.fill();
  29. }
  30. animate() {
  31. this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
  32. this.r = this.r + 1;
  33. if (this.r > this.rMax) {
  34. this.r = 0;
  35. }
  36. this.draw();
  37. requestAnimationFrame(this.bindAnimate);
  38. }
  39. changeColor() {
  40. fibonacci(41);
  41. if (this.color == 'black') {
  42. this.color = 'blue';
  43. } else {
  44. this.color = 'black';
  45. }
  46. this.r = 0;
  47. }
  48. }

在worker.js中,定义了一个同样的Circle类和fibonacci函数。 在onmessage函数中,接受页面端传递来的信息,当接受到start命令时,在接收到的OffscreenCanvas对象offscreen上绘制圆形的动画。当接受到changeColor命令时,调用Circle类的changeColor函数。

读者可以看出,在worker线程中绘制了图形之后,并没有传递给页面端,其内容会自动显示给页面的断的canvas。 最终显示的效果如下图:

可以看到两个canvas都在绘制动画。区别在于,单击的时候,都会调用比较重的changeColor函数,页面端的canvas会阻塞主线程,而离屏的canvas不会阻塞主线程,演示如下:

除了不阻塞主线程之外,离屏的OffscreenCanvas对象也不会被主线程的重任务阻塞,比如我们在页面添加一个button,调用一个耗时的任务:

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

其实耗时的任务还是用了fibonacci函数来模拟:

  1. function heavyTask() {
  2. fibonacci(41);
  3. }

当点击按钮的时候,页面的canvas会停止动画,而离屏的canvas不会停止动画:

如果读者不清楚canvas相关知识点,建议学习相关知识,也推荐有兴趣读者,订阅专栏(本文内容就摘取自专栏):

Canvas高级进阶 https://xiaozhuanlan.com/canvas,相关知识会在专栏中介绍。

欢迎关注公众号“ITman彪叔”。彪叔,拥有10多年开发经验,现任公司系统架构师、技术总监、技术培训师、职业规划师。熟悉Java、JavaScript、Python语言,熟悉数据库。熟悉java、nodejs应用系统架构,大数据高并发、高可用、分布式架构。在计算机图形学、WebGL、前端可视化方面有深入研究。对程序员思维能力训练和培训、程序员职业规划有浓厚兴趣。

OffscreenCanvas-离屏canvas使用说明的更多相关文章

  1. 离屏Canvas — 使用Web Worker提高你的Canvas运行速度

    离屏Canvas — 使用Web Worker提高你的Canvas运行速度 原文链接: developers.google.com 现在因为有了离屏Canvas,你可以不用在你的主线程中绘制图像了! ...

  2. PIXI如何绘制离屏canvas到舞台上

    有个方法是toDataURL(),原生的,先转换成图片再绘制. 但是pixi提供了一个BaseTexture,其构造函数的参数可以是一个canvas 因此可以直接使用如下代码绘制canvas //微信 ...

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

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

  4. 利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

    利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果 前言 近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上.既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项 ...

  5. canvas——离屏

    离屏操作: 1.创建一个新的离屏canvas; 2.把一些复杂额绘画操作(背景),画在离屏canvas上: 3.将离屏canvas通过drawImage(离屏canvas对象,x1,y1,offcan ...

  6. canvas离屏、旋转效果实践——旋转的雪花

    效果展示理论基础--"常见的canvas优化--模糊问题.旋转效果" 用离屏canvas画基础部分 1.封装画线函数 function drawLine(ctx,x1,y1,x2, ...

  7. HTML5 Canvas核心技术图形动画与游戏开发(读书笔记)----第一章,基础知识

    一,canvas元素 1 为了防止浏览器不支持canvas元素,我们设置“后备内容”(fallback content),下面紫色的字即为后备内容 <canvas id="canvas ...

  8. HTML5 Canvas核心技术—图形、动画与游戏开发.pdf5

    文本的定位 水平与垂直定位:当使用strokeText()和fillText()绘制文本时,指定了所绘文本的X与Y坐标,还有textAlign与textBaseline两个属性 textAlign:s ...

  9. canvas性能优化总结

    canvas的主要功能就是用来绘制内容,有时候为了给用户流畅的视觉感受,需要绘制的频率要求很高,这样对绘制的性能就有要求,那么怎么才能写出高性能的绘制代码呢. 尽可能少调用api 例如我们绘制一段线条 ...

  10. [JS,Canvas]日历时钟

    [JS,Canvas]日历时钟 Html: <!doctype html> <html> <head> <meta charset="UTF-8&q ...

随机推荐

  1. Javascript/DOM:如何删除 DOM 对象的所有事件侦听器

    Javascript/DOM:如何删除 DOM 对象的所有事件侦听器 一.重写 重写 EventTarget 添加监听事件方法 addEventListener if (EventTarget.pro ...

  2. Dapper升级SqlSugar问题汇总

    最近群里有个小伙伴把Dapper迁移SqlSugar几个不能解决的问题进行一个汇总,我正好写一篇文章来讲解一下 一.sql where in传参问题: SELECT * FROM users wher ...

  3. linux内核参数调优和Linux实例常用内核网络参数介绍与常见问题处理

    问题1 并发场景下,常常会出现一个进程最大文件句柄数不足的情况,会报如下错误: 24: Too many open files 解决办法 ulimit -a S:表示软限制,超出设定的值会告警. H ...

  4. 通过axios实现数据请求

    vue.js默认没有提供ajax功能的. 所以使用vue的时候,一般都会使用axios的插件来实现ajax与后端服务器的数据交互. 注意,axios本质上就是javascript的ajax封装,所以会 ...

  5. Python并行运算——threading库详解(持续更新)

    0. 写在前面:进程和线程 博文参考: Python的并行(持续更新)_python 并行-CSDN博客 <Python并行编程 中文版> 一些相关概念请见上一篇博文. 1. 在Pytho ...

  6. rabbitmq添加延时通道时报错

    rabbitmq添加延时通道时报错 'x-delayed-type' must be an existing exchange type 解决方案: 我实际用的是x-delayed-type:topi ...

  7. 7款优秀的AI搜索引擎工具推荐

    AI搜索引擎不仅能够理解复杂的查询语句,还能够通过学习用户的搜索习惯和偏好,提供更加个性化的搜索结果.本篇文章将介绍7款在这一领域表现出色的AI搜索引擎工具,它们各有特色,但都致力于为用户提供更加智能 ...

  8. openssl升级nginx升级支持openssl http2

    mkdir -p /usr/local/openssl #wget https://www.openssl.org/source/openssl-1.1.1d.tar.gz tar -xf opens ...

  9. jquery的循环 tab切换

        <ul>         <li>1</li>         <li>2</li>         <li>3< ...

  10. 极限科技旗下软件产品 INFINI Easysearch 通过统信 UOS 认证

    近日,极限数据 (北京) 科技有限公司(以下简称:极限科技)旗下的软件 INFINI Easysearch 搜索引擎软件 V1.0 通过统信 UOS 服务器操作系统 V20 认证. 此次兼容适配基于统 ...