第十五章:使用Canvas绘图(2D上下文)

时间:2022-04-18 22:17:51

使用Canvas绘图

  • HTML5 添加的最受欢迎的功能就是<canvas>元素,这个元素负责在页面中设定一个区域,然后就可以通过JavaScript 动态地在这个区域中绘制图形。

基本用法

  • 要使用<canvas>元素,必须先设置其widthheight 属性,指定可以绘图的区域大小。填写在标签内部的文本信息会在浏览器不支持该元素的情况下显示:
    <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 imgURI = drawing.toDataURL("image/png"); //效果相同,默认格式为PNG
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 上下文的两种基本绘图操作是填充和描边。这两个操作的结果取决于两个属性:fillStylestrokeStyle
  • 这两个属性的值可以是字符串渐变对象模式对象,而且它们的默认值都是“#000000”。它们指定表示颜色的字符串值,可以使用CSS 中指定颜色值的任何格式,包括颜色名十六进制码rgbrgbahslhsla
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()方法,表示要开始绘制新路径。然后,再通过调用下列方法来实际地绘制路径。
    1. arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x,y)为圆心绘制一条弧线,弧线半径为radius,起始和结束角度(用弧度表示)分别为startAngle 和endAngle。最后一个参数表示startAngle 和endAngle 是否按逆时针方向计算,值为false表示按顺时针方向计算。
    2. arcTo(x1, y1, x2, y2, radius):书上的解释是:从上一点开始绘制一条弧线,到(x2,y2)为止,并且以给定的半径radius 穿过(x1,y1)。太难理解了,而且也不一定对。我搜了资料后发现:arcTo()方法将利用当前端点、端点1(x1,y1)和端点2(x2,y2)这三个点所形成的夹角,然后绘制一段与夹角的两边相切并且半径为radius的圆上的弧线。弧线的起点就是当前端点所在边与圆的切点,弧线的终点就是端点2(x2,y2)所在边与圆的切点,并且绘制的弧线是两个切点之间长度最短的那个圆弧。此外,如果当前端点不是弧线起点,arcTo()方法还将添加一条当前端点到弧线起点的直线线段。非常复杂。
    3. bezierCurveTo(c1x, c1y, c2x, c2y, x, y):从上一点开始绘制一条曲线,到(x,y)为止,并且以(c1x,c1y)和(c2x,c2y)为控制点。涉及到贝塞尔曲线的知识,有兴趣可以去查查。。
    4. lineTo(x, y):从上一点开始绘制一条直线,到(x,y)为止。
    5. moveTo(x, y):将绘图游标移动到(x,y),不画线。
    6. quadraticCurveTo(cx, cy, x, y):从上一点开始绘制一条二次曲线,到(x,y)为止,并且以(cx,cy)作为控制点。涉及到贝塞尔曲线的知识,有兴趣可以去查查。。
    7. 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");
//确定浏览器支持<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();
}
</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);

变换

  • 可以通过如下方法来修改变换矩阵:
    1. rotate(angle):围绕原点旋转图像angle 弧度。
    2. scale(scaleX, scaleY):缩放图像,在x 方向乘以scaleX,在y 方向乘以scaleY。scaleX和scaleY 的默认值都是1.0。
    3. translate(x, y):将坐标原点移动到(x,y)。执行这个变换之后,坐标(0,0)会变成之前由(x,y)表示的点。
    4. transform(m1_1, m1_2, m2_1, m2_2, dx, dy):直接修改变换矩阵,方式是乘以如下矩阵。
    5. 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);
//将源点移动至100100
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();
}
};
  • 有时我们可能需要保存之前设置的fillStylestrokeStyle 等属性(还有变换、阴影等)。此时就可以使用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); //圆心是100,100
context.restore();
context.fillRect(10, 10, 100, 200); //圆心是110,110
context.restore();
context.fillRect(0, 0, 100, 200); //圆心是0,0
}
};

绘制图像

  • 如果你想把一幅图像绘制到画布上,可以使用drawImage()方法。
//image为img元素的引用。10,10代表左上角坐标
context.drawImage(image, 10, 10);
//20,30代表目标宽度和目标高度。会被缩放
context.drawImage(image, 50, 10, 20, 30);

//前4个参数代表的是源图像,后4个参数代表是目标图像
//可以想象成从原图像中截取0, 10, 50, 50部分的矩形图像
//然后将其放入画布的0, 100, 40, 60位置,同时做适当的缩放
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
  • 除了对drawImage()传入img元素以外,还可以传入另一个<canvas>元素。

阴影

  • 2D 上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影:
    1. shadowColor:用CSS 颜色格式表示的阴影颜色,默认为黑色。
    2. shadowOffsetX:形状或路径x 轴方向的阴影偏移量,默认为0。
    3. shadowOffsetY:形状或路径y 轴方向的阴影偏移量,默认为0。
    4. 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],//image为HTML中一个img元素
pattern = context.createPattern(image, "repeat");
//draw a rectangle
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; 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);
//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 上下文中所有绘制操作的属性:globalAlphaglobalComposition-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表示后绘制的图形怎样与先绘制的图形结合,这个属性的值是字符串,有以下可能的值:
    1. source-over(默认值):后绘制的图形位于先绘制的图形上方。
    2. source-in:后绘制的图形与先绘制的图形重叠的部分可见,两者其他部分完全透明。
    3. source-out:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形完全透明。
    4. source-atop:后绘制的图形与先绘制的图形重叠的部分可见,先绘制图形不受影响。
    5. destination-over:后绘制的图形位于先绘制的图形下方,只有之前透明像素下的部分才可见。
    6. destination-in:后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。
    7. destination-out:后绘制的图形擦除与先绘制的图形重叠的部分。
    8. destination-atop:后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的图形会变透明。
    9. lighter:后绘制的图形与先绘制的图形重叠部分的值相加,使该部分变亮。
    10. copy:后绘制的图形完全替代与之重叠的先绘制图形。
    11. 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);
}
  • 使用这个必须要小心,因为不同浏览器对这个属性的实现仍然存在较大的差别。