使用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");
//make 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)
}
- 使用这个必须要小心,因为不同浏览器对这个属性的实现仍然存在较大的差别。