基本画布功能
基本
设置width和height
使用getContext()方法,传入“2d”获取绘图上下文引用
使用时最好先测试一下getContext是否存在
示例
1
2
3
4
5
6
7
8
9
10//html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
//js
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 其他代码
}
toDataURL()方法导出canvas元素上的图像,这个方法接收一个参数:要生成的图像的MIME类型
示例
1
2
3
4
5
6
7
8
9
10
11let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
// 取得图像的数据 URI
let imgURI = drawing.toDataURL("image/png");
// 显示图片
let image = document.createElement("img");
image.src = imgURI;
document.body.appendChild(image);
}
浏览
绘图上下文
填充和描边
2d上下文有两个基本绘制操作:填充fillStyle和描边strokeStyle
示例
1
2
3
4
5
6
7let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
}所有与描边和填充相关的操作都会使用这两种样式,除非再次修改。
绘制矩形
矩形是一个唯一可以直接在2d绘图上下文中绘制的形状
与绘制矩形相关的三个方法,这些方法接收4个参数:矩形x坐标、矩形y坐标、矩形宽度、矩形高度
fillRect()绘制矩形内容区
1
2
3
4
5
6
7
8//...同上
//绘制红色矩形
context.fillStyle = "#ff0000"
context.fillRect(10,10,50,50)
//绘制半透明蓝色矩形
context.fillStyle = "rgba(0,0,255,0.5)"
context.fillRect(30, 30, 50, 50);strokeRect() 绘制矩形轮廓
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 绘制红色轮廓的矩形
context.strokeStyle = "#ff0000";
//绘制
context.strokeRect(10, 10, 50, 50);
// 绘制半透明蓝色轮廓的矩形
context.strokeStyle = "rgba(0,0,255,0.5)";
//描边宽度 值为任意整数值
context.lineWidth = 10
//控制线条端点的形状
context.lineCap = "butt" //"butt"平头 "round"出圆头 "square"出方头
//控制线条交点形状
context.lineJoin = "round" //"round"圆转 "bevel"取平 "miter" 出尖
//绘制
context.strokeRect(30, 30, 50, 50);
绘制路径
要绘制路径,必须首先调用 beginPath()方法以表示要开始绘制新路径。然后,再调用下列方法来绘制路径。
方法
arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标(x, y)为圆心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle(都是弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为顺时针)。
arcTo(x1, y1, x2, y2, radius):以给定半径 radius,经由(x1, y1)绘制一条从上一点到(x2, y2)的弧线。
bezierCurveTo(c1x, c1y, c2x, c2y, x, y):以(c1x, c1y)和(c2x, c2y)为控制点,绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。
lineTo(x, y):绘制一条从上一点到(x, y)的直线。
moveTo(x, y):不绘制线条,只把绘制光标移动到(x, y)。
quadraticCurveTo(cx, cy, x, y):以(cx, cy)为控制点,绘制一条从上一点到(x, y)
的弧线(二次贝塞尔曲线)。
- rect(x, y, width, height):以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法与 strokeRect()和 fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。创建路径之后,可以使用 closePath()方法绘制一条返回起点的线。如果路径已经完成,则既可以指定 fillStyle 属性并调用 fill()方法来填充路径,也可以指定 strokeStyle 属性并调用stroke()方法来描画路径,还可以调用 clip()方法基于已有路径创建一个新剪切区域。
示例,绘制一个不带数字的表盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 创建路径
context.beginPath();
// 绘制外圆
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 绘制内圆
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 绘制分针
context.moveTo(100, 100);
context.lineTo(100, 15);
// 绘制时针
context.moveTo(100, 100);
context.lineTo(35, 100);
// 描画路径
context.stroke();
}一个 isPointInPath()方法,接收 x 轴和 y 轴坐标作为参数。这个方法用于确定指定的点是否在路径
上,可以在关闭路径前随时调用
1
2
3if (context.isPointInPath(100, 100)) {
alert("Point (100, 100) is in the path.");
}
绘制文本
绘制文本有fillText()和strokeText()两个方法,接收4个参数:绘制的字符串、x坐标轴、y坐标轴和最大可选像素宽度,而且这两个方法最终绘制的结果都取决于以下3个属性,这些值都有默认值,不需要每次都指定
- font 以 CSS 语法指定的字体样式、大小、字体族等,比如”10px Arial”。
- textAlign 可能的值包括”start”、”end”、”left”、”right”和 “center”。
- textBaseLine 可能的值包括 “top” 、 “hanging” 、 “middle” 、”alphabetic”、”ideographic”和”bottom”
示例
1
2
3
4context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);辅助确定文本大小的measureText()方法,该方法接收一个参数,即要绘制的文本,然后返回一个TextMetrics对象,这个返回对象只有一个width属性,measureText()方法使用font、textAlign和textBaseline属性当前的值计算绘制指定文本大小
假设要把文本”Hello world!”放到一个 140 像素宽的矩形中,可以使用以下代码,
从 100 像素的字体大小开始计算,不断递减,直到文本大小合适:
1
2
3
4
5
6
7let fontSize = 100
context.font = fontSize + "px Arial"
while(context.measureText('Hello world!').width > 140){
fontSize--
context.font = fontSize + "px Arial"
}
context.fillText("Hello world!",10,10)fillText()和 strokeText()方法还有第四个参数,即文本的最大宽度。这个参数是可选的(Firefox 4 是第一个实现它的浏览器),如果调用 fillText()和 strokeText()时提供了此参数,但要绘制的字符串超出了最大宽度限制,则文本会以正确的字符高度绘制,这时字符会被水平压缩,以达到限定宽度
变换
以下方法可用于改变绘制上下文的变换矩阵。
rotate(angle):围绕原点把图像旋转 angle 弧度。
scale(scaleX, scaleY):通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY 来缩放图像。scaleX和 scaleY 的默认值都是 1.0。
translate(x, y):把原点移动到(x, y)。执行这个操作后,坐标(0, 0)就会变成(x, y)。
transform(m1_1, m1_2, m2_1, m2_2, dx, dy):像下面这样通过矩阵乘法直接修改矩阵。
m1_1 m1_2 dx
m2_1 m2_2 dy
0 0 1
setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):把矩阵重置为默认值,再以传入的参数调用 transform()。
示例,在前面绘制表盘的例子中,如果把坐标原点移动到表盘中心,那再绘制表针就非常简单了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 创建路径
context.beginPath();
// 绘制外圆
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 绘制内圆
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 移动原点到表盘中心
context.translate(100, 100);
// 绘制分针
context.moveTo(0, 0);
context.lineTo(0, -85);
// 绘制时针
context.moveTo(0, 0);
context.lineTo(-65, 0);
// 描画路径
context.stroke();
}把原点移动到(100, 100),也就是表盘的中心后,要绘制表针只需简单的数学计算即可。这是因为所有计算都是基于(0, 0),而不是(100, 100)了。当然,也可以使用 rotate()方法来转动表针:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 创建路径
context.beginPath();
// 绘制外圆
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 绘制内圆
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 移动原点到表盘中心
context.translate(100, 100);
// 旋转表针
context.rotate(1);
// 绘制分针
context.moveTo(0, 0);
context.lineTo(0, -85);
// 绘制时针
context.moveTo(0, 0);
context.lineTo(-65, 0);
// 描画路径
context.stroke();
}save() 可以保留变换,调用这个方法后,所有的这一时刻的设置会被放到一个暂存栈中,保存之后,可以继续修改上下文,而需要恢复之前的上下文时,可以调用restore()方法,从暂存栈中取出并恢复之前保存的设置,多次调用 save()方法可以在暂存栈中存储多套设置,然后通过 restore()可以系统地恢复。
1
2
3
4
5
6
7
8
9
10
11context.fillStyle = "#ff0000";
context.save();
context.fillStyle = "#00ff00";
context.translate(100, 100);
context.save();
context.fillStyle = "#0000ff";
context.fillRect(0, 0, 100, 200); // 在(100, 100)绘制蓝色矩形
context.restore();
context.fillRect(10, 10, 100, 200); // 在(100, 100)绘制绿色矩形
context.restore();
context.fillRect(0, 0, 100, 200); // 在(0, 0)绘制红色矩形注意,save()方法只保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。
绘制图像
使用drawImage()方法可以把现有的图像绘制到画布上,传入3个参数:要绘制的图像、绘制目标的x坐标,y坐标
//最简单的调用是传入一个HTML的img元素 let image = document.images[0]; context.drawImage(image, 10, 10); <!--12-->
还可以只把图像绘制到上下文中的一个区域。此时,需要给 drawImage()提供 9 个参数:要绘制的图像、源图像 x 坐标、源图像 y 坐标、源图像宽度、源图像高度、目标区域 x 坐标、目标区域 y 坐标、
目标区域宽度和目标区域高度。
1
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
第一个参数除了可以是 HTML 的
img
元素,还可以是另一个canvas
元素,这样就会把另一个画布的内容绘制到当前画布上操作的结果可以使用toDataURL()方法获取。不过有一种情况例外:如果绘制的图像来自其他域而非当前页面,则不能获取其数据。此时,调用 toDataURL()将抛出错误。比如,如果来自 www.example.com 的页面上绘制的是来自 www.wrox.com 的图像,则上下文就是“脏的”,获取数据时会抛出错误。
阴影
2D 上下文可以根据以下属性的值自动为已有形状或路径生成阴影。
shadowColor:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。
shadowOffsetX:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。
shadowOffsetY:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。
shadowBlur:像素,表示阴影的模糊量。默认值为 0,表示不模糊。
1
2
3
4
5
6
7
8
9
10
11
12let context = drawing.getContext("2d");
// 设置阴影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
渐变
基础渐变
渐变通过 CanvasGradient 的实例表示,在 2D 上下文中创建和修改都非常简单。要创建一个新的线性渐变,可以调用上下文的 createLinearGradient()方法。这个方法接收 4 个参数:起点 x 坐标、起点 y 坐标、终点 x 坐标和终点 y 坐标。调用之后,该方法会以指定大小创建一个新的 CanvasGradient
有了 gradient 对象后,接下来要使用 addColorStop()方法为渐变指定色标。这个方法接收两
个参数:色标位置和 CSS 颜色字符串。色标位置通过 0~1 范围内的值表示,0 是第一种颜色,1 是最后
一种颜色
示例
1
2
3
4一种颜色。比如:
let gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");这个 gradient 对象现在表示的就是在画布上从(30, 30)到(70, 70)绘制一个渐变。渐变的起点颜色为白色,终点颜色为黑色。可以把这个对象赋给 fillStyle 或 strokeStyle 属性,从而以渐变填充或描画绘制的图形:对象并返回实例。
1
2
3
4
5
6// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);保持渐变与形状的一致非常重要,有时候可能需要写个函数计算相应的坐标
1
2
3function createRectLinearGradient(context, x, y, width, height) {
return context.createLinearGradient(x, y, x+width, y+height);
}这个函数会基于起点的 x、y 坐标和传入的宽度、高度创建渐变对象,之后调用 fillRect()方法时可以使用相同的值:
1
2
3
4
5
6let gradient = createRectLinearGradient(context, 30, 30, 50, 50);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
径向渐变
径向渐变(或放射性渐变)要使用 createRadialGradient()方法来创建。这个方法接收 6 个参数,分别对应两个圆形圆心的坐标和半径。前 3 个参数指定起点圆形中心的 x、y 坐标和半径,后 3 个参数指定终点圆形中心的 x、y 坐标和半径。
1
2
3
4
5
6
7
8let gradient = context.createRadialgradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
图案
图案是用于填充和描画图形的重复图像。要创建新图案。可以调用 createPattern()方法并传入两个参数:一个 HTML 元素和一个表示该如何重复图像的字符串。第二个参数的值与 CSS 的background-repeat 属性是一样的,包括”repeat”、”repeat-x”、”repeat-y”和”no-repeat”。
1
2
3
4
5
6let image = document.images[0],
//传给 createPattern()方法的第一个参数也可以是<video>元素或者另一个<canvas>元素。
pattern = context.createPattern(image, "repeat");
// 绘制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);
图像数据
2D 上下文中比较强大的一种能力是可以使用 getImageData()方法获取原始图像数据。
1
2
3
4
5
6
7let imageData = context.getImageData(10, 5, 50, 50);
//其中,data 属性是包含图像的原始像素信息的数组。每个像素在 data 数组中都由 4 个值表示,分别代表红、绿、蓝和透明度值。换句话说,第一个像素的信息包含在第 0 到第 3 个值中,比如:
let data = imageData.data,
red = data[0],
green = data[1],
blue = data[2],
alpha = data[3];这个数组中的每个值都在 0~255 范围内(包括 0 和 255)。对原始图像数据进行访问可以更灵活地操作图像。例如,通过更改图像数据可以创建一个简单的灰阶过滤器:
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
29let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d"),
image = document.images[0],
imageData, data,
i, len, average,
red, green, blue, alpha;
// 绘制图像
context.drawImage(image, 0, 0);
// 取得图像数据
imageData = context.getImageData(0, 0, image.width, image.height);
data = imageData.data;
for (i=0, len=data.length; i < len; i+=4) {
red = data[i];
green = data[i+1];
blue = data[i+2];
alpha = data[i+3];
// 取得 RGB 平均值
average = Math.floor((red + green + blue) / 3);
// 设置颜色,不管透明度
data[i] = average;
data[i+1] = average;
data[i+2] = average;
}
// 将修改后的数据写回 ImageData 并应用到画布上显示出来
imageData.data = data;
context.putImageData(imageData, 0, 0);
}这个例子首先在画布上绘制了一个图像,然后又取得了其图像数据。for 循环遍历了图像数据中的每个像素,注意每次循环都要给 i 加上 4。每次循环中取得红、绿、蓝的颜色值,计算出它们的平均值。然后再把原来的值修改为这个平均值,实际上相当于过滤掉了颜色信息,只留下类似亮度的灰度信息。之后将 data 数组重写回 imageData 对象。最后调用 putImageData()方法,把图像数据再绘制到画布上。结果就得到了原始图像的黑白版。
注意:只有在画布没有加载跨域内容时才可以获取图像数据。如果画布上绘制的是跨域内
容,则尝试获取图像数据会导致 JavaScript 报错。
合成
globalAlpha 属性是一个范围在 0~1 的值(包括 0 和 1),用于指定所有绘制内容的透明度,默认值为 0。
1
2
3
4
5
6
7
8
9
10// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 修改全局透明度
context.globalAlpha = 0.5;
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
// 重置
context.globalAlpha = 0;globalCompositionOperation 属性表示新绘制的形状如何与上下文中已有的形状融合。
source-over:默认值,新图形绘制在原有图形上面。
source-in:新图形只绘制出与原有图形重叠的部分,画布上其余部分全部透明。
source-out:新图形只绘制出不与原有图形重叠的部分,画布上其余部分全部透明。
source-atop:新图形只绘制出与原有图形重叠的部分,原有图形不受影响。
destination-over:新图形绘制在原有图形下面,重叠部分只有原图形透明像素下的部分可见。
destination-in:新图形绘制在原有图形下面,画布上只剩下二者重叠的部分,其余部分完全透明。
destination-out:新图形与原有图形重叠的部分完全透明,原图形其余部分不受影响。
destination-atop:新图形绘制在原有图形下面,原有图形与新图形不重叠的部分完全透明。
lighter:新图形与原有图形重叠部分的像素值相加,使该部分变亮。
copy:新图形将擦除并完全取代原有图形。
xor:新图形与原有图形重叠部分的像素执行“异或”计算。