当前位置: 首页 > Web前端 > HTML5

OffscreenCanvas-OffscreenCanvas使用说明

时间:2023-04-06 00:07:41 HTML5

OffscreenCanvas是实验中的一个新特性,主要用于提升Canvas2D/3D绘图的渲染性能和用户体验。OffscreenCanvas的API很简单,但是你必须真正掌握如何使用它。OffscreenCanvas和canvas都是渲染图形的对象。不同的是canvas只能在window环境下使用,而OffscreenCanvas既可以在window环境下也可以在webworker上使用,这样就可以在不影响浏览器主线程的情况下进行离屏渲染。与之关联的是ImageBitmap对象和ImageBitmapRenderingContext。ImageBitmapImageBitmap对象表示可以在画布上绘制并具有低延迟特性的位图图像。ImageBitmap提供了一种异步且资源高效的方式来为WebGL渲染准备基础结构。ImageBitmap可以通过createImageBitmap函数创建,它可以从多种图像源生成。它也可以由OffscreenCanvas.transferToImageBitmap函数生成。属性ImageBitmap.height是一个只读的无符号长整数值,表示ImageData的高度,以CSS像素为单位。ImageBitmap.width只读无符号长整型值,表示ImageData的宽度,以CSS像素为单位。函数ImageBitmap.close()释放与ImageBitmap关联的所有图形资源。createImageBitmapcreateImageBitmap用于创建一个ImageBitmap对象。这个函数存在于windows和worker中。它接受各种不同的图像源并返回解析为ImageBitmap的Promise。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创建OffscreenCanvasOffscreenCanvas有两种创建方式,一种是通过OffscreenCanvas的构造函数创建直接地。例如下面的示例代码:varoffscreen=newOffscreenCanvas(width,height);//width和height表示宽度和高度。另一种方式是使用canvas的transferControlToOffscreen函数获取一个OffscreenCanvas对象,绘制OffscreenCanvas对象,同时绘制canvas对象。比如下面的代码:varcanvas=document.getElementById('canvas');//varctx=canvas.getContext('2d');varoffscreen=canvas.transferControlToOffscreen();//canvas.getContext('2d');//会报错上面代码代码先获取网页元素的canvas对象,然后调用canvas对象的transferControlToOffscreen函数创建一个OffscreenCanvas对象offscreen,将控件转移到offscreen。需要注意的是,canvas对象调用函数transferControlToOffscreen转移控制后,就无法再获取绘图上下文,调用canvas.getContext('2d')会报错;同样的原理,如果canvas已经获取了绘图上下文,调用transferControlToOffscreen会报错。OffscreenCanvas.transferToImageBitmap函数可以通过transferToImageBitmap函数从OffscreenCanvas对象的绘图内容创建一个ImageBitmap对象。该对象可用于绘制到其他画布。例如,一个常见的用途是将一个耗时的绘图放在webworker下的OffscreenCanvas对象上。绘制完成后,创建一个ImageBitmap对象,并将该对象传给页面端,在页面端绘制ImageBitmap对象。下面是示范代码,主线程序中:varworker2=null,canvasBitmap,ctxBitmap;functioninit(){canvasBitmap=document.getElementById('canvas-bitmap');ctxBitmap=canvasBitmap.getContext('2d');worker2=newWorker('./bitmap_worker.js');worker2.postMessage({msg:'init'});worker2.onmessage=function(e){ctxBitmap.drawImage(e.data.imageBitmap,0,0);}}functionredraw(){ctxBitmap.clearRect(0,0,canvasBitmap.width,canvasBitmap.height)worker2.postMessage({msg:'draw'});}worker线程中:varoffscreen,ctx;onmessage=function(e){if(e.data.msg=='init'){初始化();画();}elseif(e.data.msg=='draw'){draw();}}functioninit(){offscreen=newOffscreenCanvas(512,512);ctx=offscreen.getContext("2d");}functiondraw(){ctx.clearRect(0,0,offscreen.width,offscreen.height);for(vari=0;i<10000;i++){for(varj=0;j<1000;j++){ctx.fillRect(i*3,j*3,2,2);}}varimageBitmap=offscreen.transferTo图片位图();postMessage({imageBitmap:imageBitmap},[imageBitmap]);}在主线程中,获取canvas对象,然后生成worker对象,将绘图命令传递给worker在worker线程中,创建一个OffscreenCanvas,然后执行绘图命令,绘图完成后,通过transferToImageBitmap函数创建一个imageBitmap对象,通过postMessage将imageBitmap对象传给主线。主线程接收到imageBitmap对象后,将imageBitmap绘制到canvas对象上。最终的绘图效果如下:将绘图放在webworker中的好处是绘图过程不会阻塞主线程的运行。读者可以自行运行代码查看。在绘制过程中,界面可以是交互的,比如可以选择下拉框。ImageBitmapRenderingContextImageBitmapRenderingContext接口是画布的渲染上下文,它只提供给定的ImageBitmap替换画布的功能。它的上下文ID(HTMLCanvasElement.getContext()或OffscreenCanvas.getContext()的第一个参数)是“bitmaprenderer”。此接口在窗口上下文和工作上下文中均可用。方法ImageBitmapRenderingContext.transferFromImageBitmap函数用于在对应于此“渲染上下文”的画布中显示给定的ImageBitmap对象。ImageBitmap的所有权转移到画布。在前面的示例中,可以进行以下修改:functioninit(){...ctxBitmap=canvasBitmap.getContext('bitmaprenderer');...worker2.onmessage=function(e){ctxBitmap.transferFromImageBitmap(e.data.imageBitmap);}}首先,将获取到的渲染上下文的id改为“bitmaprenderer”,返回的ctxBitmap是一个ImageBitmapRenderingContext对象。然后,在渲染ImageBitmap对象时,把drawImage函数改成transferFromImageBitmap函数。最终渲染将如上图所示。transferControlToOffscreen函数transferControlToOffscreen函数可以通过页面的画布对象创建一个OffscreenCanvas。既然可以通过构造函数创建OffscreenCanvas对象,那为什么还要这么做。原因是这样的:我们看前面的例子,我们在worker线程中创建一个OffscreenCanvas对象并绘制,然后得到ImageBitmap对象,通过webworker通信将ImageBitmap传给页面。但是如果canvas.transferControlToOffscreen生成的OffscreenCanvas对象不需要通过webworker通信传递绘制效果,那么在OffscreenCanvas对象生成后,OffscreenCanvas对象的绘制会自动显示在canvas元素上。与webworker通信相比,这具有不言而喻的优势。transferControlToOffscreen函数创建的OffscreenCanvas对象有两大作用:避免绘图中的大量计算阻塞主线程,避免主线程的繁重任务阻塞绘图。下面,我们将用例子来说明上述结论。首先我们写一个Circle类,主要用来画一个圆,可以启动动画,不断改变圆的半径:classCircle{constructor(ctx){this.ctx=ctx;这个.r=0;这个.rMax=50;this.color='黑色';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);这个.ctx.fill();}animate(){this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);这个.r=这个.r+1;如果(this.r>this.rMax){this.r=0;}this.draw();requestAnimationFrame(this.bindAnimate);}changeColor(){斐波那契数列(41);if(this.color=='black'){this.color='blue';}else{this.color='黑色';}这个.r=0;}}draw函数是用来画一个实心圆的animate是用来做动画的,不断的改变圆的半径还有一个函数changeColor,就是改变绘图的颜色,会不断的在黑色和蓝色之间变化。在这个例子中,为了模拟一个耗时的操作,在changeColor函数中,调用了下一个斐波那契函数,fibonacci函数它用于计算斐波那契数列。当传入值为41时,计算量较大,主线程会阻塞一段时间。以下是斐波那契的定义:functionfibonacci(num){return(num<=1)?1:fibonacci(num-1)+fibonacci(num-2);}然后,我们定义两个画布,一个用于普通画布应用程序,一个用于离屏绘制内容:对于第一个canvas,我们直接在上面画一个变化半径的圆:varcanvasInWindow=document.getElementById('画布窗口');varctx=canvasInWindow.getContext('2d');varcircle=newCircle(ctx);circle.animate();canvasInWindow.addEventListener('click',function(){circle.changeColor();});并在画布上添加一个'click'事件,当被点击时,调用Circle类的changeColor函数。第二个canvas,我们使用webworker,先使用transferControlToOffscreen函数创建offscreenCanvas对象offscreen,然后创建worker对象,并将offscreen发送给worker线程:varcanvasInWorker=document.getElementById('canvas-worker');//varctxInWorker=canvasInWorker.getContext('2d');varoffscreen=canvasInWorker.transferControlToOffscreen();varworker=newWorker('./worker.js');worker.postMessage({msg:'start',canvas:offscreen},[offscreen]);canvasInWorker.addEventListener('click',function(){worker.postMessage({msg:'changeColor'});});//canvasInWorker.getContext('2d');//canvas会报错同时添加'click'事件,当被点击时,发送changeColor命令到工作线程。然后,我们看下worker.js线程序的内容:varoffscreen=null,ctx,circle;onmessage=function(e){vardata=e.data;if(data.msg=='start'){offscreen=data.canvas;ctx=offscreen.getContext('2d');圆=新圆(ctx);circle.animate();}elseif(data.msg=='changeColor'&&circle){circle.changeColor();}}functionfibonacci(num){返回(num<=1)?1:斐波那契数(num-1)+斐波那契数(num-2);}classCircle{constructor(ctx){this.ctx=ctx;这个.r=0;这个.rMax=50;this.color='黑色';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);这个.ctx.fill();}animate(){this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);这个.r=这个.r+1;如果(this.r>this.rMax){this.r=0;}this.draw();requestAnimationFrame(this.bindAnimate);}changeColor(){斐波那契(41);如果(this.color=='black'){this.color='blue';}else{this.color='black';}这个.r=0;}}在worker.js中定义了类似的Circle类和斐波那契函数。在onmessage函数中,接受页面传过来的信息。当接收到开始命令时,在接收到的OffscreenCanvas对象离屏绘制圆形动画。当收到changeColor命令时,调用Circle类的changeColor函数。读者可以看到,图形在工作线程中绘制完成后,并没有传给页面,内容会自动显示在页面的破画布上。最终显示效果如下:可以看到两个画布都在绘制动画。不同的是,点击的时候,会调用比较重的changeColor函数。页面端的画布会阻塞主线程,而离屏画布不会阻塞主线程。演示如下:除了不阻塞主线程外,离屏OffscreenCanvas对象也不会被主线程的重任务阻塞。比如我们在页面中添加一个按钮,调用一个耗时任务:heavyTask其实这个耗时任务是通过斐波那契函数来模拟的:functionheavyTask(){fibonacci(41);}当按钮被点击时,页面的画布会停止动画,而离屏canvas不会停止动画:如果读者不了解canvas的相关知识点,建议学习相关知识,也推荐有兴趣的读者订阅专栏(内容本文摘自专栏):Canvas进阶https://xiaozhuanlan.com/canvas,相关知识将在专栏介绍。欢迎关注公众号“IT人飚大叔”。飚叔,10多年开发经验,现任公司系统架构师、技术总监、技术培训师、职业规划师。对计算机图形学、WebGL、前端可视化有深入研究。对程序员思维能力的训练与训练,程序员职业生涯规划有着浓厚的兴趣。

最新推荐
猜你喜欢