OffscreenCanvas 是一个实验中的新特性,主要用于提升 Canvas 2D/3D 绘图的渲染性能和使用体验。OffscreenCanvas 的 api 很简单,但是要真正掌握好如何使用。
OffscreenCanvas和canvas都是渲染图形的对象。 不同的是canvas只能在window环境下使用,而OffscreenCanvas即可以在window环境下使用,也可以在web worker中使用,这让不影响浏览器主线程的离屏渲染成为可能。
与之关联的还有ImageBitmap对象和ImageBitmapRenderingContext。
ImageBitmap对象表示能够被绘制到 canvas上的位图图像,具有低延迟的特性。 ImageBitmap提供了一种异步且高资源利用率的方式来为WebGL的渲染准备基础结构。 ImageBitmap可以通过createImageBitmap函数来创建,它可以从多种图像源生成。 还可以通过OffscreenCanvas.transferToImageBitmap函数生成。
ImageBitmap.height 只读 无符号长整型数值,表示ImageData的css像素单位的高度。 ImageBitmap.width 只读 无符号长整型数值, 表示ImageData的CSS像素单位的宽度。
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) { ... });
有两种方式可以创建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会报错。
通过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高级进阶 https://xiaozhuanlan.com/canvas,相关知识会在专栏中介绍。
欢迎关注公众号“ITman彪叔”。彪叔,拥有10多年开发经验,现任公司系统架构师、技术总监、技术培训师、职业规划师。在计算机图形学、WebGL、前端可视化方面有深入研究。对程序员思维能力训练和培训、程序员职业规划有浓厚兴趣。
Canvas 是H5的一部分,允许脚本语言动态渲染图像。Canvas 定义一个区域,可以由html属性定义该区域的宽高,javascript代码可以访问该区域,通过一整套完整的绘图功能(API),在网页上渲染动态效果图。
上传截图很多做法是把图像发送到后端,把裁剪后的结果发送给浏览器,这种方式会增加处理时延。用canvas提供的API实现纯前端的剪切:这里头关键有三步:显示未经处理的图片,得到裁剪区域,显示裁剪后的区域。
javascript完成图片格式转换: 通过input上传图片,使用FileReader将文件读取到内存中。将图片转换为canvas,canvas.toDataURL()方法设置为我们需要的格式,最后将canvas转换为图片。
现在因为有了离屏Canvas,你可以不用在你的主线程中绘制图像了!Canvas 是一个非常受欢迎的表现方式,同时也是WebGL的入口。它能绘制图形,图片,展示动画,甚至是处理视频内容
最近的一个客户项目中,简化的需求是绘制按照行列绘制很多个圆圈。需求看起来不难,上手就可以做,写两个for循环。,IT行业的知识更新越来越快,能够以不变应万变的人,就是拥有良好的学习力、创造力、判断力和思考力的人。这些能力会让你在变换万千的技术海洋中,屹立不倒,不被淹没。
利用canvas将网页元素生成图片并保存在本地,首先引入三个文件,createElementNS() 方法可创建带有指定命名空间的元素节点。 createElementNS(ns,name) > createElementNS() 方法与 createElement() 方法相似
随着大数据时代的来临,物联网的日益发展,原先的 SCADA 系统本身也在求新求变,从最开始的专业计算机和操作系统,到通用计算机和相关软件,再到现在基于 HTML5 Canvas 的新型组态开发,其应用的范围也从最初的电力
在写下合格粒子运动时要先清楚你的思路,不能一开始就盲目的开始写,首先先要确定思路然后在去一步步的实现,在写的过程要注意细节,要思考js有些知识是跟数学知识相关的要注意观察
canvas 画的圆不是圆,是椭圆。不要在style里指定 Canvas 的宽度,Canvas 画布的尺寸的大小和显示的大小是有很大的区别的,在 canvas 里面设置的是才是 Canvas 本身的大小。不要企图通过闭合现有路径来开始一条新路径
由于一些移动端的兼容性原因,我们某个项目需要前端将pdf转换成在移动端页面可直接观看的界面。为了方便解决,我们采用了pdf.js这个插件,该插件可以将pdf转换成canvas绘制在页面上
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!