HTML5添加的最受欢迎的功能就是<canvas>元素。这个元素负责在页面中设定一个区域,然后就可以通过JavaScript动态地在这个区域中绘制图形。<canvas>元素最早是由苹果公司推出的,当时主要用在其Dashboard中。很快,HTML5也加入了这个元素,主流浏览器开始支持它。
基本用法
要使用<canvas>元素,必须先设置其width和height属性,指定可以绘图的区域大小。出现在开始和结束标签中的内容是后备信息,如果浏览器不支持<canvas>元素,就会显示这些信息。
<canvas width="800" height="800"style="background-color:antiquewhite">A drawing of something.</canvas>
与其他元素一样,<canvas>元素对应的DOM元素也有width和height属性,可以随意修改。而且,也能通过CSS为该元素添加样式,如果不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的。
要在这块画布上绘图,需要取得绘图上下文。而取得绘图上下文对象的引用,需要调用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){
var context = drawing.getContext("2d");
//取得图像的数据URI
var imageURI = drawing.toDataURL("image/png");
var image = document.createElement("img");
image.src=imageURI;
document.body.appendChild(image);
}
2D上下文
使用2D绘图上下文提供的方法,可以绘制简单的2D图形,比如矩形、弧形和路径。2D上下文的坐标开始于<canvas>左上角,原始坐标是(0,0)。所有坐标值都基于这个原点计算,x值越大表示靠右,y值越大表示越靠下。默认情况下,width和height表示水平和垂直两个方向上可用的像素数目。
填充和描边
2D上下文的两种基本绘图操作是填充和描边。填充,就是用指定的样式(颜色、渐变或图像)填充图形;描边,就是只在图形的边缘画线。大多数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";
}
绘制矩形
矩形是唯一一种可以直接在2D上下文中绘制的形状。与矩形有关的方法包括fillRect()、strokeRect()和clearRect()。这三个方法都接受4个参数:矩形的x坐标、矩形的y坐标、矩形宽度和矩形高度。这些参数的单位都是像素。
首先,fillRect()方法在画布上绘制的矩形会填充指定的颜色。填充的颜色通过fillStyle属性指定,比如:
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
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属性指定。比如:
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
var context = drawing.getContext("2d");
context.strokeStyle="#ff0000";
context.strokeRect(10,10,50,50);
context.strokeStyle="rgba(0,0,255,0.5)";
context.strokeRect(30,30,50,50);
}
最后,clearRect()方法用于清除画布上的矩形区域。本质上,这个方法可以把绘制上下文中的某一矩形变透明。通过绘制形状然后再清除指定区域,就可以生成有意思的效果,例如把某个形状切掉一块。如:
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
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);
context.clearRect(40,40,10,10);
}
绘制路径
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(0方法对路径描边,描边使用的是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.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绘图上下文也提供了绘制文本的方法。绘制文本主要有两个方法: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”和“buttom”。
这几个属性都有默认值,因此没必要每次使用它们都重新设置一遍值。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坐标表示的是文本有段的位置。
变换
通过上下文的变换,可以把处理后的图像绘制到画布上。2D绘制上下文支持各种基本的绘制变换。创建绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵下,了所有处理都按描述直接绘制。为绘制上下文应用变换,会导致使用不同的变换矩阵应用处理,从而产生不同的结果。
可以通过如下方法来修改变换矩阵。
rotate(angle):围绕原点旋转angle弧度。
scale(csaleX,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()。
变换有可能很简单,但也有可能很复杂,这都要视情况而定。比如,就拿前面例子中绘制表针来说,如果把原点变换到表盘的中信,然后再绘制表针就容易多了。如:
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.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();
context.font="bold 14px Arial";
context.textAlign="center";
context.textBaseline="middle";
context.fillText("12",0,-85);
}
还可以像下面这样使用rotate()方法旋转时钟的表针。
……
//变换原点
context.translate(100,100);
context.rotate(1);
……
无论是刚才执行的变换,还是fillStyle、strokeStyle等属性,都会在当前上下文中一直有效,除非再对上下文进行什么修改。对冉没有什么办法把上下文中的一切都重置回默认值,但有两个方法可以跟踪上下文的状态变化。如果你知道将来还要返回某组属性与变换的组合,可以调用save()方法。调用这个方法后,当时的所有设置都会进入一个栈结构,得意妥善保管。然后可以对上下文进行其他修改。等想要回到之前保存的设置时,可以调用restore()方法,在保存设置的栈结构中向前返回一级,恢复之前的状态。连续调用save()可以把更多设置保存到栈结构中,之后再连续调用restore()则可以一级一级返回。如:
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
var context = drawing.getContext("2d");
context.fillStyle="#ff0000";
context.save();
context.fillStyle="#00ff00";
context.translate(100,100);
context.save();
context.fillStyle="#0000ff";
context.fillRect(0,0,100,100);//从点(100,100)开始绘制蓝色矩形
context.restore();
context.fillRect(10,10,100,100);//从点(110,110)开始绘制绿色矩形
context.restore();
context.fillRect(0,0,100,100);//从点(0,0)开始绘制红色矩形
}
需要注意的是,save()方法保存的只是对绘图上下文的设置和变换,不会保存绘图上下文的内容。
绘制图像
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);
执行代码后,绘制出来的图像大小会变成20x30像素。
除了上述两种方式,还可以把图像中的某个区域绘制到上下文中。drawImage()方法的这种调用方式总共需要传入9个参数:要绘制的图像、源图像的x坐标、源图像的y坐标、源图像的宽度、源图像的高度、目标图像的x坐标、目标图像的y坐标、目标图像的宽度、目标图像的高度。这样调用drawImage()方法可以获得最多的控制。
前面的文章中有模仿购物网站中放大图片的效果,这里使用canvas来实现就更加简单了,也不需要准备两张图片:
<styletype="text/css">
#drawing{
background-color:antiquewhite;
}
#image_{
max-width:220px;
}
</style>
<scripttype="text/javascript">
function showBig(image){
var drawing = document.getElementById("drawing");
if(drawing.getContext){
var context = drawing.getContext("2d");
var ptX=event.offsetX;
var ptY=event.offsetY;
context.drawImage(image,ptX*2.5,ptY*2.5,50,50,0,0,320,320);
}
}
</script>
<div>
<imgid="image_"src="files/images/cymini.jpg"onmousemove="showBig(this)"></img>
<canvasid="drawing"width="320"height="320">A drawing of something.</canvas>
</div>
阴影
2D上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。
shadowColor:用CSS颜色格式表示的阴影颜色,默认为黑色。
shadowOffsetX:形状或路径X轴方向的阴影偏移量,默认为0。
shadowOffsetY:形状或路径Y轴方向的阴影偏移量,默认为0。
shadowBlur:模糊的像素数,默认0,即不模糊。
这些属性都可以通过context对象来修改。
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
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,0.5)";
context.fillRect(30,30,50,50);
}
渐变
渐变由CanvasGradient实例表示,很容易通过2D上下文来创建和修改。要创建一个新的线性渐变,可以调用createLinearGradient()方法。这个方法接收4个参数:起点的x坐标、起点的y坐标、终点的x坐标、终点的y坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回CanvasGradient对象的实例。
创建了渐变对象后,下一步就是使用addColorStop()方法来指定色标。这个方法接收两个参数:色标为止和CSS颜色值。色标为止是一个0(开始的颜色)到1(结束的颜色)之间的数字。例如:
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
var context = drawing.getContext("2d");
var gradient = context.createLinearGradient(30,30,70,70);
gradient.addColorStop(0,"white");
gradient.addColorStop(1,"black");
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=gradient;
context.fillRect(30,30,50,50);
}
要创建径向渐变(或放射渐变),可以使用createRadialGradient()方法。这个方法接收6个参数,对应着两个圆的圆心和半径。前三个参数指定的是起点圆心和半径,后三个参数指定的是终点圆的圆心和半径。可以把径向渐变想象成一个长圆通,而这6个参数定义的正式这个桶的两个圆形开口的为止,就可达到像旋转这个圆锥体一样的效果。
var gradient =context.createRadialGradient(55,55,10,55,55,35);
模式
模式其实就是重复的图像,可以用来填充或描边图形。要创建一个新模式,可以调用createPattern()方法并传入两个参数:一个HTML<img>元素和一个表示如何重复图像的字符串。其中,第二个参数的值与CSS的background-repate属性值相同,包括“repeat”、“repeat-x”、“repeat-y”和“no- repeat”。
var drawing =document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
var context = drawing.getContext("2d");
var img = document.images[0];
var pattern = context.createPattern(img,"repeat");
context.fillStyle = pattern;
context.fillRect(10,10,100,100);
}
createPattern()方法的第一个参数也可以是一个<vedio>元素,或者另一个<canvas>元素。
使用图像数据
2D上下文的一个明显的长处就是,可以通过getImageData()取得原始图像数据。这个方法接收4个参数:要取得其数据的画面区域的x和y坐标以及该区域的像素宽度和高度。例如,要取得左上角坐标为(10,5),大小为50x50像素的图像数据,可以使用如下代码:
var imageData =context.getImageData(10,5,50,50);
这里返回的对象是ImageData的实例。每个ImageData对象都有三个属性:width、height和data。其中data属性是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示红、绿、蓝和透明度值。因此,第一个像素的数据就保存数据的第0到第3个元素中。数组中每个元素的值都介于0到255之间。能够直接访问到原始图像数据,就能够以各种方式来操作这些数据。例如,通过修改图像数据,可以像下面这样创建一个简单的灰阶过滤器。
var drawing = document.getElementById("drawing");
//确定浏览器支持<canvas>元素
if(drawing.getContext){
var context = drawing.getContext("2d");
var img = document.images[0],
imageData,data,i,len,average,
red,green,blue,alpha;
context.drawImage(img,0,0);
imageData =context.getImageData(0,0,img.width,img.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];
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);
}
合成
还有两个会应用到2D上下问中所有绘制操作的属性:globalAlpha和globalCompositionOperation。其中,globalAlpha是一个介于0到1之间的值,用于指定所有绘制的透明度。默认值为0.如果所有后续操作都要基于相同的透明度,就可以先把globalAlpha设置为适当的值。然后绘制,最后再把它设置回默认值0.
第二个属性globalCompositionOperation表示后绘制的图形怎样与先绘制的图形结合。这个属性的值是字符串,可能的值如下:
source-over(默认值):后绘制的图形位于先绘制的图形上方。
source-in:后绘制的图形与先绘制的图形重叠的部分可见,两者其他部分完全透明。
source-out:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形完全透明。
source-atop:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形不受影响。
destination-over:后绘制的图形位于先绘制的图形下方,只有之前透明像素下的部分才可见。
destination-in:后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。
destination-out:后绘制的图形擦除与先绘制的图形重叠的部分。
destination-atop:后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的图形会变透明。
lighter:后绘制的图形与先绘制的图形重叠部分的值相加,使该部分变亮。
copy:后绘制的图形完全替代与之重叠的先绘制图形。
xor:后绘制的图形与先绘制的图形重叠的部分执行“异或”操作。
WebGL
WebGL是针对Canvas的3D上下文。与其他Web技术不同,WebGL并不是W3C制定的标准,而是由KhronosGroup制定的。学习网址:www.learningwebgl.com。