Canvas简单入门
创建canvas
至少需要提供width
和height
属性,才能通知浏览器需要多大位置画图。标签的内容是后备数据,在浏览器不支持canvas
元素时显示。
1
| <canvas id="mycanvas" width="200" height="200">haha</canvas>
|
可以通过if(canvas.getContext)
来判断浏览器是否支持canvas
。
通过canvas.getContext('2d')
可以获取 2D 绘图上下文。2D 绘图上下文提供了绘制 2D 图形的方法。左边原点(0, 0)在 canvas
元素的左上角,x 坐标向右增长,y 坐标向下增长。
从画布上导出一张 PNG 格式的图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <body> <canvas id="mycanvas" width="200" height="200">haha</canvas>
<script> const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const imgURI = mycanvas.toDataURL("image/png"); console.log(imgURI); } </script> </body>
|

我们查看控制台可以发现,输出了一串base64
编码,也就是说,canvas.toDataURL
就是将画布 canvas
转换成base64
编码。
填充与描边
显示效果取决于两个属性:fillStyle
和strokeStyle
。
1 2 3 4 5 6 7 8 9
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.fillStyle = "#000"; context.strokeStyle = "red"; }
|
没有效果?
别急,这是因为我们只是设置了填充和描边而已,想要它生效,还需要绘制出来才能有效果。
绘制矩形
与绘制矩形相关的方法有三个。它们都接收 4 个参数:矩形 x 坐标、矩形 y 坐标、矩形宽度和矩形高度。(单位是像素,但是传参时不需要传单位)
fillRect
strokeRect
clearRect
fillRect
:绘制并填充矩形
fillRect
:以指定颜色在画布上绘制并填充矩形,填充色使用fillStyle
来设置。
1 2 3 4 5 6 7 8 9 10 11 12
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.fillStyle = "pink"; context.fillRect(10, 10, 50, 50);
context.fillStyle = "rgba(0, 0, 0, .1)"; context.fillRect(30, 30, 50, 50); }
|

stokeRect
:绘制矩形轮廓
stokeRect
:绘制矩形轮廓,颜色由strokeStyle
来指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.strokeStyle = "red"; context.lineWidth = 5; context.strokeRect(10, 10, 50, 50);
context.strokeStyle = "blue"; context.fillStyle = "rgba(0, 0, 0, .1)"; context.strokeRect(30, 30, 50, 50); }
|

clearRect
:擦除画布中某个区域
clearRect
:擦除画布中某个区域,把擦除的区域变透明。
1 2 3 4 5 6 7 8 9 10 11
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.fillStyle = "red"; context.fillRect(0, 0, 200, 200);
context.clearRect(50, 50, 100, 100); }
|

绘制路径
绘制路径需要先调用beginPath
,表示要开始绘制路径,再调用以下方法来绘制路径。
lineTo(x, y)
:绘制一条从上一个点到(x, y)的直线
moveTo(x, y)
:不绘制线条,只是把画笔移动到(x, y)
- 更多
绘制完路径后,可以指定fillStyle
属性并调用fill
方法来填充路径,也可以指定strokeStyle
属性并调用stoke
方法来描画路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.beginPath();
context.arc(100, 100, 99, 0, 2 * Math.PI, true);
context.strokeStyle = "pink"; context.stroke(); }
|

还可以调用clip
方法创建一个新的剪切区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.beginPath();
context.arc(100, 100, 50, 0, 2 * Math.PI, true);
context.fillStyle = "pink"; context.clip();
context.fillRect(0, 0, 100, 100); }
|
上面的扇形怎么出来的呢?
我们可以把clip
变成fill
,看下没有被剪切的话,是什么样子。

也就是说,实际上剪切就是两个图形相交部分。
如果使用lineTo
需要注意:没有设置moveTo
时,这个位置并不是(0, 0),而是空,所以第一次的lineTo
没法画出结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d"); context.beginPath();
context.lineTo(100, 50); context.lineTo(200, 0);
context.lineWidth = 8; context.strokeStyle = "pink";
context.stroke(); }
|
没有moveTo
:

有moveTo
:

beginPath 的作用
上面的例子中,beginPath
并没有作用,也就是说上面的例子中,其实有没有beginPath
都一样。那么beginPath
有什么作用呢?
beginPath
表示下面绘制的图形是一个新的路径。具体看下实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const context = mycanvas.getContext("2d");
context.beginPath();
context.moveTo(0, 0); context.lineTo(100, 50);
context.lineWidth = 8; context.strokeStyle = "pink";
context.stroke();
context.lineTo(200, 0); context.strokeStyle = "purple"; context.stroke();
|

想要的效果是画出两条不一样颜色的线,但是最后是一种颜色折线,这是因为我们只是用了一次beginPath
,所以就会把这两条线当成同一个路径,最后调用的stroke
就会把原本是粉色的线再用紫色画一遍,所以最终的效果就是只有一条折线。
所以需要使用beginPath
创建新路径,新的路径还是会有没有设置moveTo
时,这个位置并不是(0, 0),而是空的问题,所以需要使用moveTo
设置位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d"); context.beginPath();
context.moveTo(0, 0); context.lineTo(100, 50);
context.lineWidth = 8; context.strokeStyle = "pink"; context.stroke();
context.beginPath(); context.moveTo(100, 50); context.lineTo(200, 0); context.strokeStyle = "purple"; context.stroke(); }
|

closePath 的作用
有可能会陷进closePath
是结束路径的误区,认为closePath
就是beginPath
的配套。但是closePath
和beginPath
并不是配套的,它们的功能不一样。所以closePath
之后的路径也不是新的路径,只有beginPath
才行。
而closePath
的作用是将最近绘制的路径闭合,和之前有没有beginPath
无关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.moveTo(10, 10); context.lineTo(100, 50); context.lineTo(20, 70); context.closePath();
context.lineWidth = 8; context.strokeStyle = "pink"; context.stroke(); }
|

上面我们只绘制了两条线,但是最终得到的结果是一个三角形,这是因为我们使用closePath
把最近绘制的路径闭合了。
绘制文本
绘制文本有两种方法。
fillText
:使用fillStyle
属性绘制文本
strokeText
:使用strokeStyle
属性绘制文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.moveTo(10, 10); context.lineTo(150, 75); context.lineTo(30, 100); context.closePath();
context.lineWidth = 1; context.strokeStyle = "pink";
context.fillStyle = "purple"; context.fillText("CLZ", 50, 60); context.strokeText("CLZ", 50, 80);
context.stroke(); }
|

可以通过font
、textAlign
、textBaseline
属性设置文本的字体、对齐方式、基线。
示例:
1
| context.font = "700 16px Arial";
|

textAlign
:
- 如果是
start
,那么 x 坐标就是文本的左侧坐标
- 如果是
center
,那么 x 坐标就是文本的中心点坐标
- 如果是
end
,那么 x 坐标就是文本的右侧坐标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.moveTo(10, 10); context.lineTo(150, 75); context.lineTo(30, 100); context.closePath();
context.lineWidth = 1; context.strokeStyle = "pink";
context.font = "700 16px Arial"; context.fillStyle = "purple";
context.textAlign = "start"; context.strokeText("CLZ", 50, 50);
context.textAlign = "center"; context.fillText("CLZ", 50, 65);
context.textAlign = "end"; context.strokeText("CLZ", 50, 80);
context.stroke(); }
|

textBaseline
类似
变换
2D 换图上下文支持所有常见的绘制变化。
rotate(a)
:围绕原点把图像旋转 a 弧度
scale(x, y)
:缩放图像
translate(x, y)
:移动原点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.beginPath();
context.arc(100, 100, 50, 0, 2 * Math.PI, true);
context.lineWidth = "8"; context.strokeStyle = "pink";
context.translate(100, 100);
context.rotate(Math.PI);
context.scale(0.75, 0.75);
context.moveTo(0, 0); context.lineTo(25, 30);
context.stroke(); }
|

上面的例子中,已经把很多变化都使用上了,如果想要了解具体例子可以注释掉其他部分。
save 和 restore 的作用
save
方法可以保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。后续可以通过restore
方法,恢复上下文的设置和变换。save
和restore
的使用类似于栈,后进先出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.fillStyle = "red"; context.save();
context.fillStyle = "blue"; context.translate(100, 100); context.save();
context.fillStyle = "purple"; context.translate(-100, -100); context.fillRect(0, 0, 100, 100);
context.restore(); context.fillRect(0, 0, 100, 100);
context.restore(); context.fillRect(100, 0, 100, 100);
context.restore(); context.fillRect(0, 100, 100, 100); }
|

分析:设 XXX 为绘图上下文的设置和变化
- 设置填充色为红色,
save
保存
- 设置填充色为蓝色,移动原点,
save
保存
- 设置填充色为紫色,移动原点,画出紫色的矩形
restore
恢复XXX,此时,原点为(100, 100),填充色为蓝色。画出蓝色的矩形
restore
恢复**XXX**,此时,原点为(0, 0),填充色为红色。画出红色的矩形
restore
已经没有保存的XXX,所以XXX不会变化
绘制图像
1 2
| <img src="./avatar.png" alt=""> <canvas id="mycanvas" width="200" height="200">haha</canvas>
|
通过drawImage
把 HTML 的 img 元素或另一个 canvas 元素绘制到当前画布中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
const img = document.images[0];
context.drawImage(img, 10, 10, 100, 100); }
|
只传3个参数,画到画布上的跟原来的图像一样大,但画布没那么大。所以会只有一部分。

传入五个参数,可以让设置图像的宽高,显示完整的图像。

去掉DOM树上的img
上面的做法是需要html
中有img
元素才能执行的.实际上,我们也可以通过image
对象来实现。
即获取图像不再是通过document.images[0]
,而是
1 2
| const img = new Image(); img.src = "./avatar.png";
|
另外,绘制图像应该在img
的load
事件回调中调用。
1 2 3 4 5 6
| const img = new Image(); img.src = "./avatar.png"; img.onload = () => { context.drawImage(img, 10, 10, 100, 100); };
|

还可以接收 9 个参数,实现把原始图像的一部分绘制到画布上。
如:context.drawImage(img, 0, 10, 50, 50, 0, 100, 20, 30)
,从原始图像的(0, 10)开始,50 像素宽、50 像素高,画到画布上(0, 100)开始,宽 40 像素、高 60 像素。
1 2 3 4 5 6 7 8 9 10 11 12
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
const img = document.images[0];
context.drawImage(img, 0, 10, 300, 300, 100, 100, 40, 40); }
|

下载图像
操作的结果可以使用canvas.toDataURL()
方法获取。
再搭配下载图片的方式就能实现下载图片。(这里用的是a
标签方法)
1 2 3 4 5 6 7
| const a = document.createElement("a"); a.href = mycanvas.toDataURL();
a.download = img.src.split("/")[img.src.split("/").length - 1];
a.click();
|
阴影
设置好阴影有关的属性值,就能够自动为要绘制的形状或路径生成阴影
shadowOffsetX
:阴影相对于形状或路径的 x 坐标偏移。默认为 0
shadowOffsetY
:阴影相对于形状或路径的 y 坐标偏移。默认为 0
shadowBlur
:阴影的模糊量。默认值为 0,表示不模糊
shadowColor
:阴影的颜色。默认为黑色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
context.shadowOffsetX = 5; context.shadowOffsetY = 10; context.shadowBlur = 5; context.shadowColor = "rgba(0, 0, 0, .2)";
context.fillStyle = "red"; context.fillRect(0, 0, 50, 50);
context.moveTo(100, 100); context.lineTo(180, 20);
context.lineWidth = 12; context.stroke(); }
|

渐变
线性渐变
线性渐变可以调用上下文的createLinearGradient
方法,接收四个参数:起点 x 坐标、起点 y 坐标、终点 x 坐标、终点 y 坐标,创建CanvasGradient
对象。
有了渐变对象后,就需要添加渐变色标了,通过addColorStop
可以添加色标,第一个参数范围为 0~1,第二个参数是 CSS 颜色字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
const gradient = context.createLinearGradient(10, 10, 180, 180);
gradient.addColorStop(0, "red"); gradient.addColorStop(0.5, "blue"); gradient.addColorStop(1, "purple");
context.fillStyle = gradient; context.fillRect(0, 0, 200, 200); }
|

为了让渐变覆盖整个矩形,渐变的坐标和矩形的坐标应该搭配合适,不然只会显示部分渐变。
还可以调用上下文的createRadialGradient
方法来创建径向渐变。接收 6 个参数,前 3 个参数指定起点圆形中心的 x 坐标、y 坐标和半径,后 3 个参数指定终点圆形中心的 x 坐标和半径。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
let gradient = context.createRadialGradient(100, 100, 20, 100, 100, 80); gradient.addColorStop(0, "white"); gradient.addColorStop(1, "black");
context.fillStyle = gradient; context.fillRect(0, 0, 200, 200); }
|
上面这个渐变,简单理解就是内层圆为半径为 20 像素的纯白圆
,外层圆为 80 像素的白渐变黑圆
,剩余部分就是黑色。

图案
图案适用于填充和描画图形的重复图像。
通过createPattern
方法,该方法接收两个参数,第一个参数是img
元素,第二个参数是是否重复,和background-repeat
属性一样。
然后,像渐变一样,把pattern
对象赋值给fillStyle
属性即可。
这个图案实际上就有点背景图像的味道了,通过创建pattern
对象,来控制图像的重复。然后,给绘图上下文的fillStyle
赋值,设置填充样式,最后再通过fillRect
来设置图案的位置和大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const mycanvas = document.getElementById("mycanvas");
if (mycanvas.getContext) { const context = mycanvas.getContext("2d");
const image = document.images[0];
const pattern = context.createPattern(image, "repeat");
context.fillStyle = pattern; context.fillRect(0, 0, 190, 190); }
|
