Canvas在线教程

Canvas在线教程

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样式的线头会在线的末端绘制一个矩形。矩形的大小为:线条的宽度 X 线条的宽/2。

下面是几个不同线头样式的线条的例子。所有的线条的宽度都是10。最总版的一组线条的lineCap的取值为butt,中间的一组线条的lineCap的取值为round,最右边的一组线条的lineCap的取值为square。

lineCap的取值butt和square非常相似。有时难以区别。这里制作了几个小例子,从这些例子中你可以看出它们之间的微小差别。下面又三组线条,每一组左边(或上边)的线条的lineCap属性取值为butt,右边(或下边)的线条的lineCap属性取值为square。

正如上面的结果所示,square线头的线条要比butt线头的线条要长。

线条的连接

2D上下文的lineJoin属性用于定义两条线条连接处的点如何绘制。两条线条连接处的点被称为“连接点”。lineJoin属性有下面的三种取值:

下面是这三种取值的示例代码:

context.lineJoin = "miter";
context.lineJoin = "bevel";
context.lineJoin = "round"; 

miter的连接点是一个三角形的连接点。

bevel的连接点是一个平头的连接点。

round的连接点是一个圆角的连接点。

下面分别是三种线条连接点的例子,从左到右的lineJoin属性分别是:miter,bevel和round。

绘制曲线

2D上下文的arc()函数可以用于绘制一条曲线。arc()函数有6个参数:

下面是一段示例代码:

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属性阴影和图形之间的距离。正数值表示阴影绘制在图形的右边(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表示如何使用图片来创建特定的模式。它的取值可以是:

下面是一个使用填充模式的简单例子:

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";     

对于上面的这些属性,我们可以有下面的一些可取值:

需要注意的是,不是所有的浏览器都支持所有这些属性的,实际使用中你需要根据实际情况做一些测试。

绘制文字

当在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>作为绘制源的图片可以是下面的几种元素类型:

我们可以通过2D上下文的三种方法来在<canvas>中绘制图片。

基本绘制图片方法: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

globalAlpha属性决定你绘制图形的透明度。它的取值可以在0-1之间。0代表绘制的图形完全透明,1表示绘制的图形完全不透明。0.5则表示绘制的图形处于半透明状态。默认的取值为1。

设置globalAlpha属性的js代码如下:

context.globalAlpha = 0.5;             

globalCompositeOperation

globalCompositeOperation属性决定绘制的图形如何与canvas上已经存在的图形进行混合。

globalCompositeOperation的语法如下:

globalCompositeOperation = type       

globalCompositeOperation的值引用一个“源”和一个“目标”,并且指定源和目标如何进行合成。“源”就是你要绘制的图形,“目标”是已经绘制在canvas上的元素。下面列出了globalCompositeOperation属性的可选值和它们的描述。

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上下文的设置(属性值),这些设置包括:

上面的列表并不是完整的列表。还有更多的属性属于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()。它们都能在指定时间内调用指定的函数。

为了获得更好的动画性能,我们通常使用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);         

底部

返回顶部