HTML5 Canvas 学习概览
HTML5 Canvas元素本身是一个HTML元素,用于在Canvas上工作的API是javascript API。我们将在后面的文章中逐一对其进行介绍。
声明一个Canvas元素
现在,让我们来看看如何在HTML页面中声明一个Canvas元素。
<canvas id="ex1" width="500" height="150" style="border: 1px solid #cccccc;">
你的浏览器不支持HTML5 Canvas!
</canvas>
上面的代码声明了一个Canvas元素,它的宽度为500,高度为150,并且设置了1个像素的灰色描边。
如果浏览器支持HTML5 Canvas元素,那么在<canvas>中的文字会被忽略。如果浏览器不支持HTML5 Canvas元素,这些文字会被作为提示文字显示出来。
你可以将<canvas>元素放置在页面中任何你想显示它的地方,例如放置在一个<div>中。
在Canvas元素上绘制图形
在HTML5 Canvas上绘制图形是一种即时模式(immediate mode)。所谓的即时模式是指一旦在Canvas上绘制了图形之后,Canvas将不再知道这个图形的任何信息。被绘制的图形是可见的,但是你不能够操作这个图形。它就像一块正真的画布,在你绘制图形之后,留在上面的只是一些颜色(像素)。
这是Canvas和SVG同的地方,SVG图形是可以被单独操纵的,也可以被重新绘制。在HTML5 canvas中如果你想修改绘制的图形,你需要重新绘制所有的东西。
要想在HTML5 canvas中绘制各种图形,需要使用javascript。下面是绘制的步骤:
1、等待页面DOM元素加载完毕。
2、获取canvas元素的引用。
3、从canvas元素中获取一个2D上下文(context)。
4、从2D上下文中使用绘制函数绘制图形。
下面是一个简单的例子,它遵从了上面的4个步骤来在canvas中绘制一个矩形。
<script>
// 1. 等待页面DOM元素加载完毕。
window.onload = function() {
drawExamples();
}
function drawExamples(){
// 2. 获取canvas元素的引用。
var canvas = document.getElementById("ex1");
// 3. 从canvas元素中获取一个2D上下文(context)。
var context = canvas.getContext("2d");
// 4. 从2D上下文中使用绘制函数绘制图形。
context.fillStyle = "#ff0000";
context.fillRect(10,10, 100,100);
}
</script>
在上面的代码中,首先在window对象中添加了一个事件监听。这个事件监听函数在页面页面加载完成之后被执行。这个函数会调用一个已经定义好的函数drawExamples()。
接着,drawExamples()函数通过document.getElementById()方法获取canvaas元素的引用。然后,通过在canvas引用上执行getContext(“2d”)方法获取一个2D上下文。
最后,就可以在这个2D上下文中使用绘制函数来绘制图形了。
下面是上面代码的返回结果。
HTML5 Canvas:绘制矩形
不论你在HTML5 canvas上绘制什么图形,有两个属性是你必须设置的:
描边属性-stroke
填充属性-fill
填充属性和描边属性决定如何来绘制图形。stroke是图形的外轮廓边框。fill则是图形的填充颜色。
下面是一个简单的例子,在Canvas中绘制了一个蓝色边框和绿色填充的矩形(实际上这是两个矩形):
上面例子的实现代码如下:
// 1. 等待页面DOM元素加载完毕
window.onload = function() {
drawExamples();
}
function drawExamples(){
// 2. 获取canvas元素的引用
var canvas = document.getElementById("ex1");
// 3. 从canvas元素中获取一个2D context
var context = canvas.getContext("2d");
// 4. 绘制图形
context.fillStyle = "#009900";
context.fillRect(10,10, 100,100);
context.strokeStyle = "#0000ff";
context.lineWidth = 5;
context.strokeRect(10,10, 100,100);
}
注意上面的填充样式和描边样式是分别设置的。分别是使用2D上下文的strokeStyle和fillStyle属性。
描边的宽度使用的是lineWidth属性,lineWidth设置为5表示矩形的外边框的线条宽度为5个单位。
绘制矩形
在HTML5 canvas中,可以绘制的最简单的图形莫过于绘制一个矩形。在上面的例子中我们就绘制了两个矩形。它们分别是通过2D上下文的fillRect()方法和strokeRect()方法来实现的。
绘制矩形的代码如下:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.fillStyle = "#ff0000";
context.fillRect(10,10, 100,100);
context.strokeStyle = "#0000ff";
context.strokeRect(30,20, 120,110);
fillRect()
fillRect()方法用于绘制一个填充颜色的矩形。它由x和y属性决定矩形的左上角位置,width和height实现决定矩形的宽度和高度。
请记住,canvas的坐标系统由canvas的左上角(0,0)坐标开始,然后X轴向右为正值方向,Y轴向下为正值方向。它的Y轴和正常的几何坐标系统是相反的。
x、y、width和height4个变量作为参数传入fillRect()中,用以确定如何绘制这个矩形。下面是一个例子:
var x = 10;
var y = 10;
var width = 100;
var height = 100;
context.fillRect(x, y, width, height);
下面是上面的代码所绘制的矩形:
上面的矩形是黑色的,是因为我们没有设置2D上下文的fillStyle属性,它默认的颜色就是黑色的。
strokeRect()
strokeRect()方法用于绘制一个描边矩形,没有填充色。同样,它由x和y属性决定矩形的左上角位置,width和height实现决定矩形的宽度和高度。熄灭是一个例子:
var x = 10;
var y = 10;
var width = 100;
var height = 100;
context.strokeRect(x, y, width,height);
上面代码的返回结果如下:
同样,在我们没有设置2D上下文的strokeStyle属性的情况下,描边色是黑色的。
你可以使用2D上下文的lineWidth属性来设置描边的宽度。例如下面的例子:
var x = 10;
var y = 10;
var width = 100;
var height = 100;
context.lineWidth = 4;
context.strokeRect(x, y, width, height);
上面的代码的返回结果如下:
矩形的颜色
你可以使用2D上下文的fillStyle和strokeStyle来设置绘制矩形的颜色。例如下面的例子:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.fillStyle = "#f2784b";
context.fillRect(10,10, 100,100);
context.lineWidth = 4;
context.strokeStyle = "#049372";
context.strokeRect(30,20, 120,110);
上面的代码的返回结果如下:
clearRect()
2D上下文的clearRect()函数用于在Canvas中清除一个矩形区域。被清除的矩形区域变为透明。下面是一个例子:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.fillStyle = "#ff0000";
context.fillRect(10,10, 100,100);
context.strokeStyle = "#0000ff";
context.strokeRect(30,20, 120, 110);
context.clearRect(50, 30, 110, 35);
上面代码的而返回结果如下:
注意观察上面的图形,红色和蓝色矩形分别被清除掉一部分。因为clearRect()会使矩形区域变得透明,而canvas元素位于其它元素的上面,所以透过透明区域可以看到这些canvas元素。
clearRect()和矩形一样也可以传入4个参数:clearRect(x, y, width, height)。这4个参数的意义和矩形的描述是相同的。
HTML5 Canvas:绘制路径
一条HTML5 canvas路径是通过绘制指令来连接一系列的点,由这一系列的点构成直线或曲线。路径可以用于在HTML5 canvas上绘制各种类型的图形:直线、圆形、多边形等等。路径的绘制是canvas的核心,必须很好的理解和掌握。
开始和关闭一条路径
要开始和关闭一条路径可以使用2D上下文的beginPath()和closePath()函数。例如下面的例子:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.beginPath();
//... 绘制路径
context.closePath();
moveTo()函数
当你在canvas中绘制一条路径的时候,你可以想象自己正在使用一支“虚拟笔”。这支虚拟笔总是位于某个位置,你可以使用2D上下文的moveTo(x, y)函数来移动这支虚拟笔。例如下面的代码:
context.moveTo(10,10);
这个例子将“虚拟笔”移动到(10,10)这个坐标点上。
lineTo()函数
lineTo函数用于从虚拟笔的当前位置绘制一条直线到lineTo()函数中指定的点。下面是一个例子:
context.beginPath();
context.moveTo(10,10);
context.lineTo(50,50);
context.closePath();
这个例子中首先移动虚拟笔到(10,10)坐标点位置,然后从这个点绘制一条直线到(50,50)坐标点。
lineTo()函数还会将虚拟笔移动到执行的结束点位置。上面的例子中是移动到(50,50)的位置。
stroke()函数 + fill()函数
在你没有通知2D上下文绘制路径之前,实际是不会在画布上绘制任何东西的。你可以通过stroke()或fill()函数来通知2D上下文。
stroke()函数用于路径操作指定的图形的外轮廓。
fill()函数用于填充有路径操作指定的图形。
下面的例子展示了stroke()或fill()函数的用法。
context.beginPath();
context.moveTo(10,10);
context.lineTo(60,50);
context.lineTo(110,50);
context.lineTo(10,10);
context.stroke();
context.closePath();
context.beginPath();
context.moveTo(100,10);
context.lineTo(150,50);
context.lineTo(200,50);
context.lineTo(100,10);
context.fill();
context.closePath();
上面代码的返回结果如下:
线条的宽度
你可以使用2D上下文的lineWidth属性来设置绘制线条的宽度。下面是一个例子:
context.lineWidth = 10;
上面的例子设置绘制线条的宽度为10像素。
下面的例子绘制3条直线,它们的宽度分别为1,5和10。
当你绘制的线条宽度大于1的时候,扩展的线条宽度将平均分配在线条中心线的两侧。距离来说,如果你从(10,10)这个点绘制一条直线到(100,10)这个点,线条的宽度为10,那么,实际上是从(10,5)这个点开始绘制,然后扩展到(10,15)这个点,在水平绘制到(100,5)和(100,15)这两个点,就像是绘制一个矩形。
线条的线条(Line Cap)
在使用路径来绘制线条的时候,你可以设置线条的线头样式。线头的样式通过2D上下文的lineCap属性来设置。它有三个可选值:
- butt
- round
- square
butt样式的线头是扁平且和线正交的样式。
round样式的线头是一个圆角的线头,圆的半径等于线条宽度的一半。
square样式的线头会在线的末端绘制一个矩形。矩形的大小为:线条的宽度 X 线条的宽/2。
下面是几个不同线头样式的线条的例子。所有的线条的宽度都是10。最总版的一组线条的lineCap的取值为butt,中间的一组线条的lineCap的取值为round,最右边的一组线条的lineCap的取值为square。
lineCap的取值butt和square非常相似。有时难以区别。这里制作了几个小例子,从这些例子中你可以看出它们之间的微小差别。下面又三组线条,每一组左边(或上边)的线条的lineCap属性取值为butt,右边(或下边)的线条的lineCap属性取值为square。
正如上面的结果所示,square线头的线条要比butt线头的线条要长。
线条的连接
2D上下文的lineJoin属性用于定义两条线条连接处的点如何绘制。两条线条连接处的点被称为“连接点”。lineJoin属性有下面的三种取值:
- miter
- bevel
- round
下面是这三种取值的示例代码:
context.lineJoin = "miter";
context.lineJoin = "bevel";
context.lineJoin = "round";
miter的连接点是一个三角形的连接点。
bevel的连接点是一个平头的连接点。
round的连接点是一个圆角的连接点。
下面分别是三种线条连接点的例子,从左到右的lineJoin属性分别是:miter,bevel和round。
绘制曲线
2D上下文的arc()函数可以用于绘制一条曲线。arc()函数有6个参数:
- x:圆弧的中心点的X坐标位置。
- y:圆弧的中心点的Y坐标位置。
- radius:圆弧的半径。
- startAngle:圆弧开始的角弧度。
- endAngle:圆弧结束的角弧度。
- anticlockwise:设置是以顺时针还是逆时针绘制圆弧,false为顺时针。
下面是一段示例代码:
context.lineWidth = 3;
var x = 50;
var y = 50;
var radius = 25;
var startAngle = (Math.PI / 180) * 45;
var endAngle = (Math.PI / 180) * 180;
var anticlockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, anticlockwise);
context.stroke();
context.closePath();
上面的代码绘制了一条弧线,它的中心点位于(50,50)坐标点,半径为25,从45度开始到180度结束。
下面是上面代码的返回结果:
上面的例子如果将anticlockwise设置为true,会得到下面的结果:
如果你要花一个完整的圆,可以简单的设置startAngle为0,endAngle设置为2 * Math.PI,它相当于(Math.PI / 180) * 360。
arcTo()函数
2D上下文中有一个arcTo()函数,它用于从当前的点绘制一条曲线到参数指定的点,曲线的半径也由参数指定。它的语法为:arcTo(x1, y1, x2, y2, radius)。注意:参数中x1, y1, x2, y2指的是这个点的控制点。arcTo()函数可以使用lineTo()和arc函数来模仿。
quadraticCurveTo()函数
quadraticCurveTo()函数用于绘制一条二次贝兹曲线。这条曲线由一个控制点来控制,它的语法为:quadraticCurveTo(cp1x, cp1y, x, y)。下面是一个示例代码:
context.lineWidth = 3;
var fromX = 50;
var fromY = 50;
var toX = 100;
var toY = 50;
var cpX = 75;
var cpY = 100;
context.beginPath();
context.moveTo(fromX, fromY);
context.quadraticCurveTo(cpX, cpY, toX, toY);
context.stroke();
context.closePath();
上面的代码绘制一条从(50,50)开始到(100,50)的二次贝兹曲线,这条曲线的控制点为(75,100)。得到的结果如下所示:
仔细看,在曲线下方有一个小点,那是这条曲线的控制点。(这个点是专门绘制出来让大家看的)
关于控制点,可以参考下面的图像:
bezierCurveTo()函数
bezierCurveTo()函数用于从一个点到另一个点绘制一条三次贝兹曲线。三次贝兹曲线有两个控制点,而二次贝兹曲线只有一个控制点。它的语法为:bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)。下面是一个例子:
context.lineWidth = 3;
var fromX = 50;
var fromY = 50;
var toX = 300;
var toY = 50;
var cp1X = 100;
var cp1Y = 100;
var cp2X = 250;
var cp2Y = 100;
context.beginPath();
context.moveTo(fromX, fromY);
context.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, toX, toY);
context.stroke();
context.closePath();
这段代码从(50,50)绘制一条三次贝兹曲线到(300,50),两个控制点分别为:(100,100)和(250,100)。得到的结果如下:
曲线下方的两个小圆点是两个控制点,他们并不是曲线的一部分。
下面是一个不同的例子,它的开始点和结束点于上面的例子相同,但是控制点和上面的例子不相同。
context.lineWidth = 3;
var fromX = 50;
var fromY = 50;
var toX = 300;
var toY = 50;
var cp1X = 100;
var cp1Y = 10;
var cp2X = 250;
var cp2Y = 100;
context.beginPath();
context.moveTo(fromX, fromY);
context.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, toX, toY);
context.stroke();
context.closePath();
上面的代码得到的结果如下:
文章的最后,我们引用MDN上的一个例子,它用Canvas路径绘制出“吃豆人”游戏的一个小场景:
实现的代码如下:
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
roundedRect(ctx,12,12,150,150,15);
roundedRect(ctx,19,19,150,150,9);
roundedRect(ctx,53,53,49,33,10);
roundedRect(ctx,53,119,49,16,6);
roundedRect(ctx,135,53,49,33,10);
roundedRect(ctx,135,119,25,49,10);
ctx.beginPath();
ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,false);
ctx.lineTo(31,37);
ctx.fill();
for(var i=0;i<8;i++){
ctx.fillRect(51+i*16,35,4,4);
}
for(i=0;i<6;i++){
ctx.fillRect(115,51+i*16,4,4);
}
for(i=0;i<8;i++){
ctx.fillRect(51+i*16,99,4,4);
}
ctx.beginPath();
ctx.moveTo(83,116);
ctx.lineTo(83,102);
ctx.bezierCurveTo(83,94,89,88,97,88);
ctx.bezierCurveTo(105,88,111,94,111,102);
ctx.lineTo(111,116);
ctx.lineTo(106.333,111.333);
ctx.lineTo(101.666,116);
ctx.lineTo(97,111.333);
ctx.lineTo(92.333,116);
ctx.lineTo(87.666,111.333);
ctx.lineTo(83,116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91,96);
ctx.bezierCurveTo(88,96,87,99,87,101);
ctx.bezierCurveTo(87,103,88,106,91,106);
ctx.bezierCurveTo(94,106,95,103,95,101);
ctx.bezierCurveTo(95,99,94,96,91,96);
ctx.moveTo(103,96);
ctx.bezierCurveTo(100,96,99,99,99,101);
ctx.bezierCurveTo(99,103,100,106,103,106);
ctx.bezierCurveTo(106,106,107,103,107,101);
ctx.bezierCurveTo(107,99,106,96,103,96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101,102,2,0,Math.PI*2,true);
ctx.fill();
ctx.beginPath();
ctx.arc(89,102,2,0,Math.PI*2,true);
ctx.fill();
}
}
// A utility function to draw a rectangle with rounded corners.
function roundedRect(ctx,x,y,width,height,radius){
ctx.beginPath();
ctx.moveTo(x,y+radius);
ctx.lineTo(x,y+height-radius);
ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
ctx.lineTo(x+width-radius,y+height);
ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
ctx.lineTo(x+width,y+radius);
ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
ctx.lineTo(x+radius,y);
ctx.quadraticCurveTo(x,y,x,y+radius);
ctx.stroke();
}
HTML5 Canvas:绘制渐变色
HTML5 Canvas渐变是一种用于填充或描边图形的颜色模式。渐变色是由不同的颜色进行过渡,而不是单一的颜色。先来看几个canvas渐变色的例子:
渐变按照类型来分可以分为两种类型:
- 线性渐变
- 径向渐变
线性渐变以线性的模式来改变颜色,也就是水平,垂直或对角方向。
径向渐变以圆形模式来改变颜色,颜色以圆形的中心向外辐射。
线性渐变
正如前面所说,线性渐变以线性的模式来改变颜色。我们可以通过2D上下文的createLinearGradient()方法来创建一个线性渐变。下面是一个例子:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
var x1 = 0;
var y1 = 0;
var x2 = 100;
var y2 = 0;
var linearGradient1 = context.createLinearGradient(x1,y1,x2,y2);
createLinearGradient()函数有4个参数:x1,y1,x2和y2。这4个参数决定渐变的方向和距离。线性渐变会从第一个点(x1,y1)扩展到第二个点(x2,y2)。
水平的线性渐变仅仅是水平方向的参数值(x1和x2)不同,例如:
var x1 = 0;
var y1 = 0;
var x2 = 100;
var y2 = 0;
var linearGradient1 = context.createLinearGradient(x1,y1,x2,y2);
垂直的线性渐变仅仅是垂直方向的参数值(y1和y2)不同,例如:
var x1 = 0;
var y1 = 0;
var x2 = 0;
var y2 = 100;
var linearGradient1 = context.createLinearGradient(x1,y1,x2,y2);
一个对角线的线性渐变水平和垂直方向上的参数都不相同,例如:
var x1 = 0;
var y1 = 0;
var x2 = 100;
var y2 = 100;
var linearGradient1 = context.createLinearGradient(x1,y1,x2,y2);
颜色停止点(Color Stops)
在上面的例子中,并没有指明线性渐变使用什么颜色。为了指明使用什么渐变颜色,可以在渐变对象上使用addColorStop()方法来指定。例如:
var linearGradient1 = context.createLinearGradient(0,0,100,0);
linearGradient1.addColorStop(0, 'rgb(255, 0, 0)');
linearGradient1.addColorStop(1, 'rgb( 0, 0, 0)');
addColorStop()方法有两个参数。第一个参数是0-1之间的一个数值,这个数值指定该颜色进入渐变多长的距离,第二个参数是颜色值,例子中使用的是rgb()颜色值。
在上面的例子中为渐变添加了两种颜色。第一种颜色是红色,设置在渐变的开始处。第二种颜色是黑色,设置在渐变的结束处。
你可以添加通过addColorStop()函数来添加更多的颜色。例如下面的例子添加了三种颜色:
var linearGradient1 = context.createLinearGradient(0,0,100,0);
linearGradient1.addColorStop(0 , 'rgb(255, 0, 0)');
linearGradient1.addColorStop(0.5, 'rgb( 0, 0, 255);
linearGradient1.addColorStop(1 , 'rgb( 0, 0, 0)');
上面的代码在渐变的中间添加了一个蓝色。整个渐变将平滑的从红色过渡到蓝色,在过渡到黑色。
使用渐变来填充和描边图形
你可以使用渐变来填充或描边图形。要填充或描边图形可以通过2D上下文的fillStyle或strokeStyle属性来完成。下面是一个示例代码:
var linearGradient1 = context.createLinearGradient(0,0,100,0);
linearGradient1.addColorStop(0 , 'rgb(255, 0, 0)');
linearGradient1.addColorStop(0.5, 'rgb( 0, 0, 255);
linearGradient1.addColorStop(1 , 'rgb( 0, 0, 0)');
context.fillStyle = linearGradient1;
context.strokeStyle = linearGradient1;
通过fillStyle或strokeStyle属性来指向渐变对象即可完成渐变的填充或描边。
现在我们可以为图形填充渐变色或描边渐变色。下面是一个例子,一个矩形的填充渐变色和一个矩形的描边渐变色。
var canvas = document.getElementById("ex2");
var context = canvas.getContext("2d");
var linearGradient1 = context.createLinearGradient(0,0,100,0);
linearGradient1.addColorStop(0 , 'rgb(246, 36, 89)');
linearGradient1.addColorStop(0.5, 'rgb( 31, 58, 147)');
linearGradient1.addColorStop(1 , 'rgb( 34, 49, 63)');
context.fillStyle = linearGradient1;
context.fillRect(10,10,100, 100);
var linearGradient2 = context.createLinearGradient(125,0, 225,0);
linearGradient2.addColorStop(0 , 'rgb(255, 0, 0)');
linearGradient2.addColorStop(0.5, 'rgb( 0, 0, 255)');
linearGradient2.addColorStop(1 , 'rgb( 0, 0, 0)');
context.strokeStyle = linearGradient2;
context.strokeRect(125, 10, 100, 100);
渐变的长度
我们在使用渐变的时候要非常明白的知道渐变的长度的概念。如果我们设置渐变从x=10扩展到x=110的距离,那么渐变只会作用在水平方向上从10到110的距离的范围内。超出这个范围的图形将任然受渐变色的影响,但是在这个范围之外的颜色只会是渐变的开始或结束颜色。
距离来说,加入有一个水平线性渐变,x1=150,x2=350。渐变从蓝色过渡到绿色。那么所有水平方向定位x值小于150的图形都会使用蓝色蓝填充。所有水平方向定位x值大于350的图形都会使用绿色来填充。只有那些在水平方向定位x值在150到350之间的图形会使用蓝绿渐变色来填充。
具体来看下面的代码,这里绘制了5个矩形,并用上面所说的渐变来填充它们,让我们蓝看看效果:
var linearGradient1 = context.createLinearGradient(150, 0, 350,0);
linearGradient1.addColorStop(0, 'rgb(0, 0, 255)');
linearGradient1.addColorStop(1, 'rgb(0, 255, 0)');
context.fillStyle = linearGradient1;
context.fillRect(10,10,130, 100);
context.fillRect(150,10, 200, 100);
context.fillRect(360,10, 130, 100);
context.fillRect(100,120, 150, 100);
context.fillRect(280,120, 150, 100);
从上面的结果可以看出,在渐变区域之外的图形仅会使用开始或结束颜色来填充。
渐变长度是非常重要的概念,需要大家仔细体会。只有掌握它才能在使用渐变是获得正确的结果。
径向渐变
径向渐变是一种圆形的颜色扩展模式,颜色从圆心位置开始向外辐射。下面是一个例子:
一个径向渐变于两个圆形来定义。每一个圆都有一个圆心和一条半径。下面是示例代码:
var x1 = 100; // 第一个圆圆心的X坐标
var y1 = 100; // 第一个圆圆心的Y坐标
var r1 = 30; // 第一个圆的半径
var x2 = 100; // 第二个圆圆心的X坐标
var y2 = 100; // 第二个圆圆心的Y坐标
var r2 = 100; // 第二个圆的半径
var radialGradient1 =
context.createRadialGradient(x1, y1, r1, x2, y2, r2);
radialGradient1.addColorStop(0, 'rgb(0, 0, 255)');
radialGradient1.addColorStop(1, 'rgb(0, 255, 0)');
context.fillStyle = radialGradient1;
context.fillRect(10,10, 200, 200);
如上面的代码所示,这里有两个圆,圆心分别为x1,y1和x2,y2,它们的半径分别为r1和r2,这些值将作为参数传入到2D上下文的createRadialGradient()方法中。
这两个圆必须设置不同的半径,形成一个内圆和一个外圆。这样渐变色就从一个圆形辐射到另一个圆形。
颜色停止点会被添加到这两个圆形之间,例如上面的代码中,第一个颜色停止点中的参数0表示该颜色从第一个圆形开始,第二个颜色停止点中的参数1表示第二种颜色从第二个圆形开始。
下面是上面代码的返回结果:
如果两个圆形的圆心位置相同,那么径向渐变将是一个完整的圆形。如果两个圆的圆心位置不相同,那么径向渐变看起来就像是一个探照灯发出的光线。例如下面的样子:
var x1 = 100;
var y1 = 100;
var r1 = 30;
var x2 = 150;
var y2 = 125;
var r2 = 100;
var radialGradient1 = context.createRadialGradient(x1, y1, r1, x2, y2, r2);
radialGradient1.addColorStop(0, 'rgb(0, 0, 255)');
radialGradient1.addColorStop(1, 'rgb(0, 255, 0)');
context.fillStyle = radialGradient1;
context.fillRect(10,10, 200, 200);
得到的结构如下所示:
HTML5 Canvas:绘制阴影和填充模式
绘制阴影
我们可以在HTML5 canvas上绘制出图形或文字的阴影效果。canvas的阴影效果非常简单,通过一些简单的设置,就可以自动在图片或文字下面生成相应的阴影。下面是一个简单的例子:
在canvas中,图形的阴影由2D上下文的4个属性来控制:
- shadowOffsetX
- shadowOffsetY
- shadowBlur
- shadowColor
shadowOffsetX和shadowOffsetY属性阴影和图形之间的距离。正数值表示阴影绘制在图形的右边(X轴方向),或图形的下方(Y轴方向)。而负数值表示阴影绘制在图形的左边(X轴方向),或图形的上方(Y轴方向)。它们的默认值都是0。
shadowBlur属性用于设置阴影的模糊效果。数值越大,阴影越模糊。数值越小,用于越清晰。它的值是一个浮点数,0表示阴影不模糊。
shadowColor表示阴影的颜色。
上面例子的实现代码如下:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.shadowOffsetX = 10;
context.shadowOffsetY = 10;
context.shadowBlur = 4;
context.shadowColor = "#666666"; //or use rgb(red, green, blue)
context.fillStyle = "#000000";
context.fillRect(10,10, 50, 50);
context.fillStyle = "#000066";
context.font = "30px Arial";
context.fillText("HTML5 Canvas Shadow", 10,120);
填充模式
填充模式是指在canvas中使用某张图片作为一种模式来填充图形。我们可以通过createPattern()方法来创建一种填充模式。它的语法为:createPattern(image, type)。
参数image可以是一个HTML图片元素,另一个canvas或一个元素等。
参数type表示如何使用图片来创建特定的模式。它的取值可以是:
- repeat:在水平和垂直方向上重复图片。
- repeat-x:只在水平方向上重复图片。
- repeat-y:只在垂直方向上重复图片。
- no-repeat:不重复图片,只显示一次。
下面是一个使用填充模式的简单例子:
var ctx = document.getElementById('canvas').getContext('2d');
// create new image object to use as pattern
var img = new Image();
img.src = 'Canvas_createpattern.png';
img.onload = function(){
// create pattern
var ptrn = ctx.createPattern(img,'repeat');
ctx.fillStyle = ptrn;
ctx.fillRect(0,0,150,150)
我们在模式中使用的图片如下:
上面代码的返回结果如下:
HTML5 Canvas:绘制文字
我们可以在HTML5 canvas上绘制绘制文字,并且可以设置文字的字体,大小和颜色。
绘制文字的字体由2D上下文的font属性来控制。如果你需要使用颜色来填充文字或制作描边文字,可以使用2D上下文的fillStyle和strokeStyle属性来完成。
要在canvas上绘制文字,可以通过2D上下文的fillText()函数或strokeText()函数来完成。下面是一个简单的例子:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.font = "normal 36px Verdana";
context.fillStyle = "#000000";
context.fillText("HTML5 Canvas Text", 50, 50);
context.font = "normal 36px Arial";
context.strokeStyle = "#000000";
context.strokeText("HTML5 Canvas Text", 50, 90);
下面的图片是上面代码的返回结果:
字体和样式
当在HTML5 canvas上绘制文字的时候,我们可以设置文字的字体和样式。我们可以通过一组2D上下文的font属性来完成这些工作。这些属性和CSS中设置字体的属性是兼容的:
[font style][font weight][font size][font face]
举例来说,我们可以这样设置字体:
context.font = "normal normal 20px Verdana";
对于上面的这些属性,我们可以有下面的一些可取值:
-
font style可取值有:
-
normal
- italic
- oblique
- inherit
-
font weight可取值有:
- normal
- bold
- bolder
- lighter
- auto
- inherit
- 100
- 200
- 300
- 400
- 500
- 600
- 700
- 800
- 900
-
font size:字体的尺寸,单位像素。
- ont face:字体。例如:verdana, arial, serif, sans-serif, cursive, fantasy, monospace等
需要注意的是,不是所有的浏览器都支持所有这些属性的,实际使用中你需要根据实际情况做一些测试。
绘制文字
当在HTML5 canvas中绘制文字的时候,你可以绘制填充文字,也可以绘制描边文字。它们分别通过2D上下文的fillText()和strokeText()函数来实现。这两个函数的定义如下:
fillText (textString, x, y [,maxWidth]);
strokeText (textString, x, y [,maxWidth]);
textString是要绘制的文字。
x和y表示文字在canvas上的位置。x参数是文字的X轴坐标,y是文字Y轴坐标,但是它如何在Y轴上定位还要取决于文本的基线。文本的基线会在后面介绍。
maxWidth表示文字在水平方向上的最大宽度。详细内容接着往下看。
下面是一个示例代码:
context.font = "36px Verdana";
context.fillStyle = "#000000";
context.fillText("HTML5 Canvas Text", 50, 50);
文本的最大宽度
可选参数maxWidth表示在canvas上绘制文字的时候,文字水平方向上最大的宽度不能大于参数指定的值。如果文本的宽度大于maxWidth指定的值,那么文字的宽度会被压缩。注意不是被剪裁掉。下面是一个例子,在canvas中绘制两串相同的文本,但是使用不同的maxWidth属性:
context.font = "36px Verdana";
context.fillStyle = "#000000";
context.fillText("HTML5 Canvas Text", 50, 50);
context.fillText("HTML5 Canvas Text", 50, 100, 200);
上面的代码的返回结果如下,注意第二串文字被压缩为总宽度200像素:
文字的颜色
文字的填充或描边颜色是通过2D上下文的fillStyle和strokeStyle属性来完成的。实现方式和图形的方式相同。可以参考HTML5 Canvas:绘制矩形中的介绍,这里不再重复。
测量文本的宽度
在2D上下文中有一个函数可以用于测量文本的宽度,返回以像素为单位的结果值。这个函数不能测量高度。这个函数是measureText()。下面是一段示例代码:
var textMetrics = context.measureText(“measure this”);
var width = textMetrics.width;
通过测量文本的宽度,我们可以知道当前的这些文字是否能够放在当前的canvas容器中,或者其它一些用途。
文本的基线
文本的基线用于决定如何解释fillText()和strokeText()函数中的y参数。通俗来讲,就是文字在垂直方向上如何定位。注意,在不同的浏览器中,这些解释可能会稍微有一些不同。
可以通过2D上下文的textBaseline属性来设置文本的基线。
文本基线的语法为:
ctx.textBaseline=
"top" || "hanging" || "middle" || "alphabetic" || "ideographic" || "bottom";
下表中列出了文本基线的可取值及其描述。
取值 | 描述
top | 文本以文本中最高的字符为基线进行对齐
hanging | 文本的基线是悬停线(hanging baseline)。它和top取值基本相同,多数情况下你可能看不出有什么区别
middle | 文本的基线是文字的中心线
alphabetic | 文本的基线是正常的文字基线
ideographic | 文本的基线是水平方向的字形底部
bottom | 文本以文本中最低的字符为基线进行对齐
看了上面的描述大家会一头雾水。现在举例来说明。我们使用相同的y值(75)来绘制一串文本但是为每一个文字设置不同的基线值。在图片中我们绘制一条y=75的执行来表示所有文字的基线。
上面图片的实现代码如下:
context.stokeStyle = "#000000";
context.lineWidth = 1;
context.beginPath();
context.moveTo( 0, 75);
context.lineTo(500, 75);
context.stroke();
context.closePath();
context.font = "16px Verdana";
context.fillStyle = "#000000";
context.textBaseline = "top";
context.fillText("top", 0, 75);
context.textBaseline = "hanging";
context.fillText("hanging", 40, 75);
context.textBaseline = "middle";
context.fillText("middle", 120, 75);
context.textBaseline = "alphabetic";
context.fillText("alphabetic", 200, 75);
context.textBaseline = "ideographic";
context.fillText("ideographic", 300, 75);
context.textBaseline = "bottom";
context.fillText("bottom-glyph", 400, 75);
文本对齐
2D上下文的textAlignment属性用于定义文字在水平方向上如何对齐。换句话来说,就是textAlignment属性定义绘制文本时文本的x坐标属性。
textAlignment属性的语法为:
ctx.textAlign = "left" || "right" || "center" || "start" || "end";
下表中列出了textAlignment属性各个取值及其描述。
取值 | 描述
start | 文本从x左边开始绘制
left | 文本从x左边开始绘制,和start属性相同
center | x坐标位于文本的中心
end | x坐标位于文本的末尾
right | x坐标位于文本的末尾,和end属性相同
在下面的例子中显示了textAlignment属性的各种取值的定位。垂直直线的x坐标为x=250,所有的字的x属性值也是250,但是它们的textAlignment属性取值各不相同。
上面的图片的实现代码如下:
context.stokeStyle = "#000000";
context.lineWidth = 1;
context.beginPath();
context.moveTo( 250, 0);
context.lineTo( 250, 250);
context.stroke();
context.closePath();
context.font = "16px Verdana";
context.fillStyle = "#000000";
context.textAlign = "center";
context.fillText("center", 250, 20);
context.textAlign = "start";
context.fillText("start", 250, 40);
context.textAlign = "end";
context.fillText("end", 250, 60);
context.textAlign = "left";
context.fillText("left", 250, 80);
context.textAlign = "right";
context.fillText("right", 250, 100);
HTML5 Canvas:绘制图片
通过前面的学习,我们现在已经可以在HTML5 canvas中绘制图形和文字,并给它们设置一些样式。我们还可以在<canvas>中绘制图片。用于在<canvas>作为绘制源的图片可以是下面的几种元素类型:
- HTMLImageElement:可以是由Image()构造函数创建的图片,也可以是任何的元素。
- HTMLVideoElement:使用一个HTML<video>元素作为图片源,会从视频中截取当前帧作为图片源。
-
HTMLCanvasElement:也可以使用另一个<canvas>元素作为图片源。
绘制图片
我们可以通过2D上下文的三种方法来在<canvas>中绘制图片。
- drawImage(image, dx, dy);
- drawImage(image, dx, dy, dw, dh);
- drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dw, dh);
基本绘制图片方法:drawImage(image, dx, dy)
这个方法是在<canvas>中绘制一张图片。image参数是要绘制的图片,dx和dy参数是“destinationX”和“destinationY”的简写,这两个参数决定在canvas中什么位置绘制图片。
下面是一个例子。这个例子在<canvas>中(0,0)位置开始绘制指定的图片。
var ctx = document.getElementById('ex1').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
};
img.src = 'img/canvas-image-1.jpg';
上面的代码的返回结果如下:
绘制并缩放图片:drawImage(image, dx, dy, dw, dh)
第二种在<canvas>中绘制图片的方法添加了两个参数:dw和dh,这两个参数分别是“destinationWidth”和“destinationHeight”的简写,它们决定在绘制图片时是否对图片进行缩放。
下面的例子中,我们将绘制的图片缩小1/3左右,然后将它重复排列形成一个网格。
var ctx = document.getElementById('ex2').getContext('2d');
var img = new Image();
img.onload = function(){
for (var i=0;i<4;i++){
for (var j=0;j<5;j++){
ctx.drawImage(img,j*60,i*60,60,60);
}
}
};
img.src = 'img/canvas-image-2.jpg';
上面的代码的返回结果如下:
图片切片方法:drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dw, dh)
第三种在<canvas>中绘制图片的方法有8个参数。image是源图片,sx和sy是“sourceX”和“sourceY”的简写,这两个参数决定从什么位置开始在源图片上裁剪出一个矩形区域,这个区域的图片将会被绘制在Canvas中。sWidth和sHeight表示矩形区域的宽度和高度。剩下的4个参数和上面的绘制图片方法中的描述相同。看下面的图片,图片上标出了各个参数的位置。
来看下面的一个例子。这个例子中,我们将源图片剪裁出一部分,然后将它绘制在canvas的一个边框图片之上。
var canvas = document.getElementById('ex3');
var ctx = canvas.getContext('2d');
// 绘制图片切片
ctx.drawImage(document.getElementById('source'),
98, 205, 104, 124, 21, 20, 87, 104);
// 绘制边框图片
ctx.drawImage(document.getElementById('frame'),0,0);
上面的代码得到的结果如下:
创建和调用图片
在你能够在Canvas中绘制图片之前,你需要创建一个Image对象,然后将图片加载到内存中。下面是完成这个操作的js代码:
var image = new Image();
image.src = "img/sample.png";
在你能够绘制图片之前,图片必须被完全加载。为了确保图片被完全加载,你可以为图片添加一个事件监听,这个事件监听中的方法会在图片被完全加载之后被调用。下面是一个示例代码:
image.addEventListener('load', drawImage1);
或者:
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
};
HTML5 Canvas:合成模式
在我们前面所有HTML5 canvas的例子中,图形的绘制都是一个图形位于另一个图形之上的。我们可以通过设置globalCompositeOperation属性来修改这个默认的行为。换句话来说,我们可以设置绘制的图形与已经绘制在canvas上的图形的合成模式。
2D上下文有两个属性用于控制canvas的图形合成模式:
- globalAlpha
- globalCompositeOperation
globalAlpha
globalAlpha属性决定你绘制图形的透明度。它的取值可以在0-1之间。0代表绘制的图形完全透明,1表示绘制的图形完全不透明。0.5则表示绘制的图形处于半透明状态。默认的取值为1。
设置globalAlpha属性的js代码如下:
context.globalAlpha = 0.5;
globalCompositeOperation
globalCompositeOperation属性决定绘制的图形如何与canvas上已经存在的图形进行混合。
globalCompositeOperation的语法如下:
globalCompositeOperation = type
globalCompositeOperation的值引用一个“源”和一个“目标”,并且指定源和目标如何进行合成。“源”就是你要绘制的图形,“目标”是已经绘制在canvas上的元素。下面列出了globalCompositeOperation属性的可选值和它们的描述。
- source-over:这是默认的取值,表示绘制的图形位于canvas中已经存在的图形之上。
- source-in:新的图形和目标canvas中的图形重叠且都不透明的部分才被绘制。其它部分使用不同透明度来显示。
- source-out:新的图形和canvas上已经存在的图形不重叠的部分会被绘制。
- source-atop:只有和canvas内容重叠的部分的新图形会被绘制。
canvas的source-atop合成模式
- destination-over:在已经存在的canvas内容后面的新图形会被绘制。
- destination-in:新图形和已经存在的canvas内容重叠的部分的canvas内容会被保留。其他部分显示为透明。
canvas的destination-in合成模式
- destination-out:新图形和已经存在的canvas内容不重叠的部分的canvas内容会被保留。
- destination-atop:已经存在的canvas内容只有和新图形重叠部分会被保留。新图形绘制在canvas内容的后面。
canvas的destination-atop合成模式
- lighter:源和目标的重叠部分的颜色由添加的颜色值决定。
- copy:源和目标重叠的部分,源被显示出来。
- xor:源和目标重叠的部分变为透明,其它部分正常绘制。
- multiply:顶层的像素和相应的底层的像素相乘。图片会变暗。
- screen:像素会被反转,相乘,然后再反转。返回的图片会变亮(和multiply相反)。
- overlay:multiply 和 screen模式的组合。暗的图层会变得更暗,亮的图层会变得更亮。
- darken:保留源和目标的最暗像素。
- lighten:保留源和目标的最亮像素。
- color-dodge:通过反转的源图层来分类底部的目标图层。
- color-burn:通过源图层来分离反转的底部图层,然后在将结果反转。
- hard-light:类似multiply 和 screen的组合,但是源和目标图层交换。
- soft-light:柔光模式。纯黑色或白色不会产生纯黑色或白色。
- difference:从顶层减去底层。
- exclusion:和difference模式相同,但是基于低对比度。
- hue:保留底层的亮度和色度,采用顶层的色调(hue)。
-
saturation:保留底层的亮度和色度,采用顶层的色度(chroma )。
-
color:保留底层的亮度和色度,采用顶层的色调(hue)和色度(chroma )。
-
luminosity:保留底层的亮度和色度,采用顶层的亮度。
HTML5 Canvas:图形转换
你可以对任何在HTML5 canvas中绘制的图形应用转换效果。下面是可以使用的转换列表:
- 移动
- 旋转
- 缩放
移动
我们可以将绘制在canvas上的任何图形进行移动操作。移动意味着要重新定位绘制的图形。下面是一个示例代码:
var x = 100;
var y = 50;
context.translate(x, y);
上面的代码将canvas中已经绘制的所有元素在水平方向上移动100,在垂直方向上移动50。
注意:移动操作仅仅对那些在translate()函数被调用之后才绘制的图形有效果,之前绘制的图形是不会移动的。
下面是另一个例子。两个矩形被绘制于同样的坐标系,长度和宽度也相等。但是其中一个绘制在translate()函数之前,另一个绘制在translate()函数之后。来看看效果:
context.fillStyle = "#f62459";
context.fillRect(10,10, 100, 100);
context.translate(50, 25);
context.fillStyle = "#4183d7";
context.fillRect(10,10, 100, 100);
得到的结果如下图所示:
旋转
你也可以将绘制在canvas上的元素进行旋转。旋转操作可以通过2D上下文的rotate()函数来完成。
context.rotate(radians);
旋转的角度作为参数传递到rotate()函数中。这个角度必须是一个弧度值,而不能是角度值。
所有的旋转图形都将绕canvas的(0,0)坐标点旋转,这是canvas的左上角位置。
和移动操作相同,所有绘制在rotate()函数之后的图形都会被旋转。
下面的例子绘制了两个相同的矩形,一个绘制在rotate()函数之前,一个绘制在rotate()函数之后。
context.fillStyle = "#f62459";
context.fillRect(60,20, 100, 100);
context.rotate( (Math.PI / 180) * 25); //旋转25度
context.fillStyle = "#4183d7";
context.fillRect(60,20, 100, 100);
得到的结果如下图所示:
绕不同的点进行旋转
正如上面提到的,所有的图形都将绕canvas的左上角进行旋转。如果我们想要图形绕其它位置进行旋转,该怎么办呢?例如,我们需要图形绕自己的中心点进行旋转。
为了使图形绕自己的中心点进行旋转,你必须首先将canvas移动到图形的中心点,然后执行旋转操作,再将canvas移动回(0,0)位置,再绘制图形。
下面的代码演示了图形如何绕自己的中心点进行旋转。
var x = 10;
var y = 10;
var width = 100;
var height = 100
var cx = x + 0.5 * width; // 图形的X轴中心
var cy = y + 0.5 * height; // 图形的Y轴中心
context.fillStyle = "#ff0000";
context.fillRect(x, y, width, height); //绘制正常的图形
context.translate(cx, cy); //移动到图形的中心
context.rotate( (Math.PI / 180) * 25); //旋转25度
context.translate(-cx, -cy); //将中心点移动回(0,0)
context.fillStyle = "#0000ff";
context.fillRect(x, y, width, height);
得到的结果如下所示:
这个例子首先将canvas的旋转点移动到矩形的中心(cx, cy),然后旋转canvas 25度,再接着将canvas的旋转点移动回(0,0)位置。现在,canvas绕(cx,cy)旋转了25度。这时所有绘制的东西都将绕(cx,cy)旋转25度。因此,接下来绘制的矩形也会绕(cx,cy)旋转25度。
注意,矩形的坐标系统并没有改变,你可以看到红色和蓝色的矩形的fillRect方法使用的是相同的参数。这里仅仅是转换操作使两个矩形位于不同的位置和旋转角度。
缩放
我们还可以实现将绘制在HTML5 canavs中的图形自动进行缩放。
在进行缩放的时候,x轴和y轴的坐标系统会按一定比例进行缩放。这个缩放比例可以通过scale()函数来设置。例如:
var scaleX = 2;
var scaleY = 2;
context.scale(scaleX, scaleY);
这个例子将X轴和Y轴的坐标系统都放大2倍。
和translate()和rotate()函数相同,所有在scale()函数被调用之后绘制的图形才会被缩放。来看下面的例子:
var x = 10;
var y = 10;
var width = 100;
var height = 100
context.fillStyle = "#ff0000";
context.fillRect(x, y, width, height);
context.scale(2,2);
context.fillStyle = "#0000ff";
context.fillRect(x, y, width, height);
上面的代码得到的结果如下:
现在,蓝色的矩形被放大了2倍。注意观察,蓝色的矩形距离canvas左上角(0,0)位置的距离也被放大了2倍。这是因为蓝色矩形的所有坐标系都被放大了2倍。如果你不想在缩放图形的时候使它移动,你要在缩放的同时还要对它进行移动操作。
HTML5 Canvas:绘图状态和状态栈
当我们在HTML5 canvas中使用2D上下文来绘制图形的时候,2D上下文会处于某种状态中。你可以通过操纵2D上下文的属性来设置这些状态,例如fillStyle属性和strokeStyle属性。所有的这些操作被称为2D上下文的state(状态)。
有时候,我们在canvas上绘制图形的时候,经常需要改变2D上下文的状态。举例来说,你在绘制直线或矩形的时候需要一种strokStyle,在绘制下一条直线或矩形的时候需要另一种strokStyle。又或者是不同的填充色,旋转角度等等。
我们不可能在绘制图形之前就设置好所有图形的状态,但是我们可以将当前的状态压栈到一个状态栈中。在这个状态栈中,最后压入的状态将最先被弹出。通过这种方式我们可以非常方便的恢复到前一次的绘图状态。
HTML5 canvas绘图状态的例子
将一个绘图状态进行压栈和出栈的方法如下:
context.save(); // 将一个状态压入状态栈中
context.restore(); // 将最前面的状态出栈,并设置到2d上下文中
对于一个状态栈,你可以压入多个状态,然后在将它们依次弹出。来看下面的例子:
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.fillStyle ="#66ff66";
context.strokeStyle="#990000";
context.lineWidth = 5;
context.fillRect (5, 5, 50, 50);
context.strokeRect(5, 5, 50, 50);
context.save();
context.fillStyle = "#6666ff";
context.fillRect (65, 5, 50, 50);
context.strokeRect(65, 5, 50, 50);
context.save();
context.strokeStyle = "#000099";
context.fillRect (125, 5, 50, 50);
context.strokeRect(125, 5, 50, 50);
context.restore();
context.fillRect (185, 5, 50, 50);
context.strokeRect(185, 5, 50, 50);
context.restore();
context.fillRect (245, 5, 50, 50);
context.strokeRect(245, 5, 50, 50);
上面的代码得到的结果如下:
状态栈的用处
状态栈对于改变canvas的合成模式,图形的转换设置和在需要回到以前设置的状态的场景中十分有用。
通过保存和恢复合成模式或图形转换设置,你可以确保它们被正确的重置。否则,你要想恢复到以前设置的某种状态时十分困难的。
2D上下文的状态有哪些?
所有的2D上下文的属性都是可以保存和恢复的属性。你在恢复一个状态的时候,绘制区域并不会自动进行恢复。你恢复的仅仅是2D上下文的设置(属性值),这些设置包括:
- fillStyle
- font
- globalAlpha
- globalCompositionOperation
- lineCap
- lineJoin
- lineWidth
- miterLimit
- shadowBlur
- shadowColor
- shadowOffsetX
- shadowOffsetY
- strokeStyle
- textAlign
- textBaseline
- clipping区域
- 转换矩阵
上面的列表并不是完整的列表。还有更多的属性属于2D上下文状态的一部分。
HTML5 Canvas:获取canvas内容-toDataURL()
我们可以通过canvas的toDataURL()方法来获取绘制在HTML5 canvas中的内容。做法类似下面的示例代码:
var canvas = document.getElementById("ex1");
var dataUrl = canvas.toDataURL();
从toDataURL()方法中返回的数据是一个经过编码的URL,它里面包含了从canvas中抓取的图形的数据。我们可以将这些数据显示在一个文本框中,例如:
var canvas = document.getElementById("ex1");
var dataUrl = canvas.toDataURL();
document.getElementById(“textArea”).value = dataUrl;
我们还可以将抓取到的图形数据显示在一个新的窗口中,例如:
var canvas = document.getElementById("ex1");
var dataUrl = canvas.toDataURL();
window.open(dataUrl,"toDataURL() image", "width=600, height=200");
HTML5 Canvas:像素处理
我们可以直接从HTML5 canvas中获取单个像素。通过ImageData对象我们可以以读写一个数据数组的方式来操纵像素数据。当完成像素操作之后,如果要显示它们,需要将这些像素复制到canvas上。
创建一个ImageData 对象
要创建一个ImageData对象,可以使用2D上下文的createImageData()方法。
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
var width = 100;
var height = 100;
var imageData = context.createImageData(width, height);
ImageData对象代表canvas中某个区域的底层像素数据。它包含三个只读的属性:
width:图像的宽度,单位像素。
height:图像的高度,单位像素。
data:包含像素值的一维数组。
上面的例子中创建了一个100x100像素的ImageData对象。
管理像素
在data数组中的每一个像素包含4个字节的值。也就是说每一个像素由4个字节表示,每一个字节分别表示红色,绿色,蓝色和一个透明度alpha通道(RGBA)。像素的颜色由红、绿、蓝混合得到的最终颜色决定。透明度alpha通道决定这个像素的透明度。红、绿、蓝和alpha通道的值都在0-255之间。这和photoshop中的光的三原色RGB的原理是相同的。
要读取一个像素的值,你可以使用下面的代码:
var pixelIndex = 0;
var red = imageData.data[pixelIndex ]; // 红色
var green = imageData.data[pixelIndex + 1]; // 绿色
var blue = imageData.data[pixelIndex + 2]; // 蓝色
var alpha = imageData.data[pixelIndex + 3]; // 透明度
如果要接收后面的像素值,可以增加pixelIndex的值为4的倍数。你可以通过下面的方法来计算给定像素的index值。
var index = 4 * (x + y * width);
在上面的语句中,x和y表示该像素在像素网格中的坐标位置。data数组中的像素会被初始化为一个很长的像素序列网格。它从左上角开始,然后向前移动。当到达一行的末尾时,接着从下一行开始显示。
下面的图像时一个20像素宽,8像素高的ImageData像素数组。如图所示,序列从左上角开始,然后向右移动,当到达一行的最大时再换行显示。
复制像素到canvas上
当你完成了像素操作,你可以使用2D上下文的putImageData()函数将它们复制到canvas上。putImageData()函数有两种格式。第一种格式是复制所有的像素到canvas中。下面是一个示例代码:
var canvasX = 25;
var canvasY = 25;
context.putImageData(imageData, canvasX, canvasY);
canvasX和canvasY参数是canvas上插入像素的x和y坐标。
第二种格式的putImageData()函数可以复制一个矩形区域的像素到canvas中。下面是一个示例代码:
var canvasX = 25;
var canvasY = 25;
var sx = 0;
var sy = 0;
var sWidth = 25;
var sHeight = 25;
context.putImageData(imageData, canvasX, canvasY,
sx, sy, sWidth, sHeight);
sx和sy参数(sourceX 和 sourceY)是矩形左上角的x和y坐标。
sWidth和sHeight参数(sourceWidth 和 sourceHeight)是矩形的宽度和高度。
从canvas中获取像素
我们也可以从canvas上获取一个矩形区域的像素到一个ImageData对象中。通过getImageData函数可以完成这个操作。例如下面的代码:
var x = 25;
var y = 25;
var width = 100;
var height = 100;
var imageData2 = context.getImageData(x, y, width, height);
x和y参数是从canvas上获取的矩形的左上角的坐标。
width和height参数是从canvas上获取的矩形的宽度和高度。
HTML5 Canvas:制作动画特效
要在HTML5 canvas中绘制图像动画效果,你需要绘制出每一帧的图像,然后在一个极短的时间内从一帧过渡到下一帧,形成动画效果。这其实是一种视觉欺骗,原理就像播放电影一样,胶片上每一格是一帧图片,然后快速的播放它们,在人的眼睛看来就是一个完整的动画效果。
制作canvas动画的基本步骤
下面是你在canvas上绘制一个动画帧的基本步骤:
1、清空canvas:除了背景图像之外,你需要清空之前绘制的所有图形。
2、保存canvas的状态:如果在这一步中你使用了不同的绘图状态(例如描边大小和填充色等),并且你想在绘制每一帧时使用相同的原始状态,你需要保存这些原始状态。
3、绘制动画图形:这一步中你需要绘制那些动画的图形元素。
4、恢复canvas状态:如果你之前保存过canvas的状态,在这一步中将它们恢复。
控制canvas动画
我们需要一种方法来在指定时间内执行我们的绘制图形函数。有两种方式可以控制动画的执行。
第一种是使用下面的三个window对象上的方法:window.setInterval(),window.setTimeout()和window.requestAnimationFrame()。它们都能在指定时间内调用指定的函数。
- setInterval(function, delay):在每delay毫秒时间内反复执行-function指定的函数。
- setTimeout(function, delay):在delay毫秒内执行function指定的函数。
- requestAnimationFrame(callback):通知浏览器你需要执行一个动画,并请求浏览器调用指定的函数来在下一次重绘前更新动画。
第二种方法是使用事件监听。例如你需要做一个小游戏,你可以监听键盘和鼠标的事件,然后在捕获相应的事件时使用setTimeout()方法来制作动画效果。
为了获得更好的动画性能,我们通常使用requestAnimationFrame()方法。当浏览器装备好绘制下一帧动画时,我们可以将绘制函数作为参数传入这个方法中。
通过在浏览器准备画下一帧的时候,给浏览器发出信号,可以使浏览器对你的动画进行硬件加速,这比使用setTimeout()来绘制动画效果会好得多。
下面是一段示例代码:
function animate() {
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(animate);
draw();
}
animate()函数首先会获取requestAnimationFrame()函数的一个引用。注意在不同的浏览器中会使用不同的名称。变量reqAnimFrame会在不同的浏览器中设置为不同的值,总之它不能为空。
然后reqAnimFrame()方法被调用,并将animate()函数作为参数传入。当浏览器准备好绘制下一帧动画时,animate()函数就会被调用。
最后,animate()函数会调用draw()方法。draw()方法在上面的代码中没有写出来,它实际上做的事情就是前面提到的绘制一个动画帧的4个步骤:清空canvas,保存状态,绘制图形,恢复状态。
还有一件需要注意的事情是animate()函数必须被调用一次来启动动画,否则requestAnimationFrame()函数将永远不会被调用,动画也不会被正常执行。
下面是一个小例子:一个小矩形在canvas中来回不停的运动。
上面canvas动画的实现代码如下:
var x = 0;
var y = 15;
var speed = 5;
function animate() {
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(animate);
x += speed;
if(x <= 0 || x >= 475){
speed = -speed;
}
draw();
}
function draw() {
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.clearRect(0, 0, 500, 170);
context.fillStyle = "#ff00ff";
context.fillRect(x, y, 25, 25);
}
animate();
canvas动画示例
下面是一个地球绕太阳以及月亮绕地球旋转的canvas动画效果。
上面效果的实现代码如下:
var sun = new Image();
var moon = new Image();
var earth = new Image();
function init(){
sun.src = 'img/Canvas_sun.png';
moon.src = 'img/Canvas_moon.png';
earth.src = 'img/Canvas_earth.png';
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(drawSolarSystem);
}
function drawSolarSystem () {
var ctx = document.getElementById('ex2').getContext('2d');
ctx.globalCompositeOperation = 'destination-over';
ctx.clearRect(0,0,300,300); // clear canvas
ctx.fillStyle = 'rgba(0,0,0,0.4)';
ctx.strokeStyle = 'rgba(0,153,255,0.4)';
ctx.save();
ctx.translate(150,150);
// Earth
var time = new Date();
ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() );
ctx.translate(105,0);
ctx.fillRect(0,-12,50,24); // Shadow
ctx.drawImage(earth,-12,-12);
// Moon
ctx.save();
ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() );
ctx.translate(0,28.5);
ctx.drawImage(moon,-3.5,-3.5);
ctx.restore();
ctx.restore();
ctx.beginPath();
ctx.arc(150,150,105,0,Math.PI*2,false); // Earth orbit
ctx.stroke();
ctx.drawImage(sun,0,0,300,300);
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(drawSolarSystem);
}
init();
接下来的例子是一个模拟时钟动画的例子。
模拟时钟动画的实现代码如下:
function clock(){
var now = new Date();
var ctx = document.getElementById('ex3').getContext('2d');
ctx.save();
ctx.clearRect(0,0,150,150);
ctx.translate(75,75);
ctx.scale(0.4,0.4);
ctx.rotate(-Math.PI/2);
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
ctx.lineWidth = 8;
ctx.lineCap = "round";
// Hour marks
ctx.save();
for (var i=0;i<12;i++){
ctx.beginPath();
ctx.rotate(Math.PI/6);
ctx.moveTo(100,0);
ctx.lineTo(120,0);
ctx.stroke();
}
ctx.restore();
// Minute marks
ctx.save();
ctx.lineWidth = 5;
for (i=0;i<60;i++){
if (i%5!=0) {
ctx.beginPath();
ctx.moveTo(117,0);
ctx.lineTo(120,0);
ctx.stroke();
}
ctx.rotate(Math.PI/30);
}
ctx.restore();
var sec = now.getSeconds();
var min = now.getMinutes();
var hr = now.getHours();
hr = hr>=12 ? hr-12 : hr;
ctx.fillStyle = "black";
// write Hours
ctx.save();
ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec )
ctx.lineWidth = 14;
ctx.beginPath();
ctx.moveTo(-20,0);
ctx.lineTo(80,0);
ctx.stroke();
ctx.restore();
// write Minutes
ctx.save();
ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec )
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(-28,0);
ctx.lineTo(112,0);
ctx.stroke();
ctx.restore();
// Write seconds
ctx.save();
ctx.rotate(sec * Math.PI/30);
ctx.strokeStyle = "#D40000";
ctx.fillStyle = "#D40000";
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(-30,0);
ctx.lineTo(83,0);
ctx.stroke();
ctx.beginPath();
ctx.arc(0,0,10,0,Math.PI*2,true);
ctx.fill();
ctx.beginPath();
ctx.arc(95,0,10,0,Math.PI*2,true);
ctx.stroke();
ctx.fillStyle = "rgba(0,0,0,0)";
ctx.arc(0,0,3,0,Math.PI*2,true);
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.lineWidth = 14;
ctx.strokeStyle = '#325FA2';
ctx.arc(0,0,142,0,Math.PI*2,true);
ctx.stroke();
ctx.restore();
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(clock);
}
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(clock);