<canvas>
是一个可以使用脚本绘制图形的元素。它可以用于绘制图表、图片和制作简单的动画
基本用法
canvas 元素
<canvas id="tutorial" width="150" height="150"></canvas>
<canvas>
默认宽度为 300 像素,高度为 150 像素。它可以使用 CSS 来设置宽高,但是如果使用 CSS 设置的宽高和使用 width
、height
设置的宽高比例不一致,那么绘制出来的图像会出现扭曲,因此不建议使用 CSS 来为画布设置宽高
渲染上下文
<canvas>
元素创建了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。脚本需要找到渲染上下文才能在画布上绘制。<canvas>
元素有一个 getContext()
方法,它可以用来获取渲染上下文和绘画功能。getContext()
接受一个参数,即上下文的类型。
//获取画布元素
let canvas = document.getElementById('tutorial');
//获取2d渲染上下文
let ctx = canvas.getContext('2d');
绘制图形
栅格
<canvas>
元素默认被栅格覆盖,栅格中的一个单元对应一个像素点,栅格的起始点为左上角。所有元素都是根据起始点确定位置。
绘制矩形
<canvas>
只支持两种形式的图形绘制:矩形和路径(由点连成的线段)。所有其他类型的图形都是通过一条或多条路径组合而成的。
矩形有三种绘制方法:
-
fillRect(x,y,width,height)
:绘制一个填充矩形 -
strokeRect(x,y,width,height)
:绘制一个矩形的边框 -
clearRect(x,y,width,height)
:清除指定矩形区域,让清除部分完全透明
它们三个方法拥有相同的参数,x
和 y
指定了所绘制的矩形的左上角,width
和 height
设置了矩形宽高
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
}
上面代码绘制的图像如下:
绘制路径
路径是由点连成的线段。使用路径绘制图形的步骤
- 创建路径的起始点
- 使用画图命令画出路径
- 把路径封闭
- 使用描边或填充路径区域来渲染图形
以下是所要用到的函数:
-
beginPath()
:清空子路径列表开始一个新的路径 -
closePath()
:是画笔返回当前子路径的起始点,它尝试从当前点到起始点绘制一条直线。如果子路径已经是封闭的或者自由一个点,那么方法不做任何操作 -
stroke()
:使用当前的样式描边子路径 -
fill()
:使用当前的样式填充子路径
下面的代码示例绘制了一个实心三角形:
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();
}
}
结果如下:
调用
fill()
函数,所有没有闭合的路径都会自动闭合,因此closePath()
不是必须的,因为当路径闭合时,它什么都不会做。
更多的绘制路径的方法请参考 CanvasRenderingContext2D - Web API 接口参考 | MDN (mozilla.org)
Path2D 对象
Path2D对象用来记录绘画命令,这样可以快速地回顾路径。它可以使用一个路径或者一个包含 SVG path 数据的字符串作变量
new Path2D(); // 空的 Path 对象
new Path2D(path); // 克隆 Path 对象
new Path2D(d); // 从 SVG 建立 Path 对象
下面这个例子,我们创建了一个矩形和一个圆形,将它们存到 Path2D 对象中,然后使用 stroke()
和 fill()
将对象画到画布中
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
//矩形
var rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
//圆形
var circle = new Path2D();
circle.moveTo(125, 35);
circle.arc(100, 35, 25, 0, 2 * Math.PI);
//描边矩形
ctx.stroke(rectangle);
//填充圆形
ctx.fill(circle);
}
}
上面的代码绘制的结果如下:
使用样式和颜色
我们可以设置渲染上下文来设置图形的样式和颜色
色彩 Colors
-
fillStyle
:设置图形填充颜色 -
strokeStyle
:设置图形描边颜色
这两个属性的值都是 CSS 表示颜色值的字符串。
// 这些 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.strokeStyle= "rgb(255,165,0)";
ctx.strokeStyle= "rgba(255,165,0,1)";
透明度 Transparency
通过设置 globalAlpha
属性或使用具有透明度的颜色来绘制半透明图形
//设置globalAlpha属性
ctx.globalAlpha = 0.2;
// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";
线型 Line styles
属性 | 可能的值 | 描述 |
---|---|---|
lineWidth | 正数 | 线条粗细 |
lineCap | butt 、round 、square | 线条末端样式 |
lineJoin | bevel 、round 、miter | 线条与线条连接处的样式 |
miterLimit | 正数 | 限制两条线相交时交接处最大长度 |
setLineDash | 数组 | 设置虚线样式 |
lineDashOffset | float 精度的数字,默认为 0.0 | 设置虚线偏移量 |
线条是如何画出的
如图所示,在第一个图中,填充了 (2,1) 至 (5,5) 的矩形,整个区域的边界刚好落在像素边缘上,这样就可以得到的矩形有着清晰的边缘。
如果你想要绘制一条从 (3,1) 到 (3,5),宽度是 1.0 的线条,你会得到像第二幅图一样的结果,像素是显示的最小单位,没办法渲染半个,所以它会使用笔触一半的颜色填充(深蓝色和浅蓝色的部分),这样的线条是不准确的
要解决这个问题,你必须对路径施以更加精确的控制。已知粗 1.0 的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5,1) 到 (3.5,5) 的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0 的线条。
渐变 Gradients
-
createLinearGradient(x1, y1, x2, y2)
:接受 4 个参数,表示渐变的起点和终点 -
createRadialGradient(x1, y1, r1, x2, y2, r2)
:接受 6 个参数,x1
、y1
定义圆心位置,r1
设置半径 -
gradient.addColorStop(position,color)
:给渐变设置颜色-
position
参数是一个 0.0 到 1.0 之间的数值,表示颜色在CanvasGradient
对象内的位置。 -
color
参数是以一个有效的 CSS 颜色值
-
使用以上两个方法新建一个 CanvasGradient
对象,赋给图形的 fillStyle
或 strokeStyle
属性。然后用 gradient.addColorStop(position,color)
设置颜色。下面是两个渐变效果图。
图案样式 Patterns
createPattern(image,type)
方法接受两个参数,image
参数可以是一个 image 对象的引用,或者另一个 canvas 对象,type
参数必须是以下字符串值之一:repeat
、repeat-x
、repeat-y
、no-repeat
。
图案的应用和渐变类似,创建一个 CanvasPattern
对象,然后赋值给 fillStyle
或 strokeStyle
属性即可。
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
// 创建新 image 对象,用作图案
var img = new Image();
img.src = 'canvas_createpattern.png';
img.onload = function() {
// 创建图案
var ptrn = ctx.createPattern(img, 'repeat');
ctx.fillStyle = ptrn;
ctx.fillRect(0, 0, 150, 150);
}
}
上面代码绘制的结果如下:
使用 Image 对象的
onload
事件来确保设置图案前图像已经装载完毕,否者可能出现图案效果不对的情况
阴影 Shadows
属性 | 可能的值 | 描述 |
---|---|---|
shadowOffsetX | 浮点型数值 | 设定阴影在 x 轴的延伸距离,正值向右延伸,负值向左 |
shadowOffsetY | 浮点型数值 | 设定阴影在 y 轴的延伸距离,正值向下延伸,负值向上 |
shadowBlur | 浮点数值 | 设置阴影的模糊程度 |
shadowColor | 标准的 CSS 颜色值 | 设置阴影颜色效果,默认是全透明的黑色 |
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
ctx.font = "20px Times New Roman";
ctx.fillStyle = "Black";
ctx.fillText("Sample String", 5, 30);
}
上面的代码绘制的结果如下,一个带阴影效果的文字:
Canvas 填充规则
当我们用到 fill
、clip
、isPointinPath
时我们可以选择一个填充规则,该规则根据某处在路径的外面或者里面来决定该处是否被填充。它有两个可能的值:nonzero
、evenodd
nonzero 非零规则
计算顺时针逆时针数量,计算结果不是 0,就填充;计算结果是 0,就不填充。
比如我们在下图取一点 B,向任意方向发出一条射线,找到和射线发生交叉的路径(注意路径方向,这和偏转的顺逆有关):
- 发生逆时针的偏转(路径 3),记 -1
- 发生顺时针偏转(路径 2),记 +1
计算结果是 0,所以不填充。
evenodd 奇偶规则
计算交叉路径数量,计算结果是奇数,就填充;计算结果是偶数,就不填充。
比如我们在下图取一点 A,向任意方向发送一条射线,找到和射线发生交叉的路径,计算结果是偶数,所以不填充。
本节内容参考 Canvas 保姆级教程(上):绘制篇 - 掘金 (juejin.cn)
绘制文本
canvas 提供了两种方法来渲染文本:
fillText(text, x, y [, maxWidth])
:在指定位置填充指定文本,绘制的最大宽度可选
strokeText(text, x, y [, maxWidth])
:在指定位置绘制文本边框,绘制的最大宽度可选
设置文本样式
属性 | 可能的值 | 描述 |
---|---|---|
font | 和 CSS 相同的语法 | 设置字体样式 |
textAlign | start (默认值)、end 、left 、right 、center | 文本对齐选项 |
textBaseline | top 、hanging 、middle 、alphabetic (默认值)、ideographic 、bottom | 基线对齐选项 |
direction | ltr 、rtl 、inherit (默认值) | 文本方向 |
预测量文本宽度
可以使用 measureText()
方法返回一个 Textetrice
对象,这个对象包含文本的宽度、所在像素,这些体现文本特性的属性。
下面这段代码测量文本获取了它的宽度:
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var text = ctx.measureText("foo"); // TextMetrics object
text.width; // 16;
}
使用图像
canvas 更有意思的意向特性就是图像操作能力。可以用于动态的图像合成或者作为图形的背景等。浏览器支持任意格式的外部图片使用。
获取需要绘制的图片
可以使用下面这些类型作为图片的源
-
HTMLImageElement
:图像元素 -
HTMLVideoElement
:视频元素 -
HTMLCanvasElement
:画布元素 -
ImageBitmap
:位图
绘制图片
获取了源图对象,我们就可以使用 drawImage()
方法将它渲染到 canvas 里,它有三种不同参数的实现
-
drawImage(image, dx, dy)
:常规方法 -
drawImage(image, dx, dy, dWidth, dHeight)
:缩放方法 -
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
:切片方法
根据上图来理解 drawImage()
方法的参数
参数 | 描述 |
---|---|
image | 图像源 |
sx | 矩形选择框左上角的 x 轴坐标 |
sy | 矩形选择框左上角的 y 轴坐标 |
sWidth | 切片时矩形选择框的宽度 |
sHeight | 切片时矩形选择框的高度 |
dx | image 在画布上的 x 轴坐标 |
dy | image 在画布上的 y 轴坐标 |
dWidth | image 在画布上的宽度 |
dHeight | image 在画布上的高度 |
下面是一个简单的示例:
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.moveTo(30,96);
ctx.lineTo(70,66);
ctx.lineTo(103,76);
ctx.lineTo(170,15);
ctx.stroke();
}
img.src = 'backdrop.png';
}
运行结果如下图:
控制图像的缩放行为
过度的缩放图像可能会导致图像模糊或者像素化。可以通过 imageSmoothingEnabled
属性来控制是否在缩放图像时使用平滑算法。默认值为 true
变形
变形可以改变原点位置,对网格进行旋转和缩放。
状态的保存和恢复
-
save()
:保存 canvas 状态 -
restore()
:恢复 canvas 状态
这两个方法和 word 的保存和撤销一样
移动
translate(x, y)
方法用来移动 canvas 和它的原点,它有两个参数:x
是左右偏移量,y
是上下偏移量,如下图所示:
旋转
rotate(angle)
方法用来以原点为中心旋转 canvas,angle
是以顺时针方向旋转的角度
缩放
scale(x,y)
方法的两个参数都是可以为负数的实数,x
为水平缩放因子,y
为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。默认值为 1,为实际大小。
画布初始情况下,是以左上角坐标为原点的第一象限。如果参数为负实数,相当于以 x 或 y 轴作为对称轴镜像反转
变形
变形是将平移、缩放、旋转等合为一体,使用矩阵来操作几何变化,变形针对的是画布本身。矩阵的概念可以看这一篇:简单教程。
transform(a, b, c, d, e, f)
将当前的变形矩阵乘以上一个矩阵
-
a
:水平方向的缩放 -
b
:竖直方向的倾斜偏移 -
c
:水平方向的倾斜偏移 -
d
:竖直方向的缩放 -
d
:水平方向的移动 -
f
:竖直方向的移动
参数 b
和 c
用于斜拉画布,详见 “Canvas 斜拉 - Canvas 基础教程”[^1]
下面是一个示例代码:
ctx.fillStyle="orange";
ctx.fillRect(50,50,100,50);
ctx.transform(1,1,0,1,0,0);
ctx.fillStyle="green";
ctx.fillRect(50,50,100,50);
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IOpZtqrO-1681779147762)(https://assets.b3logfile.com/siyuan/1622442485157/assets/image-20230417143247-79nqvwq.png)]
setTransform(a, b, c, d, e, f)
将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform
方法。
resetTransform()
重置当前变形为单位矩阵,它和 ctx.setTransform(1, 0, 0, 1, 0, 0)
等效。
组合与裁切
组合
globalCompositeOperation
提供了 12 种合成图形的方式:
属性值 | 描述 | 效果 |
---|---|---|
source-over | 默认设置,直接在现有内容上绘制新图形 | |
source-in | 只在重叠部分绘制新图形,其他部分是透明的 | |
source-out | 在不与现有内容重叠的地方绘制新图形 | |
source-atop | 新图形只在与现有内容重叠的地方绘制 | |
destination-over | 在现有内容底下绘制新图形 | |
destination-in | 只在重叠部分底下绘制新图形 | |
destination-out | 在不与现有内容重叠的地方保持现有内容 | |
destination-atop | 现有内容只保留重叠部分,新图形在现有 内容底下绘制 | |
lighter | 重叠部分的颜色通过颜色值相加来确定 | |
copy | 只显示新图形 | |
xor | 重叠和正常绘制之外的地方是透明的 | |
multiply | 将顶层像素和底层像素相乘,结果是重叠 部分更黑暗的图片 | |
screen | 像素被倒转,相乘,再倒转,结果是一幅 更明亮的图片 | |
overlay | multiply 和 screen 的结合,原本暗的地方 更暗,原本亮的地方更亮 | |
darken | 保留两个图层中最暗的像素 | |
lighten | 保留两个图层中最亮的像素 | |
color-dodge | 将底层除以顶层的反置 | |
color-burn | 将反置的底层除以顶层,然后将结果反过来 | |
hard-light | 屏幕相乘类似于叠加,但是上下图层互换了 | |
soft-light | 用顶层减去底层或反过来得到一个正值 | |
difference | 一个柔和版本的 hard-light | |
exclusion | 和 difference 相似,但对比度比较低 | |
hue | 保留了底层的亮度和色度,同时采用了顶层的 色调 | |
saturation | 保留了底层的亮度和色度,同时采用了顶层的 色度 | |
color | 保留了底层的亮度,同时采用了顶层的色调和 色度 | |
luminosity | 保持底层的色调和色度,同时采用顶层的亮度 | |
裁切
裁切的作用是用来隐藏不需要的部分。如下图显示,五角星外部的部分不会在画布上绘制出来
clip
是绘制图形的第三个方法,它的作用是将当前正在构建的路径转换为当前的裁切路径
参考链接
Canvas 教程 - Web API 接口参考 | MDN (mozilla.org)
CanvasRenderingContext2D - Web API 接口参考 | MDN (mozilla.org)
Canvas 保姆级教程(上):绘制篇 - 掘金 (juejin.cn)