本章内容
- 理解
<canvas>
元素 - 绘制简单的 2D 图形
- 使用 WebGL 绘制 3D 图形
这个元素负责在页面中设定一个区域,然后就可以通过 JavaScript 动态地在这个区域中绘制图形。
与浏览器环境中的其他组件类似,<canvas>
由几组 API 构成,但并非所有浏览器都支持。除了具备基本绘图能力的 2D 上下文,<canvas>
还建议了一个名为 WebGL 的 3D 上下文。
15.1 基本用法
要使用<canvas>
元素,必须先设置其width
和height
属性,指定可以绘图的区域大小。出现在开始和结束标签中的内容是后备信息,如果浏览器不支持<canvas>
元素,就会显示这些信息。如下例。
<canvas id="drawing" width="200" height="200">A drawing of something</canvas>
如果不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的。
要在这块画布上绘图,需要取得绘图上下文。使用getContext()
方法获得绘图上下文对象的引用。
var drawing = document.getElementById("drawing");
// 确定浏览器支持<canvas>元素
if (drawing.getContext) {
var context = drawing.getContext("2d");
//更多代码
}
使用toDataURL()
方法,可以导出在<canvas>
元素上绘制的图像。这个方法接受一个参数,即图像的 MIME 类型格式,而且适合用于创建图像的任何上下文。比如,要取得画布中的一幅 PNG 格式的图像,可以使用如下代码。
var drawing = document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if (drawing.getContext) {
//取得图像的数据 URI
var imgURI = drawing.toDataURL("image/png");
//显示图像
var image = document.createElement("img");
image.src = imgURI;
document.body.appendChild(image);
}
默认情况下,浏览器会将图像编码为 PNG 格式。
如果绘制到画布上的图像源自不同的域,
toDataURL()
方法会抛出错误。
15.2 2D 上下文
使用 2D 绘图上下文提供的方法,可以绘制简单的 2D 图形,比如矩形、弧线和路径。2D 上下文的坐标开始于<canvas>
元素的左下角,原点坐标是(0,0)。默认情况下,width
和height
表示水平和垂直两个方向上可用的像素数目。
15.2.1 填充和描边
两种基本操作是填充和描边。大多数 2D 上下文操作都会细分为填充和描边两个操作,而操作的结果取决于两个属性:fillstyle
和strokeStyle
。
这两个属性的值可以是字符串、渐变对象或模式对象,而且它们的默认值都是“#000000
”。如果为它们指定表示颜色的字符串值,可以使用 CSS 中指定颜色值得任何格式,包括颜色名、十六进制码、rgb、rgba、hsl 或 hsla。举例:
var drawing = document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if (drawing.getContext) {
var context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
}
这样以后,所有涉及描边和填充的操作都将使用这两个样式,直至重新设置这两个值。这两个属性的值也可以是渐变对象或模式对象。
15.2.2 绘制矩形
矩形是唯一一种可以直接在 2D 上下文中绘制的形状。与矩形有关的方法包括fillRect()
、strokeRect()
和clearRect()
。这三个方法都能接收 4 个参数: 矩形的 x 坐标、矩形的 y 坐标、矩形宽度和矩形高度。这些参数的单位都是像素。
首先,fillRect()
方法在画布上绘制的矩形会填充指定的颜色。填充的颜色通过fillStyle
属性指定。
var drawing = document.getElementById("drawing");
if (drawing.getContext) {
var context = drawing.getContext("2d");
//绘制红色矩形
context.fillStyle = "#ff0000";
cotext.fillRect(10, 10, 50, 50);
//绘制半透明的蓝色矩形
context.fillStyle = "rgba(0, 0, 255, 0.5)";
context.fillRect(30, 30, 50, 50);
}
同样地,strokeRect()
方法在画布上绘制的矩形会使用指定的颜色描边。
描边线条的宽度由
lineWidth
属性控制,该属性的值可以是任意整数。另外,通过lineCap
属性可以控制线条末端的形状是平头、圆头还是方头("butt"
、"round"
或"square"
),通过lineJoin
属性可以控制线条相交的方式是圆交、斜交还是斜接("round"
、"bevel"
、"miter"
)。
最后,clearRect()
方法用于清除画布上的矩形区域。本质上,这个方法可以把绘制上下文中的某一矩形区域变透明。
15.2.3 绘制路径
2D 绘制上下文支持很多在画布上绘制路径的方法。通过路径可以创造出复杂的形状和线条。要绘制路径,首先必须调用beginPath()
方法,表示要开始绘制新路径。然后,再通过下列方法来实际地绘制路径。
-
arc(x, y, radius, startAngle, endAngle, counterclockwise)
:以(x,y)
为圆心绘制一条弧线,弧线半径为radius
,起始和结束角度(用弧度表示)分别为startAngle
和endAngle
。最后一个参数表示startAngle
和andAngle
是否按逆时针方向计算,值为false
表示按顺时针方向计算。 -
arcTo(x1, y1, x2, y2, radius)
:从上一点开始绘制一条弧线,到(x2, y2)
为止,并且以给定的半径radius
穿过(x1, y1)
。 -
bezierCurveTo(c1x, c1y, c2x, c2y, x, y)
:从上一点开始绘制一条曲线,到(x, y)
为止,并且以(c1x, c1y)
和(c2x, c2y)
为控制点。 -
lineTo(x, y)
:从上一点开始绘制一条直线,到(x,y)
为止。 -
moveTo(x,y)
:将绘图游标移动到(x,y)
,不画线。 -
quadraticCurveTo(cx, cy, x, y)
:从上一点开始绘制一条二次曲线,到(x, y)
为止,并且以(cx, cy)
作为作为控制点。 -
rect(x, y, width, height)
:从点(x, y)
开始绘制一个矩形,宽度和高度分别由width
和height
指定。这个方法绘制的是矩形路径,而不是strokeRect()
和fillRect()
所绘制的独立的形状。
创建了路径后,接下来有几种可能的选择。如果想绘制一条连接到路径起点的线条,可以调用closePath()
。如果路径已经完成,你想用fillStyle
填充它,可以调用fill()
方法。另外,还可以调用stroke()
方法对路径描边,描边使用的是strokeStyle
。最后还可以调用clip()
,这个方法可以在路径上创建一个剪切区域。
看一个绘制不带数字的时钟表盘。
var drawing = document.getElementById("drawing");
//确定浏览器支持`<canvas>`元素
if (drawing.getContext) {
var 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();
}
在 2D 绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制。由于路径的频繁使用,有了isPointInPath()
的方法。这个方法接收 x 和 y 坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径上,例如:
if (context.isPointInPath(100, 100)) {
alert('Point (100, 100) is in the path.');
}
2D 上下文的路径 API 已经非常稳定,可以利用它们结合不同的填充和描边样式,绘制出非常复杂的图形来。
15.2.4 绘制文本
绘制文本主要有两个方法:fillText()
和strokeText()
。这两个方法都可以接收 4 个参数:要绘制的文本字符串、x 坐标、y 坐标和可选的最大像素宽度。第四个参数存在兼容性问题。而且,这两个方法都以下列 3 个属性为基础。
-
font
: 表示文本样式、大小及字体,用 CSS 中指定字体的格式来指定。 -
textAlign
:表示文本对齐方式。建议使用“start
”和“end
”,不要使用“left
”和”right
“,因为前两者的意思更稳妥,能同时适合从左到右和从右到左显示(阅读)的语言。 -
textBaseline
:表示文本的基线。
这几个属性都有默认值,因此没有必要每次使用它们都重新设置一遍值。fillText()
方法使用fillStyle
属性绘制文本,而strokeText()
方法使用strokeStyle
属性为文本描边。相对来说,还是使用fillText()
的时候更多,因为该方法模仿了在网页中正常显示文本。例如,下面的代码在前一节创建的表盘上方绘制了数字 12:
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
由于绘制文本比较复杂,特别是需要把文本控制在某一区域中的时候, 2D 上下文提供了辅助确定文本大小的方法measureText()
。这个方法接收一个参数,即要绘制的文本;返回一个TextMetrics
对象。返回的对象目前只有一个width
属性,但将来还会增加更多度量属性。
measureText()
方法利用font、textAlign
和textBaseline
的当前值计算指定文本的大小。假设你想在一个 140 像素的矩形区域中绘制文本 Hello world!,下面的代码从 100 像素的字体大小开始递减,最终会找到合适的字体大小。
var fontSize = 100;
content.font = fontSize + "px Arial";
while (context.measureText("Hello world!").width > 140) {
fontSize --;
context.font = fontSize + "px Arial";
}
context.fillText("Hello world!", 10, 10);
context.fillText("Font size is" + fontSize + "px", 10, 50);
绘制文本还是相对比较复杂的操作,因此支持<canvas>
元素的浏览器也并未完全实现所有与绘制文本相关的 API。
15.2.5 变换
通过上下文的变换,可以把处理后的图像绘制到画布上。2D 绘制上下文支持各种基本的绘制变换。创建绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵,所有处理都按描述直接绘制。为绘制上下文应用变换,会导致使用不同的变换矩阵应用处理,从而产生不同的结果。
可以通过如下方法来修改变换矩阵。
-
rotate(angle)
:围绕原点旋转图像angle
弧度。 -
scale(scaleX, scaleY)
:缩放图像,在 x 方向乘以scaleX
,在 y 方向乘以scaleY
。默认值都是 1.0。 -
traslate(x, y)
:将坐标原点移动到(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()
。如果把前面的例子中的原点变换到表盘的中心,然后再绘制表针就容易多了。
var drawing = document.getElementById("drawing");
//确定浏览器支持`<canvas>`元素
if(drawing.getContext) {
var content = 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();
}
可以连续使用save()
和restore()
方法跟踪上下文的状态变化,save()
方法会使当时的设置都会进入一个栈结构,restore()
方法,会在保存设置的栈结构中向前返回一级,恢复之前的状态。
需要注意的是,save()
方法保存的只是对绘图上下文的设置和变换,不会保存绘图上下文的内容。
15.2.6 绘制图像
2D 绘图上下文内置了对图像的支持。如果你想把一幅图像绘制到画布上,可以使用drawImage()
方法。根据期望的最终结果不同,调用这个方法时,可以使用三种不同的参数组合。最简单的调用方式是传入一个HTML<img>
元素,以及绘制该图像的起点的 x 和 y 坐标。例如:
var image = document.images[0];
context.drawImage(image, 10, 10);
多传两个参数可以分别表示目标宽度和目标高度。例如:
context.drawImage(image, 50, 10, 20, 30);
还可以选择把图像中的某个区域绘制到上下文中。drawImage()
方法的这种调用方式总共需要传入 9 个参数:要绘制的图像、源图像的x坐标、源图像的y坐标、源图像的宽度、源图像的高度、目标图像的x坐标、目标图像的y坐标、目标图像的宽度、目标图像的高度。
除了给drawImage()
方法传入 HTML<img>
元素外,还可以传入另一个<canvas>
元素作为其第一个参数。这样,就可以把另一个画布内容绘制到当前画布上。
15.2.7 阴影
2D 上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。
-
shadowColor
:用 CSS 颜色格式表示的阴影颜色,默认为黑色。 -
shadowOffsetX
: 形状或路径x轴方向的阴影偏移量,默认为0。
*shadowOffsetY
:形状或路径y轴方向的阴影偏移量,默认为0. -
shadowBlur
:模糊的像素数,默认 0,即不模糊。
这些属性都可以通过context
对象来修改。只要在绘制前为它们设置适当的值,就能自动产生阴影。例如:
var 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);
俩矩形的阴影样式相同。
15.2.8 渐变
渐变由CanvasGradient
实例表示,很容易通过2D上下文你来创建和修改。要创建一个新的线性渐变,可以调用createLinearGradient()
方法。这个方法接收 4 个参数:起点的x坐标、起点的y坐标、终点的x坐标、终点的y坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回CanvasGradient
对象的实例。
创建了渐变对象后,下一步就是使用addColorStop()
方法来指定色标。这个方法接收两个参数:色标位置和 CSS 颜色值。色标位置是一个0(开始的颜色)到1(结束的颜色)之间的数字。例如:
var gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
此时,gradient
对象表示的是一个从画布上点(30,30)到点(70,70)的渐变。起点的色标是白色,终点的色标是黑色。然后就可以把fillStyle
或strokeStyle
设置为这个对象,从而使用渐变来绘制形状或描边:
//绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
//绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
要创建径向渐变(或放射渐变),可以使用createRadialGradient()
方法。这个方法接收6个参数,对应着两个圆的圆心和半径。前三个参数指定的是起点圆的圆心和半径,后三个参数指的是终点圆的圆心及半径。
如果想从某个形状的中心点开始创建一个向外扩散的径向渐变效果,就要将两个圆定义为同心圆。
15.2.9 模式
模式其实就是重复的图像,可以用来填充或描边图形。使用createPattern()
方法并传入两个参数:一个<img>
元素和一个表示如何重复图像的字符串。其中,第二个参数的值与 css 的background-repeat
属性值相同。例子:
var image = document.images[0];
var pattern = context.createPattern(image, "repeat");
//绘制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);
createPattern()
方法的第一个参数也可以是一个<video>
元素,或者另一个<canvas>
元素。
15.2.10 使用图像数据
可以通过getImageData()
取得原始图像数据。这个方法接收 4 个参数:要取得其数据的画面区域的 x 和 y 坐标以及该区域的像素宽度和高度。
var imageData = context.getImageData(10, 5, 50, 50);
每个ImageData
对象都有三个属性:width
、height
和data
。其中data
属性是一个数组,保存着图像中每一个像素的数据。每一个像素用 4 个元素来保存,分别表示红、绿、蓝、和透明度值。例如:
var data = imageData.data;
var red = data[0];
green = data[1];
blue = data[2];
alpha = data[3];
例如,通过修改图像数据,可以像下面这样创建一个煎蛋的灰阶过滤器。
var drawing = document.getElementById('drawing');
//确定浏览器支持<canvas>元素
if (drawing.getContext) {
var context = drawing.getContext('2d');
var image = document.images[0];
var 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.data = data;
context.putImageData(imageData, 0 ,0);
}
15.2.11 合成
还有两个会应用到 2D 上下文所有绘制操作的属性:globalAlpha
和globalCompositionOperation
。其中,globalAlpha
是一个介于 0 和 1 之间的值(包括 0 和 1),用于指定所有绘制的透明度。默认值为 0。如果后续操作都要基于相同的透明度,就可以先把globalAlpha
设置为适当值,然后绘制,最后再把它设置回默认值 0 。
第二个属性globalCompositionOperation
表示后绘制的图形怎样与先绘制的图形结合。这个属性的值是字符串。
15.3 WebGL
WebGL 是针对 Canvas 的 3D 上下文。
15.3.1 类型化数组
类型化数组元素被设定为特定类型的值。核心是一个名为ArrayBuffer
的类型。每个ArrayBuffer
对象表示的只是内存中指定的字节数,但不会指定这些字节用于保存什么类型的数据。
- 视图
- 类型化视图
15.3.2 WebGL 上下文
目前,在支持的浏览器中,WebGL 的名字叫“experimental-webgl”
。
var drawing = document.getElementById('drawing');
//确定浏览器支持
if (drawing.context) {
var gl = drawing.getContext("experimental-webgl");
if (gl) {
//使用 WebGl
}
}
- 常量
- 方法命名
- 准备绘图
- 视口与坐标
- 缓冲区
- 错误
- 着色器
- 编写着色器
- 编写着色器程序
- 为着色器传入值
- 调试着色器和程序
- 绘图
- 纹理
- 读取像素
15.3.3 支持
目前只适合实验性地学习
15.4 小结
HTML5 的<canvas>
元素提供了一组 JavaScript API,让我们可以动态地创建图形和图像。图形是在一个特定的上下文中创建的,而上下文对象目前有两种。一种是 2D 上下文,可以执行原始的绘图操作,比如:
- 设置填充、描边颜色和模式
- 绘制矩形
- 绘制路径
- 绘制文本
- 创建渐变和模式
第二种是 3D 上下文,即 WebGL 上下文,支持比 2D 上下文更丰富和更强大的图形图像处理能力。比如:
- 用 GLSL 编写的顶点和片段着色器
- 支持类型化数组,即能够将数组中的数据限定为某种特定的数值类型
- 创建和操作纹理