canvas 简介
什么是canvas
?简单来说,canvas是h5中提供的一个元素,可以用来在网页上绘制图像或者动画,甚至可以进行实时视频的处理和渲染工作;
它最初由苹果内部使用自己 MacOS X WebKit 推出,供应用程序使用像仪表盘的构件和 Safari 浏览器使用。后来,有人通过 Gecko 内核的浏览器 (尤其是 Mozilla和Firefox),Opera 和 Chrome 和超文本网络应用技术工作组建议为下一代的网络技术使用该元素。
Canvas 是由 HTML 代码配合高度和宽度属性而定义出的可绘制区域。JavaScript 代码可以访问该区域,类似于其他通用的二维 API,通过一套完整的绘图函数来动态生成图形。
Mozilla 程序从 Gecko 1.8 (Firefox 1.5) 开始支持<canvas>
, Internet Explorer 从 IE9 开始<canvas>
。Chrome 和 Opera 9+ 也支持<canvas>
。
引自:www.runoob.com/w3cnote/htm…
基本使用
新建canvas
在我们的html中添加如下元素,就可以得到一个新的canvas画布
<canvas id="container" style="border:1px solid black;"></canvas>
此时并没有给这个元素设置宽高,它的默认width为300px、height为150px
<canvas> 元素
<canvas> 只有两个可选的属性:width、heigth 属性。
如果不给 <canvas> 设置 widht、height 属性时,则默认 width为300、height 为 150,单位都是 px。也可以使用 css 属性来设置宽高,但是如宽高属性和初始比例不一致,它会出现扭曲。所以,建议永远不要使用 css 属性来设置 <canvas> 的宽高。
>>>什么是canvas的扭曲?
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<style>
canvas {
height: 300px;
width: 300px;
border: 1px solid black;
}
</style>
</head>
<body>
<canvas width="100px" height="200px"></canvas>
<script src='canvas.js'></script>
</body>
</html>
// canvas.js
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
// 绘制一个50*50的矩形
ctx.fillRect(10,10,50,50);
当我们执行上面的代码之后,本意是在画布上绘制一个50*50的矩形,但是实际上的效果却显然跟我们预期的不一致,这时候就是canvas发生了扭曲。可以这么理解,画布和形状是先渲染在页面上的,然后由css设置的宽高进行了拉伸,最后出现的就是拉伸后的结果。
替换内容
可以看到canvas并没有像img中一样的alt属性,在浏览器不支持canvas时,可以用下面的这种方式来进行文本替换
<canvas>
你的浏览器不支持 canvas,请升级你的浏览器。
</canvas>
canvas 渲染上下文
从前文中可以看到,canvas在初始化的时候是一片空白的,那么如何进行绘图呢?就是通过canvas提供的上下文进行; canvas提供了多种渲染上下文
- "2d", 建立一个 CanvasRenderingContext2D 二维渲染上下文。
- "webgl" (或"experimental-webgl") 将创建一个 WebGLRenderingContext 三维渲染上下文对象。只在支持WebGL 版本1(OpenGL ES 2.0)的浏览器上可用。
- "webgl2" (或 "experimental-webgl2") 这将创建一个 WebGL2RenderingContext 三维渲染上下文对象。只在支持 WebGL 版本2 (OpenGL ES 3.0)的浏览器上可用。
- "bitmaprenderer" 将创建一个只提供将canvas内容替换为指定ImageBitmap功能的ImageBitmapRenderingContext 。
这里我们还是专注于2d上下文环境(后文简称为ctx),看它可以帮助我们做哪些事情。
栅格
在真正开始绘制之前,首先需要了解一下画布栅格(canvas grid)以及坐标空间。
假设我们创建了一个宽150px, 高150px的canvas元素。canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。
绘制形状
不同于svg提供的多种默认形状,<canvas>只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线段),各种路径的组合绘制出所有其他类型的图形。canvas中提供了多种绘制路径的方法。
矩形
首先看如何绘制矩形 canvast 提供了三种方法绘制矩形:
1、fillRect(x, y, width, height):绘制一个填充的矩形;
2、strokeRect(x, y, width, height):绘制一个矩形的边框;
3、clearRect(x, y, widh, height):清除指定的矩形区域,然后这块区域会变的完全透明;
说明:这 3 个方法具有相同的参数。
- x, y:指的是矩形的左上角的坐标。(相对于canvas的坐标原点)
- width, height:指的是绘制的矩形的宽和高
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
// 绘制一个100*100的矩形,填充黑色
ctx.fillRect(25, 25, 100, 100);
// 清除60*60的矩形区域,背景为透明
ctx.clearRect(45, 45, 60, 60);
// 生成50*50的边框矩形区域
ctx.strokeRect(50, 50, 50, 50);
path 路径
使用路径绘制图形需要一些额外的步骤:
-
创建路径起始点
-
调用绘制方法去绘制出路径
-
把路径封闭
-
一旦路径生成,通过描边或填充路径区域来渲染图形。 下面是canvas提供的方法:
-
beginPath() -- 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径
-
moveTo(x, y) -- 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。
-
closePath() -- 闭合路径之后,图形绘制命令又重新指向到上下文中
-
stroke() -- 通过线条来绘制图形轮廓
-
fill() -- 通过填充路径的内容区域生成实心的图形 接下来开始绘制一些形状和图形
线段
首先绘制一条直线
ctx.beginPath();
ctx.moveTo(10,70);
ctx.lineTo(200,70);
ctx.stroke();
多次调用lineTo,我们可以得到多线段
ctx.beginPath();
ctx.moveTo(10,70);
ctx.lineTo(40,70);
ctx.lineTo(60,100);
ctx.lineTo(250,120);
ctx.stroke();
矩形
尝试使用closePath可以闭合当前形状,这里我们绘制了一个矩形:
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(80,10);
ctx.lineTo(80,80);
ctx.lineTo(10,80);
ctx.closePath();
ctx.stroke();
还可以绘制三角形
ctx.beginPath();
ctx.moveTo(100,70);
ctx.lineTo(200,70);
ctx.lineTo(150,120);
ctx.closePath();
ctx.fill();
圆形及圆弧
上面都是使用直线进行绘画,再来看下ctx如何绘制曲线
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false); // 口(顺时针)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, false); // 右眼
ctx.stroke();
可以看到使用arc方法来绘制了两个圆形,组成了左眼和右眼,并且绘制了一个圆弧。
arc函数如下:
void ctx.arc(x, y, r, startAngle, endAngle, anticlockwise)
// 以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。
// anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针)
- 这里的度数都是弧度。
- 0 弧度是指的 x 轴正方向。
还可以使用arcTo方法进行绘制
void ctx.arcTo(x1, y1, x2, y2, radius);
/* 使用当前的描点(前一个moveTo或lineTo等函数的止点)。根据当前描点与给定的控制点1连接的直
线,和控制点1与控制点2连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径
x1,y1为第一个控制点的坐标,x2,y2为第二个控制点坐标,radius为圆弧半径
*/
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制
ctx.moveTo(110, 75);
// 分别绘制两段圆弧
ctx.arcTo(110, 110, 65, 110, 35);
ctx.arcTo(40, 110, 40, 100, 35);
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, false); // 右眼
ctx.stroke();
贝塞尔曲线
quadraticCurveTo(cp1x, cp1y, x, y)
绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点。
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。
如果有svg中绘制贝塞尔曲线的经历,这里的参数想必也都很好理解,或者可以根据下图来认识控制点和贝塞尔曲线:
二次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(10, 200); //起始点
//绘制二次贝塞尔曲线
ctx.quadraticCurveTo(40, 100, 200, 200);
ctx.stroke();
三次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(10, 20); //起始点
//绘制二次贝塞尔曲线
ctx.bezierCurveTo(20, 100, 100, 50, 200, 130);
ctx.stroke();
Path2d 对象 (Experimental)
path2d是一个实验中的功能
const rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
const circle = new Path2D();
circle.moveTo(125, 35);
circle.arc(100, 35, 25, 0, 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
// 使用svg path来初始化路径
const svgPath = new Path2D("M150 10 h 80 v 80 h -80 Z");
ctx.stroke(svgPath);
样式和颜色
上文中主要介绍了如何绘制不同的形状和路径,但是图案当然还需要一些色彩和样式作为补充。
颜色
要给图形增加颜色,可以使用这两个属性:fillStyle
和strokeStyle
。
fillStyle
ctx.fillStyle = 'rgb(200,100,200)';
ctx.fillRect(30, 30, 100, 100);
strokeStyle
ctx.strokeStyle = 'rgb(200,100,200)';
ctx.strokeRect(30, 30, 100, 100);
透明度
globalAlpha
这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0
ctx.fillStyle = 'rgb(200,100,200)';
ctx.globalAlpha = 0.2;
ctx.fillRect(30, 30, 100, 100);
ctx.globalAlpha = 1;
ctx.fillRect(150, 30, 100, 100);
我们还可以使用rgba的方式来设置透明度,也可以达到同样的效果:
ctx.fillStyle = 'rgb(200,100,200,0.2)';
ctx.fillRect(30, 30, 100, 100);
ctx.fillStyle = 'rgb(200,100,200)';
ctx.fillRect(150, 30, 100, 100);
线形
对于线的样式,canvas提供了很多的属性可以配置,依次来介绍一下:
lineWidth
设置当前绘线的粗细。属性值必须为正数。默认值是1.0。
for (var i = 0; i < 10; i++){
ctx.lineWidth = 1+i;
ctx.beginPath();
ctx.moveTo(5+i*14,5);
ctx.lineTo(5+i*14,140);
ctx.stroke();
}
需要注意的是,线宽是指给定路径的中心到两边的粗细,也就是在路径的两边各绘制线宽的一半。因为画布的坐标并不和像素直接对应,当需要获得精确的水平或垂直线的时候要特别注意。
在上面的例子中,用递增的宽度绘制了10条直线。最左边的线宽1.0单位。并且,最左边的以及所有宽度为奇数的线并不能精确呈现,这就是因为路径的定位问题。
如果我们「仔细观察」上图,可以发现两点问题:
- 宽度1.0与宽度2.0从粗细上看起来没有什么差异;
- 奇数粗细的线边缘都有些模糊。
再从下图来开始分析边的绘制过程:
如果你想要绘制一条从 (3,1) 到 (3,5),宽度是 1.0 的线条,你会得到像第二幅图一样的结果。实际填充区域(深蓝色部分)仅仅延伸至路径两旁各一半像素。而这半个像素又会以近似的方式进行渲染,这意味着那些像素只是部分着色,结果就是以实际笔触颜色一半色调的颜色来填充整个区域(浅蓝和深蓝的部分)。这就是上例中为何宽度为 1.0 的线并不准确的原因。
要解决这个问题,你必须对路径施以更加精确的控制。已知粗 1.0 的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5,1) 到 (3.5,5) 的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0 的线条。
lineCap
属性决定了线端点的样式。它可以为下面的三种的其中之一:butt
,round
和 square
。默认是 butt
。
// 创建路径
ctx.strokeStyle = '#09f';
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(140, 10);
ctx.moveTo(10, 140);
ctx.lineTo(140, 140);
ctx.stroke();
// 画线条
ctx.strokeStyle = 'black';
for (let i = 0; i < lineCap.length; i++) {
ctx.lineWidth = 15;
ctx.lineCap = lineCap[i];
ctx.beginPath();
ctx.moveTo(25 + i * 50, 10);
ctx.lineTo(25 + i * 50, 140);
ctx.stroke();
}
lineJoin
的属性值决定了图形中两线段连接处所显示的样子。它可以是这三种之一:round, bevel 和 miter。默认是 miter。
const lineJoin = ['round', 'bevel', 'miter'];
ctx.lineWidth = 10;
for (let i = 0; i < lineJoin.length; i++) {
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(-5, 5 + i * 40);
ctx.lineTo(35, 45 + i * 40);
ctx.lineTo(75, 5 + i * 40);
ctx.lineTo(115, 45 + i * 40);
ctx.lineTo(155, 5 + i * 40);
ctx.stroke();
}
在使用miter属性的时候,线段的外侧边缘会被延伸交汇于一点上。线段之间夹角比较大时,交点不会太远,但随着夹角变小,交点距离会呈指数级增大。miterLimit
属性就是用来设定外延交点与连接点的最大距离,如果交点距离大于此值,连接效果会变成了 bevel。
ctx.lineWidth = 10;
ctx.strokeStyle = '#000';
ctx.miterLimit = 5;
ctx.beginPath();
ctx.moveTo(0, 100);
for (let i = 0; i < 24; i++) {
var dy = i % 2 == 0 ? 25 : -25;
ctx.lineTo(Math.pow(i, 1.5) * 2, 75 + dy);
}
ctx.stroke();
可以看到,最左侧的几条线由于夹角太小而延长线太长,导致连接线样式更改为bevel。
虚线
可以用 setLineDash
方法和 lineDashOffset
属性来制定虚线样式。区别在于 setLineDash
方法接受一个数组,来指定线段与间隙的交替;lineDashOffset
属性设置起始偏移量。
ctx.beginPath();
ctx.setLineDash([4,2]);
ctx.strokeRect(10,10, 100, 100);
canvas 文本绘制
canvas 提供了两种方法来渲染文本:
fillText(text, x, y [, maxWidth])
在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.
ctx.font = "48px serif";
ctx.fillText("Hello world", 10, 50);
strokeText(text, x, y [, maxWidth])
在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.
ctx.font = "48px serif";
ctx.strokeText("Hello world", 10, 50);
我们可以通过以下的属性来配置文本样式:
font = value
当前我们用来绘制文本的样式. 这个字符串使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif
。
textAlign = value
文本对齐选项. 可选的值包括:start
, end
, left
, right
or center
. 默认值是 start。
这里的textAlign="center"比较特殊。textAlign的值为center时候文本的居中是基于你在fillText的时候所给的x的值,也就是说文本一半在x的左边,一半在x的右边(可以理解为计算x的位置时从默认文字的左端,改为文字的中心,因此你只需要考虑x的位置即可)。所以,如果你想让文本在整个canvas居中,就需要将fillText的x值设置成canvas的宽度的一半。
textBaseline = value
决定文字垂直方向的对齐方式. 可选的值包括:top
, hanging
, middle
, alphabetic
, ideographic
, bottom
。默认值是 alphabetic
。
direction = value
文本方向。可能的值包括:ltr
, rtl
, inherit
。默认值是 inherit
。
状态管理
Saving and restoring state 是绘制复杂图形时必不可少的操作,在这里我们简单介绍一下作为一个预备知识。
save
和 restore
方法是用来保存和恢复 canvas 状态的,都没有参数。
Canvas 的状态就是当前画面应用的所有样式
和变形
的一个快照。
save()
Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。
一个绘画状态包括:
-
当前应用的变形(即移动,旋转和缩放)
-
各种样式的值(strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation)
-
当前的裁切路径(clipping path)
可以调用任意多次 save
方法。
restore()
每一次调用 restore
方法,上一个保存的状态就从栈中弹出,所有设定都恢复(类似数组的 pop())。
ctx.fillRect(0, 0, 150, 150); // 使用默认设置绘制一个矩形
ctx.save(); // 保存默认状态
ctx.fillStyle = 'red' // 在原有配置基础上对颜色做改变
ctx.fillRect(15, 15, 120, 120); // 使用新的设置绘制一个矩形
ctx.save(); // 保存当前状态
ctx.fillStyle = '#FFF' // 再次改变颜色配置
ctx.fillRect(30, 30, 90, 90); // 使用新的配置绘制一个矩形
ctx.restore(); // 重新加载之前的颜色状态
ctx.fillRect(45, 45, 60, 60); // 使用上一次的配置绘制一个矩形
ctx.restore(); // 加载默认颜色配置
ctx.fillRect(60, 60, 30, 30); // 使用加载的配置绘制一个矩形
裁剪路径
前面我们介绍了使用stroke、fill方法,canvas还提供了clip方法用来裁剪路径。
裁切路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分。
ctx.clip(path, fillRule)
fillRule
这个算法判断一个点是在路径内还是在路径外。
允许的值:
"nonzero": 非零环绕原则,默认的原则。
- 在路径包围的区域中,随便找一点,向外发射一条射线,
- 和所有围绕它的边相交,
- 然后开启一个计数器,从0计数,
- 如果这个射线遇到顺时针围绕,那么+1,
- 如果遇到逆时针围绕,那么-1,
- 如果最终值非0,则这块区域在路径内。 "evenodd": 奇偶环绕原则。
- 在路径包围的区域中,随便找一点,向外发射一条射线,
- 和所有围绕它的边相交,
- 查看相交线的个数,如果为奇数,就填充,如果是偶数,就不填充。
对于箭头发起的区域,左为奇偶环绕规则,右为非零环绕规则
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
const x = w / 2;
const y = h / 2;
const r = 200;
const start = -Math.PI / 2;
const end = Math.PI * 3 / 2;
ctx.arc(x, y, r, start, end);
ctx.fillStyle = '#D43D59';
ctx.fill();
ctx.beginPath();
ctx.moveTo(x, y - r);
// 顶点连下左
ctx.lineTo(x - r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
// 下左连上右
ctx.lineTo(x + r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
// 上右连上左
ctx.lineTo(x - r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
// 上左连下右
ctx.lineTo(x + r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
ctx.fillStyle = '#246AB2';
// fill填充规则---奇偶原则
ctx.fill('evenodd');
path
需要剪切的 Path2D 路径。
变形 Transformations
translate
translate(x, y)
用来移动 canvas 的原点到指定的位置。
translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如下图所示。
ctx.save(); //保存坐原点平移之前的状态
ctx.translate(10, 10);
ctx.strokeRect(0, 0, 30, 30);
ctx.restore(); //恢复到最初状态
ctx.translate(50, 50);
ctx.fillRect(0, 0, 30, 30);
rotate(angle)
旋转坐标轴。
这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。 旋转的中心是坐标原点。
ctx.fillStyle = "red";
ctx.save();
ctx.translate(100, 100);
ctx.rotate(Math.PI / 180 * 45);
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 100, 100);
ctx.restore();
ctx.save();
ctx.translate(0, 0);
ctx.fillRect(0, 0, 50, 50)
ctx.restore();
scale(x, y)
用来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。
scale方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩 小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。
默认情况下,canvas 的 1 单位就是 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。
ctx.fillStyle = "red";
ctx.scale(1,2);
ctx.fillRect(100, 20, 100, 100);
变形矩阵
transform(a, b, c, d, e, f)
这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵.
a (m11)
水平方向的缩放;
b(m12)
竖直方向的倾斜偏移;
c(m21)
水平方向的倾斜偏移;
d(m22)
竖直方向的缩放;
e(dx)
水平方向的移动;
f(dy)
竖直方向的移动;
我们可以猜测出默认的矩阵值(1,0,0,1,0,0);
setTransform(a, b, c, d, e, f)
这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。
resetTransform()
重置当前变形为单位矩阵,它等价于ctx.setTransform(1, 0, 0, 1, 0, 0)。
ctx.transform(1, 1, 0, 1, 0, 0);
ctx.fillRect(0, 0, 100, 100);
ctx.resetTransform();
ctx.fillRect(110, 110, 100, 100);
canvas VS svg
svg
相关的入门介绍可以看这里
canvas与svg相比,两种技术个人觉得并不好说哪种有绝对的优劣,只能说应用场景存在差异,但在一些js库的帮助下,这两者间的能力壁垒可能会有些突破,比如说
zRender
,使用数据驱动,并且提供类Dom事件模型,弥补了canvas本身弱事件操作的一些问题。
简单对两者做一些比较:
标题 | canvas | svg |
---|---|---|
使用方式 | 偏向于使用js程序式绘图,动态生成 | 使用xml描述绘图 |
操作对象 | 基于像素点(单canvas元素) | 基于svg的图形元素(多元素Rect、Path、Text等) |
使用场景 | 像素处理,适合动态渲染以及大数据量绘制 | 大面积的小数据量,高保真场景(如打印等) |
设计原理 | 基于位图,不能改变大小,只能缩放 | 基于矢量,能很好处理图形大小的改变 |
功能支持 | 2d(图形、动画)、3d(webgl)绘制 | 图形、滤镜、动画 |
参考资料
MDN:developer.mozilla.org/zh-CN/docs/…
菜鸟教程:www.runoob.com/w3cnote/htm…
canvas填充原则:www.jianshu.com/p/d4b8b5d93…