使用Canvas绘图
HTML5 添加的最受欢迎的功能就是<canvas>
元素,这个元素负责在页面中设定一个区域,然后就可以通过JavaScript 动态地在这个区域中绘制图形。
基本用法
要使用<canvas>
元素,必须先设置其width 和height 属性,指定可以绘图的区域大小。填写在标签内部的文本信息会在浏览器不支持该元素的情况下显示:
<canvas id ="drawing" width =" 200" height ="200" > Your browser doesn't support the canvas tag.</canvas >
要在这块画布(canvas)上绘图,需要获得绘图上下文。而取得绘图上下文对象的引用,需要调用getContext() 方法并传入上下文的名字。传入”2d”,就可以取得2D 上下文对象。
var drawing = document .getElementById("drawing" );
//m ake sure <canvas> is completely supported
if (drawing.getContext) {
var context = drawing.getContext("2d" );
// and ...
}
使用toDataURL() 方法,可以导出在<canvas>
元素上绘制的图像。这个方法接受一个参数,即图像的MIME 类型 格式,而且适合用于创建图像的任何上下文。比如,要取得画布中的一幅PNG 格式的图像,可以使用以下代码。
<!DOCTYPE html>
<html >
<head >
<title > Canvas Fill Rect Example</title >
</head >
<body >
<canvas id ="drawing" width ="200" height ="200" > Your browser doesn't support the canvas tag.</canvas >
<input type ="button" value ="Export" id ="export-btn" >
<script type ="text/javascript" >
window.onload = function () {
var drawing = document.getElementById("drawing" ),
btn = document.getElementById("export-btn" ),
download = document.getElementById("download-btn" );
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 );
}
btn.onclick = function () {
var imgURI = drawing.toDataURL();
var image = document.createElement("img" );
image.src = imgURI;
document.body.appendChild(image);
};
};
</script >
</body >
</html >
2D上下文
使用2D 绘图上下文提供的方法,可以绘制简单的2D 图形,比如矩形 、弧线 和路径 。2D 上下文的坐标开始于<canvas>
元素的左上角,原点坐标是(0,0)。所有坐标值都基于这个原点计算,x 值越大表示越靠右 ,y 值越大表示越靠下 。默认情况下,width 和height 表示水平 和垂直 两个方向上可用的像素数目 。
填充和描边
2D 上下文的两种基本绘图操作是填充和描边 。这两个操作的结果取决于两个属性:fillStyle 和strokeStyle 。 这两个属性的值可以是字符串 、渐变对象 或模式对象 ,而且它们的默认值都是“#000000” 。它们指定表示颜色的字符串值,可以使用CSS 中指定颜色值的任何格式,包括颜色名 、十六进制码 、rgb 、rgba 、hsl 或hsla 。
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 );
}
绘制矩形
矩形是唯一 一种可以直接 在2D 上下文中绘制的形状。与矩形有关的方法包括fillRect() 、strokeRect() 和clearRect() 。这三个方法都能接收4 个参数:矩形的x 坐标 (左上角)、矩形的y 坐标 (左上角)、矩形宽度 和矩形高度 。这些参数的单位都是像素。 fillRect() 不必多说,strokeRect() 就是描边,而clearRect() 相当于橡皮擦,从画布中擦出一个空白的矩形域。描边线条的宽度由lineWidth 属性控制,该属性的值可以是任意整数。另外,通过lineCap 属性可以控制线条末端的形状是平头、圆头还是方头(“butt”、”round”或”square” ),通过lineJoin 属性可以控制线条相交的方式是圆交、斜交还是斜接(“round”、”bevel”或”miter” )。
if (drawing.getContext ){
var context = drawing.getContext ("2d" )
context.lineWidth = 5
context.lineJoin = "round"
context.strokeStyle = "#ff0000"
context.strokeRect (10 , 10 , 50 , 50 )
context.lineWidth = 10
context.strokeStyle = "rgba(0,0,255,0.5)"
context.strokeRect (30 , 30 , 50 , 50 )
}
绘制路径
如何绘制不是矩形的图形呢 ?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)。太难理解了,而且也不一定对。我搜了资料 后发现:arcTo()方法将利用当前端点、端点1(x1,y1)和端点2(x2,y2)这三个点所形成的夹角,然后绘制一段与夹角的两边相切并且半径为radius的圆上的弧线。弧线的起点就是当前端点所在边与圆的切点,弧线的终点就是端点2(x2,y2)所在边与圆的切点,并且绘制的弧线是两个切点之间长度最短的那个圆弧。此外,如果当前端点不是弧线起点,arcTo()方法还将添加一条当前端点到弧线起点的直线线段。非常复杂。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() ,这个方法可以在路径上创建一个剪切区域。
<canvas id ="drawing" width ="500" height ="500" > Your browser doesn't support the canvas tag.</canvas >
<script type ="text/javascript" >
var drawing = document.getElementById("drawing" );
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();
}
</script >
在2D 绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制。由于路径的使用很频繁,所以就有了一个名为isPointInPath()的方法。这个方法接收x 和y 坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径上,例如:
if (context.isPointInPath(100 , 100 )){
alert("Point (100, 100) is in the path." );
}
绘制文本
文本与图形总是如影随形。为此,2D 绘图上下文也提供了绘制文本的方法。绘制文本主要有两个方法:fillText() 和strokeText() 。这两个方法都可以接收4 个参数:要绘制的文本字符串 、x 坐标 、y 坐标 和可选的最大像素宽度 (传入该参数调用fillText() 或strokeText() 时如果传入的字符串大于最大宽度,则绘制的文本字符的高度正确,但宽度会收缩以适应最大宽度)。而且,这两个方法都以下列3 个属性为基础。 font :表示文本样式、大小及字体,用CSS 中指定字体的格式来指定,例如”10px Arial”。textAlign :表示文本对齐方式。可能的值有“start”、”end”、”left”、”right”和”center” 。建议使用”start”和”end”,不要使用”left”和”right”,因为前两者的意思更稳妥,能同时适合从左到右和从右到左显示(阅读)的语言。说白了就是文本相对于中心点横坐标的对齐方式。textBaseline :表示文本的基线。可能的值有“top”、”hanging”、”middle”、”alphabetic”、”ideographic”和”bottom” 。说白了就是文本相对于中心点纵坐标的对齐方式。由于绘制文本比较复杂,特别是需要把文本控制在某一区域中的时候,2D 上下文提供了辅助确定文本大小的方法measureText() 。这个方法接收一个参数,即要绘制的文本;返回一个TextMetrics 对象。 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 )
变换
可以通过如下方法来修改变换矩阵:
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) :直接修改变换矩阵,方式是乘以如下矩阵。setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy) :将变换矩阵重置为默认状态,然后再调用transform() 。
m1_1 m1_2 dx
m2_1 m2_2 dy
0 0 1
window.onload = function(){
var drawing = document.getElementById ("drawing" )
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)
//将源点移动至100 ,100
context.translate (100 , 100 )
//旋转45 度
context.rotate (Math.PI / 4 )
//画分针
context.moveTo (0 ,0 )
context.lineTo (0 , -85 )
//画时针
context.moveTo (0 , 0 )
context.lineTo (-65 , 0 )
context.stroke ()
}
}
有时我们可能需要保存之前设置的fillStyle 、strokeStyle 等属性(还有变换、阴影等 )。此时就可以使用save()方法。当时的所有设置都会进入一个栈结构,得以妥善保管。在我们需要的时候调用restore()方法,就可以恢复之前的状态。需要注意的是,save()方法保存的只是对绘图上下文的设置和变换 ,不会保存绘图上下文的内容 。
window.onload = function(){
var drawing = document.getElementById ("drawing" )
//make sure <canvas> is completely supported
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 , 200 )
context.restore ()
context.fillRect (10 , 10 , 100 , 200 )
context.restore ()
context.fillRect (0 , 0 , 100 , 200 )
}
}
绘制图像
如果你想把一幅图像绘制到画布上,可以使用drawImage() 方法。
context.drawImage(image , 10 , 10 );
context.drawImage(image , 50 , 10 , 20 , 30 );
context.drawImage(image , 0 , 10 , 50 , 50 , 0 , 100 , 40 , 60 );
除了对drawImage() 传入img元素以外,还可以传入另一个<canvas>
元素。
阴影
2D 上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影:
shadowColor :用CSS 颜色格式表示的阴影颜色,默认为黑色。shadowOffsetX :形状或路径x 轴方向的阴影偏移量,默认为0。shadowOffsetY :形状或路径y 轴方向的阴影偏移量,默认为0。shadowBlur :模糊的像素数,默认0,即不模糊。
context.shadowOffsetX = 10
context.shadowOffsetY = 10
context.shadowBlur = 10
context.shadowColor = "rgba(0, 0, 0, 0.5)"
//draw a red rectangle
context.fillStyle = "#ff0000"
context.fillRect (10 , 10 , 50 , 50 )
//draw a blue rectangle
context.fillStyle = "rgba(0,0,255,1)"
context.fillRect (30 , 30 , 50 , 50 )
渐变
我们可以使用createLinearGradient()来创造线性渐变 对象。这个方法接收4 个参数:起点的x 坐标、起点的y 坐标、终点的x 坐标、终点的y 坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回CanvasGradient 对象的实例。创建了渐变对象后,下一步就是使用addColorStop()方法来指定色标。这个方法接收两个参数:色标位置和CSS 颜色值 。色标位置是一个0 (开始的颜色)到1 (结束的颜色)之间的数字。
window.onload = function(){
var drawing = document.getElementById ("drawing" )
if (drawing.getContext ){
var context = drawing.getContext ("2d" ),
gradient = context.createLinearGradient (30 , 30 , 70 , 70 )
gradient.addColorStop (0 , "white" )
gradient.addColorStop (1 , "black" )
//draw a red rectangle
context.fillStyle = "#ff0000"
context.fillRect (10 , 10 , 50 , 50 )
//draw a gradient rectangle
context.fillStyle = gradient
context.fillRect (30 , 30 , 70 , 70 )
//context.fillRect (50 , 50 , 50 , 50 )
}
}
要创建径向渐变 (或放射渐变 ),可以使用createRadialGradient() 方法。这个方法接收6 个参数,对应着两个圆的圆心 和半径 。
window.onload = function(){
var drawing = document.getElementById ("drawing" )
if (drawing.getContext ){
var context = drawing.getContext ("2d" ),
gradient = context.createRadialGradient (55 , 55 , 10 , 55 , 55 , 30 )
gradient.addColorStop (0 , "white" )
gradient.addColorStop (1 , "black" )
//draw a red rectangle
context.fillStyle = "#ff0000"
context.fillRect (10 , 10 , 50 , 50 )
//draw a gradient rectangle
context.fillStyle = gradient
context.fillRect (30 , 30 , 70 , 70 )
//context.fillRect (50 , 50 , 50 , 50 )
}
}
模式
模式其实就是重复的图像, 可以用来填充或描边图形。要创建一个新模式, 可以调用createPattern() 方法并传入两个参数:一个HTML <img>
元素和一个表示如何重复图像的字符串。其中,第二个参数的值与CSS 的background-repeat 属性值相同,包括“repeat” 、“repeat-x” 、“repeat-y” 和“no-repeat” 。
window.onload = function () {
var drawing = document.getElementById("drawing" );
if (drawing.getContext){
var context = drawing.getContext("2d" ),
image = document.images[0 ],
pattern = context.createPattern(image, "repeat" );
context.fillStyle = pattern;
context.fillRect(10 , 10 , 150 , 150 );
}
};
使用图像数据
可以通过getImageData() 取得原始图像数据。这个方法接收4 个参数:要取得其数据的画面区域的x 和y 坐标以及该区域的像素宽度和高度。返回一个ImageData 的实例。每个ImageData 对象都有三个属性:width、height 和data 。其中data 属性是一个数组,保存着图像中每一个像素的数据。在data 数组中,每一个像素用4 个元素来保存,分别表示红、绿、蓝和透明度值 。因此,第一个像素的数据就保存在数组的第0 到第3 个元素中(第二个像素保存在数组的第4到第7个元素中)。数组中每个元素的值都介于0 到255 之间(包括0 和255)。对data数组进行一定的修改,再把data 数组回写到imageData 对象后,调用putImageData() 方法就可以把图像数据绘制到画布上。例如,通过修改图像数据,可以像下面这样创建一个简单的灰阶过滤器 。
window.onload = function(){
var drawing = document.getElementById ("drawing" )
//make sure <canvas> is completely supported
if (drawing.getContext ){
var 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
red = data[i]
green = data[i+1 ]
blue = data[i+2 ]
alpha = data[i+3 ]
average = Math.floor ((red + green + blue) / 3 )
//set the colors, leave alpha alone
data[i] = average
data[i+1 ] = average
data[i+2 ] = average
}
imageData.data = data
context.putImageData (imageData, 0 , 0 )
}
}
合成
还有两个会应用到2D 上下文中所有绘制操作的属性:globalAlpha 和globalComposition-Operation 。前者为全局透明度,是一个介于0和1之间的值(0代表全透明)。
if (drawing.getContext ){
var context = drawing.getContext ("2d" )
//draw a red rectangle
context.fillStyle = "#ff0000"
context.fillRect (10 , 10 , 50 , 50 )
context.globalAlpha = 0.3
//draw a blue rectangle
context.fillStyle = "rgba(0,0,255,1)"
context.fillRect (30 , 30 , 50 , 50 )
}
globalComposition-Operation 表示后绘制的图形怎样与先绘制的图形结合,这个属性的值是字符串,有以下可能的值:
source-over (默认值):后绘制的图形位于先绘制的图形上方。source-in :后绘制的图形与先绘制的图形重叠的部分可见,两者其他部分完全透明。source-out :后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形完全透明。source-atop :后绘制的图形与先绘制的图形重叠的部分可见,先绘制图形不受影响。destination-over :后绘制的图形位于先绘制的图形下方,只有之前透明像素下的部分才可见。destination-in :后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。destination-out :后绘制的图形擦除与先绘制的图形重叠的部分。destination-atop :后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的图形会变透明。lighter :后绘制的图形与先绘制的图形重叠部分的值相加,使该部分变亮。copy :后绘制的图形完全替代与之重叠的先绘制图形。xor :后绘制的图形与先绘制的图形重叠的部分执行“异或”操作。
if (drawing.getContext ){
var context = drawing.getContext ("2d" )
//draw a red rectangle
context.fillStyle = "#ff0000"
context.fillRect (10 , 10 , 50 , 50 )
context.globalCompositeOperation = "xor"
//draw a blue rectangle
context.fillStyle = "rgba(0,0,255,1)"
context.fillRect (30 , 30 , 50 , 50 )
}
使用这个必须要小心,因为不同浏览器对这个属性的实现仍然存在较大的差别。