不用说,HTML5 添加的最受欢迎的功能就是canvas元素。这个元素负责在页面中设定一个区域,然后就可以通过 JavaScript 动态地在这个区域中绘制图形。
IE9+、Firefox 1.5+、Safari 2+、Opera 9+、Chrome、iOS 版 Safari 以及 Android 版 WebKit 都在某种程度上支持canvas。
与浏览器环境中的其他组件类似,canvas由几组 API 构成,但并非所有浏览器都支持所有这些 API。
除了具备基本绘图能力的 2D 上下文,canvas还建议了一个名为 WebGL 的 3D 上下文。目前, 支持该元素的浏览器都支持 2D 上下文及文本 API,但对 WebGL 的支持还不够好。
由于 WebGL 还是实验性的,因此要得到所有浏览器支持还需要很长一段时间。
Firefox 4+和 Chrome 支持 WebGL 规范的早期版本,但一些老版本的操作系统,比如 Windows XP,由于缺少必要的绘图驱动程序,即便安装了这两款浏览器也无济于事。
基本用法
要使用canvas元素,必须先设置其 width 和 height 属性,指定可以绘图的区域大小。出现在开始和结束标签中的内容是后备信息,如果浏览器不支持canvas元素,就会显示这些信息。下面就是canvas元素的例子:
<canvas id="drawing" width=" 200" height="200">A drawing of something.</canvas>
与其他元素一样,canvas元素对应的 DOM 元素对象也有 width 和 height 属性,可以随意修改。而且,也能通过 CSS 为该元素添加样式,如果不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的。
要在这块画布(canvas)上绘图,需要取得绘图上下文。而取得绘图上下文对象的引用,需要调用 getContext() 方法并传入上下文的名字。传入”2d”,就可以取得 2D 上下文对象。
var drawing = document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
//更多代码
}
在使用canvas元素之前,首先要检测 getContext()方法是否存在,这一步非常重要。
有些浏览器会为 HTML 规范之外的元素创建默认的 HTML 元素对象,在这种情况下,即使 drawing 变量中保存着一个有效的元素引用,也检测不到 getContext() 方法。
使用 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");
document.body.appendChild(image);
}
默认情况下,浏览器会将图像编码为 PNG 格式(除非另行指定)。
Firefox 和 Opera 也支持基于 “image/jpeg”参数的 JPEG 编码格式。由于这个方法是后来才追加的,所以支持canvas的浏览器也是在较新的版本中才加入了对它的支持,比如 IE9、Firefox 3.5 和 Opera 10。
2D 上下文
使用 2D 绘图上下文提供的方法,可以绘制简单的 2D 图形,比如矩形、弧线和路径。
2D 上下文的 坐标开始于canvas元素的左上角,原点坐标是(0, 0)。所有坐标值都基于这个原点计算,x 值越大表示越靠右,y 值越大表示越靠下。默认情况下,width 和 height 表示水平和垂直两个方向上可用的像素数目。
1、填充和描边
2D 上下文的两种基本绘图操作是填充和描边。填充,就是用指定的样式(颜色、渐变或图像)填充图形;描边,就是只在图形的边缘画线。
大多数 2D 上下文操作都会细分为填充和描边两个操作,而操作的结果取决于两个属性:fillStyle 和 strokeStyle。
这两个属性的值可以是字符串、渐变对象或模式对象,而且它们的默认值都是”#000000”。如果为它们指定表示颜色的字符串值,可以使用 CSS 中指定颜色值的任何格式。
var drawing = document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
}
以上代码将 strokeStyle 设置为 red(CSS 中的颜色名),将 fillStyle 设置为#0000ff(蓝色)。 然后,所有涉及描边和填充的操作都将使用这两个样式,直至重新设置这两个值。
如前所述,这两个属性的值也可以是渐变对象或模式对象。本章后面会讨论这两种对象。
2、绘制矩形
矩形是唯一一种可以直接在 2D 上下文中绘制的形状。
与矩形有关的方法包括 fillRect()、 strokeRect()和 clearRect()。
这三个方法都能接收 4 个参数:矩形的 x 坐标、矩形的 y 坐标、矩形宽度和矩形高度。这些参数的单位都是像素。
首先,fillRect() 方法在画布上绘制的矩形会填充指定的颜色。填充的颜色通过 fillStyle 属性指定,比如:
var context = drawing.getContext("2d");
//绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
//绘制半透明的蓝色矩形
context.fillStyle = "rgba(0,0,255,0.5)";
context.fillRect(30, 30, 50, 50);
strokeRect()方法在画布上绘制的矩形会使用指定的颜色描边。描边颜色通过 strokeStyle 属性指定。比如:
//绘制红色描边矩形
context.strokeStyle = "#ff0000";
context.strokeRect(10, 10, 50, 50);
//绘制半透明的蓝色描边矩形
context.strokeStyle = "rgba(0,0,255,0.5)";
context.strokeRect(30, 30, 50, 50);
最后,clearRect()方法用于清除画布上的矩形区域。
本质上,这个方法可以把绘制上下文中的某一矩形区域变透明。通过绘制形状然后再清除指定区域,就可以生成有意思的效果,例如把某个形状切掉一块。下面看一个例子。
//清除一个小矩形
context.clearRect(40, 40, 10, 10);
3、绘制路径
2D 绘制上下文支持很多在画布上绘制路径的方法。通过路径可以创造出复杂的形状和线条。
要绘制路径,首先必须调用 beginPath()方法,表示要开始绘制新路径。然后,再通过调用下列方法来实际地绘制路径:
arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x,y)为圆心绘制一条弧线,弧线半径为 radius,起始和结束角度(用弧度表示)分别为 startAngle 和 endAngle。最后一个参数表示 startAngle 和 endAngle 是否按逆时针方向计算,值为 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(),这个方法可以在路径上创建一个剪切区域。
下面看一个例子,即绘制一个不带数字的时钟表盘。
//开始路径
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);
//描边路径,调用 stroke()方法,这样才能把图形绘制到画布上
context.stroke();
在 2D 绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制。
由于路径的使用很频繁,所以就有了一个名为 isPointInPath()的方法。这个方法接收 x 和 y 坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径上,例如:
if (context.isPointInPath(100, 100)){
alert("Point (100, 100) is in the path.");
}
2D 上下文中的路径 API 已经非常稳定,可以利用它们结合不同的填充和描边样式,绘制出非常复杂的图形来。
4、绘制文本
文本与图形总是如影随形。
为此,2D 绘图上下文也提供了绘制文本的方法。绘制文本主要有两个方法:fillText()和 strokeText()。这两个方法都可以接收 4 个参数:要绘制的文本字符串、x 坐标、y 坐标和可选的最大像素宽度。而且,这两个方法都以下列 3 个属性为基础。
font:表示文本样式、大小及字体,用CSS中指定字体的格式来指定,例如"10px Arial"。
textAlign:表示文本对齐方式。可能的值有"start"、"end"、"left"、"right"和"center"。建议使用"start"和"end",不要使用"left"和"right",因为前两者的意思更稳妥,能同时适合从左到右和从右到左显示(阅读)的语言。
textBaseline:表示文本的基线。可能的值有"top"、"hanging"、"middle"、"alphabetic"、"ideographic"和"bottom"。
这几个属性都有默认值,因此没有必要每次使用它们都重新设置一遍值。
fillText()方法使用 fillStyle 属性绘制文本,而 strokeText() 方法使用 strokeStyle 属性为文本描边。相对来说,还是使用 fillText()的时候更多,因为该方法模仿了在网页中正常显示文本。例如,下面的代码在前一 节创建的表盘上方绘制了数字 12:
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
因为这里把 textAlign 设置为”center”,把 textBaseline 设置为”middle”,所以坐标 (100,20) 表示的是文本水平和垂直中点的坐标。如果将 textAlign 设置为”start”,则 x 坐标表示的是文本左端的位置(从左到右阅读的语言);设置为”end”,则 x 坐标表示的是文本右端的位置(从左到右阅读的 语言)。例如:
//起点对齐
context.textAlign = "start";
context.fillText("12", 100, 40);
//终点对齐
context.textAlign = "end";
context.fillText("12", 100, 60);
由于绘制文本比较复杂,特别是需要把文本控制在某一区域中的时候,2D 上下文提供了辅助确定文本大小的方法 measureText()。
这个方法接收一个参数,即要绘制的文本;返回一个 TextMetrics 对象。返回的对象目前只有一个 width 属性,但将来还会增加更多度量属性。
measureText()方法利用 font、textAlign 和 textBaseline 的当前值计算指定文本的大小。 比如,假设你想在一个 140 像素宽的矩形区域中绘制文本 Hello world!,下面的代码从 100 像素的字体大小开始递减,最终会找到合适的字体大小。
var 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);
context.fillText("Font size is " + fontSize + "px", 10, 50);
前面提到过,fillText 和 strokeText() 方法都可以接收第四个参数, 也就是文本的最大像素宽度。不过,这个可选的参数尚未得到所有浏览器支持 (最早支持它的是 Firefox 4)。提供这个参数后,调用 fillText() 或 strokeText() 时如果传入的字符串大于最大宽度,则绘制的文本字符的高度正确,但宽度会收缩以适应最大宽度。
5、变换
通过上下文的变换,可以把处理后的图像绘制到画布上。
2D 绘制上下文支持各种基本的绘制变换。 创建绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵下,所有处理都按描述直接绘制。 为绘制上下文应用变换,会导致使用不同的变换矩阵应用处理,从而产生不同的结果。
可以通过如下方法来修改变换矩阵。
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()。
//变换原点
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() 方法旋转时钟的表针。
无论是刚才执行的变换,还是 fillStyle、strokeStyle 等属性,都会在当前上下文中一直有效,除非再对上下文进行什么修改。虽然没有什么办法把上下文中的一切都重置回默认值,但有两个方法可 以跟踪上下文的状态变化。如果你知道将来还要返回某组属性与变换的组合,可以调用 save() 方法。 调用这个方法后,当时的所有设置都会进入一个栈结构,得以妥善保管。然后可以对上下文进行其他修改。等想要回到之前保存的设置时,可以调用 restore() 方法,在保存设置的栈结构中向前返回一级, 恢复之前的状态。连续调用 save() 可以把更多设置保存到栈结构中,之后再连续调用 restore() 则可以一级一级返回。
需要注意的是,save() 方法保存的只是对绘图上下文的设置和变换,不会保存绘图上下文的内容。
6、绘制图像
2D 绘图上下文内置了对图像的支持。如果你想把一幅图像绘制到画布上,可以使用 drawImage() 方法。根据期望的最终结果不同,调用这个方法时,可以使用三种不同的参数组合。最简单的调用方式是传入一个 HTML img元素,以及绘制该图像的起点的 x 和 y 坐标。例如:
var image = document.images[0];
context.drawImage(image, 10, 10);
这两行代码取得了文档中的第一幅图像,然后将它绘制到上下文中,起点为(10,10)。绘制到画布上的图像大小与原始大小一样。如果你想改变绘制后图像的大小,可以再多传入两个参数,分别表示目标宽度和目标高度。通过这种方式来缩放图像并不影响上下文的变换矩阵。例如:
context.drawImage(image, 50, 10, 20, 30);
执行代码后,绘制出来的图像大小会变成 20×30 像素。
除了上述两种方式,还可以选择把图像中的某个区域绘制到上下文中。
drawImage()方法的这种调用方式总共需要传入 9 个参数:要绘制的图像、源图像的 x 坐标、源图像的 y 坐标、源图像的宽度、源图像的高度、目标图像的 x 坐标、目标图像的 y 坐标、目标图像的宽度、目标图像的高度。这样调用 drawImage()方法可以获得最多的控制。例如:
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
这行代码只会把原始图像的一部分绘制到画布上。原始图像的这一部分的起点为(0,10),宽和高都是 50 像素。最终绘制到上下文中的图像的起点是(0,100),而大小变成了 40×60 像素。
7、阴影
2D 上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。
shadowColor:用 CSS 颜色格式表示的阴影颜色,默认为黑色。
shadowOffsetX:形状或路径 x 轴方向的阴影偏移量,默认为0。
shadowOffsetY:形状或路径 y 轴方向的阴影偏移量,默认为0。
shadowBlur:模糊的像素数,默认0,即不模糊。
这些属性都可以通过 context 对象来修改。只要在绘制前为它们设置适当的值,就能自动产生阴影。例如:
//设置阴影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
注意不同浏览器对阴影的支持有一些差异。
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);
为了让渐变覆盖整个矩形,而不是仅应用到矩形的一部分,矩形和渐变对象的坐标必须匹配才行。
如果没有把矩形绘制到恰当的位置,那可能就只会显示部分渐变效果。
WebGL
WebGL 是针对 Canvas 的 3D 上下文。
<待续>