创建canvas
至少需要提供width
和height
属性,才能通知浏览器需要多大位置画图。标签的内容是后备数据,在浏览器不支持canvas
元素时显示。
<canvas id="mycanvas" width="200" height="200">haha</canvas>
可以通过if(canvas.getContext)
来判断浏览器是否支持canvas
。
通过canvas.getContext('2d')
可以获取 2D 绘图上下文。2D 绘图上下文提供了绘制 2D 图形的方法。左边原点(0, 0)在 canvas
元素的左上角,x 坐标向右增长,y 坐标向下增长。
<body>
<canvas id="mycanvas" width="200" height="200">haha</canvas>
<script>
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
// 获得图像的数据URI
const imgURI = mycanvas.toDataURL("image/png");
console.log(imgURI);
}
</script>
</body>
我们查看控制台可以发现,输出了一串base64
编码,也就是说,canvas.toDataURL
就是将画布 canvas
转换成base64
编码。
显示效果取决于两个属性:fillStyle
和strokeStyle
。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
context.fillStyle = "#000";
context.strokeStyle = "red";
}
没有效果? 别急,这是因为我们只是设置了填充和描边而已,想要它生效,还需要绘制出来才能有效果。
与绘制矩形相关的方法有三个。它们都接收 4 个参数:矩形 x 坐标、矩形 y 坐标、矩形宽度和矩形高度。(单位是像素,但是传参时不需要传单位)
fillRect
strokeRect
clearRect
fillRect
:绘制并填充矩形fillRect
:以指定颜色在画布上绘制并填充矩形,填充色使用fillStyle
来设置。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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
来指定。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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
:擦除画布中某个区域,把擦除的区域变透明。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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
方法来描画路径。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// 创建路径
context.beginPath();
// 绘制圆弧,参数分别是圆心x坐标、圆形y坐标、圆弧半径、圆弧起始点(单位:弧度)、圆弧终点(单位:弧度)、绘制方向(false为顺时针绘制,true为逆时针绘制)
context.arc(100, 100, 99, 0, 2 * Math.PI, true);
// context.fillStyle = 'pink'
// context.fill()
context.strokeStyle = "pink";
context.stroke();
}
还可以调用clip
方法创建一个新的剪切区域。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// 创建路径
context.beginPath();
// 绘制圆弧,参数分别是圆心x坐标、圆形y坐标、圆弧半径、圆弧起始点(单位:弧度)、圆弧终点(单位:弧度)、绘制方向(false为顺时针绘制,true为逆时针绘制)
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
没法画出结果。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// 创建路径
context.beginPath();
// context.moveTo(0, 0);
context.lineTo(100, 50);
context.lineTo(200, 0);
context.lineWidth = 8;
context.strokeStyle = "pink";
// 描画路径
context.stroke();
}
没有moveTo
:
有moveTo
:
上面的例子中,beginPath
并没有作用,也就是说上面的例子中,其实有没有beginPath
都一样。那么beginPath
有什么作用呢?
beginPath
表示下面绘制的图形是一个新的路径。具体看下实例。
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
设置位置
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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
就是beginPath
的配套。但是closePath
和beginPath
并不是配套的,它们的功能不一样。所以closePath
之后的路径也不是新的路径,只有beginPath
才行。
而closePath
的作用是将最近绘制的路径闭合,和之前有没有beginPath
无关。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// context.beginPath(); // 有无`beginPath`都没有影响
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
属性绘制文本const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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
属性设置文本的字体、对齐方式、基线。
示例:
context.font = "700 16px Arial";
textAlign
:
start
,那么 x 坐标就是文本的左侧坐标center
,那么 x 坐标就是文本的中心点坐标end
,那么 x 坐标就是文本的右侧坐标const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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)
:移动原点
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// 创建路径
context.beginPath();
// 绘制圆弧,参数分别是圆心x坐标、圆形y坐标、圆弧半径、圆弧起始点(单位:弧度)、圆弧终点(单位:弧度)、绘制方向(false为顺时针绘制,true为逆时针绘制)
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);
// 因为已经移动过原点了,所以这时候(0, 0)就是圆心
context.moveTo(0, 0);
context.lineTo(25, 30);
context.stroke();
}
上面的例子中,已经把很多变化都使用上了,如果想要了解具体例子可以注释掉其他部分。
save
方法可以保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。后续可以通过restore
方法,恢复上下文的设置和变换。save
和restore
的使用类似于栈,后进先出。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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不会变化<img src="./avatar.png" alt="">
<canvas id="mycanvas" width="200" height="200">haha</canvas>
通过drawImage
把 HTML 的 img 元素或另一个 canvas 元素绘制到当前画布中。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// 获取图像
const img = document.images[0];
// 在画布的坐标出绘制图像,此时图像和原来的图像一样大,指的是原文件的大小
// context.drawImage(img, 10, 10)
// 传入另外两个参数,设置绘制图像的宽高
context.drawImage(img, 10, 10, 100, 100);
}
只传3个参数,画到画布上的跟原来的图像一样大,但画布没那么大。所以会只有一部分。
传入五个参数,可以让设置图像的宽高,显示完整的图像。
上面的做法是需要html
中有img
元素才能执行的.实际上,我们也可以通过image
对象来实现。
即获取图像不再是通过document.images[0]
,而是
const img = new Image();
img.src = "./avatar.png";
另外,绘制图像应该在img
的load
事件回调中调用。
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 像素。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
if (mycanvas.getContext) {
const context = mycanvas.getContext("2d");
// 获取图像
const img = document.images[0];
// // 9个参数
context.drawImage(img, 0, 10, 300, 300, 100, 100, 40, 40);
}
操作的结果可以使用canvas.toDataURL()
方法获取。
再搭配下载图片的方式就能实现下载图片。(这里用的是a
标签方法)
const a = document.createElement("a");
a.href = mycanvas.toDataURL();
// 获取源图片的名字
a.download = img.src.split("/")[img.src.split("/").length - 1];
a.click();
设置好阴影有关的属性值,就能够自动为要绘制的形状或路径生成阴影
shadowOffsetX
:阴影相对于形状或路径的 x 坐标偏移。默认为 0shadowOffsetY
:阴影相对于形状或路径的 y 坐标偏移。默认为 0shadowBlur
:阴影的模糊量。默认值为 0,表示不模糊shadowColor
:阴影的颜色。默认为黑色const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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 颜色字符串。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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 坐标和半径。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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
来设置图案的位置和大小。
const mycanvas = document.getElementById("mycanvas");
// 确保浏览器支持canvas
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);
}