JavaScript图形实例:Canvas API

Posted I am a teacher!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript图形实例:Canvas API相关的知识,希望对你有一定的参考价值。

1.Canvas概述

      Canvas API(画布)用于在网页实时生成图像,并且可以操作图像内容,基本上它是一个可以用javascript操作的位图(bitmap)。

      要使用html5在浏览器窗口中绘制图形,首先需要在HTML文档中新建一个canvas网页元素。一般方法如下:

       <canvas id="myCanvas" width="400" height="300">

             您的浏览器不支持canvas!

      </canvas>

      上面这段代码,表示建立了一个名为“myCanvas”的canvas网页元素,它就是一块画布,该画布的宽为400,高为300。有了这块画布,我们就可以使用JavaScript编写程序,利用Canvas API在这块画布上绘制图形。如果所用浏览器不支持Canvas API,则就会显示canvas标签中间的文字——“您的浏览器不支持canvas!”。

      每个canvas网页元素都有一个对应的context对象(上下文对象),Canvas API定义在这个context对象上面。为了在canvas上绘制图形,必须先得到一个画布上下文对象的引用。为此,使用JavaScript编写程序段如下:

      var canvas = document.getElementById(\'myCanvas\');  // 取得网页中的画布对象

      var ctx = canvas.getContext(\'2d\');       // 得到画布上下文对象ctx

      上面代码中,getContext方法指定参数2d,表示该canvas对象用于生成2D图案(即平面图案)。如果参数是3d,就表示用于生成3D图像(即立体图案)。

      当使用一个canvas元素的getContext(“2d”)方法时,返回的是CanvasRenderingContext2D对象,其内部表现为笛卡尔平面坐标。这就是Canvas画布提供的一个用来作图的平面空间,该空间的每个点都有自己的坐标,x表示横坐标,y表示纵坐标。原点(0, 0)位于画布左上角,x轴的正向是原点向右,y轴的正向是原点向下。

      每一个canvas元素仅有一个上下文对象。得到了这个上下文对象,就可以利用这个对象的属性和方法进行图形绘制了。

2.绘图方法

2.1  绘制路径

       在Canvas API中,上下文CanvasRenderingContext2D对象提供了一系列与图形绘制相关的属性和方法。其中,与路径绘制相关的方法如下:

       void beginPath();                  //  开始绘制路径

       void closePath();                   // 结束路径绘制

       void moveTo(in float x, in float y);    // 设置线段的起点

       void lineTo(in float x, in float y);      // 设置线段的终点

       void bezierCurveTo(in float cp1x, in float cp1y, in float cp2x, in float cp2y, in float x, in float y);               // 绘制一条三次贝塞尔曲线

       void quadraticCurveTo(in float cpx, in float cpy, in float x, infloat y);                   // 绘制一条二次贝塞尔曲线

       void stroke();         // 给透明的线段着色,从而完成线段绘制

       void fill();            // 给闭合路径填充颜色,填充色由fillStyle属性指定

       与绘制路径相关的属性有:

       attribute float lineWidth;      // 线的宽度,默认为1

       attribute any strokeStyle;      // 着色的颜色,默认为black(黑色)

       attribute any fillStyle;         // 填充颜色,默认为black(黑色)

       attribute DOMString lineCap;  // 线段的箭头样式,仅有三个选项:butt(默认值)、round、square,其他值忽略

       下面通过几个例子来说明绘制路径的方法和属性的使用。

      例1  绘制一条从(20,20)到(200,20)的一条红色横线。

<!DOCTYPE html>

<head>

<title>绘图方法的使用</title>

<script type="text/javascript">

  function draw(id)

  {

     var canvas=document.getElementById(id);

     if (canvas==null)

        return false;

     var ctx=canvas.getContext(\'2d\');

     ctx.beginPath();        // 开始路径绘制

     ctx.moveTo(20, 20);    // 设置路径起点,坐标为(20,20)

     ctx.lineTo(200, 20);    // 绘制一条到(200,20)的直线

     ctx.lineWidth = 1.0;    // 设置线宽

     ctx.strokeStyle = "#FF0000"; // 设置线条颜色为红色

     ctx.stroke();          // 进行线的着色,这时整条线才变得可见

   }

</script>

</head>

<body onload="draw(\'myCanvas\');">

<canvas id="myCanvas" width="400" height="300" style="border:3px double #996633;">

</canvas>

</body>

</html>

       将上述HTML代码保存到一个html文本文件中,再在浏览器中打开包含这段HTML代码的html文件,可以看到在画布中绘制出一条长为180的红色横线。

       下面的例子中我们不再给出完整的HTML文件内容,只给出JavaScript编写的与图形绘制直接相关的代码。例如例1源文件中加了注释的6条语句。读者需要自己试一试时,只需把下列各例给出的代码去覆盖例1中的6条注释语句,其余部分保持不变即可。

       在绘制路径时,moveto和lineto方法可以多次使用。最后,还可以使用closePath方法,自动绘制一条当前点到起点的线段,形成一个封闭图形,省却使用一次lineto方法。

       例2  在画布中绘制一个红色边框的直角三角形和一个蓝色边框的等腰三角形。

     ctx.beginPath();

     ctx.moveTo(20,20);

     ctx.lineTo(20,100);   // 垂直直角边

     ctx.lineTo(70,100);   // 水平直角边

     ctx.lineTo(20,20);    // 斜边

     ctx.strokeStyle="red";

     ctx.stroke();         // 进行着色,使得线段可见

     ctx.beginPath();

     ctx.moveTo(40,120);

     ctx.lineTo(20,180);   // 左边的腰

     ctx.lineTo(60,180);   // 底边

     ctx.closePath();      // 右边的腰是通过自动封闭绘制得到

     ctx.strokeStyle="blue";

     ctx.stroke();  

       例3  线段箭头的三种样式的比较。

ctx.lineWidth=10;

ctx.strokeStyle="red";

ctx.beginPath();

ctx.lineCap=\'butt\';

ctx.moveTo(100,50);

ctx.lineTo(250,50);

ctx.stroke();

ctx.beginPath();

ctx.lineCap=\'round\';

ctx.moveTo(100,80);

ctx.lineTo(250,80);

ctx.stroke();

ctx.beginPath();

ctx.lineCap=\'square\';

ctx.moveTo(100,110);

ctx.lineTo(250,110);

ctx.stroke(); 

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图1所示的三条线段。其中,第1根线段箭头样式为“butt”(为默认值),线段的头和尾都是长方形,也就是不做任何的处理;第2根线段箭头样式为“round”,线段的头和尾都增加一个半圆形的箭头;第3根线段的样式为“square”,线段的头和尾都增加一个长方形,长度为线宽一半,高度为线宽。

图1  绘制的3根红色线段

      例4  绘制红绿蓝3个实心三角形。

     ctx.beginPath();

     ctx.moveTo(20,20);

     ctx.lineTo(20,100); 

     ctx.lineTo(70,100); 

     ctx.lineTo(20,20);  

     ctx.fillStyle="red";

     ctx.fill();         // 填充红色三角形

     ctx.beginPath();

     ctx.moveTo(40,120);

     ctx.lineTo(20,180);

     ctx.lineTo(60,180);

     ctx.closePath();   

     ctx.fillStyle="green";

     ctx.fill();         // 填充绿色三角形

     ctx.beginPath();

     ctx.moveTo(70,20);

     ctx.lineTo(120,180);

     ctx.lineTo(140,150);

     ctx.fillStyle="blue";

     ctx.fill();         // 填充蓝色三角形

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图2所示的三个实心三角形。

图2 三个实心三角形

      通过上面的示例,我们可以知道,填充的形状应该是封闭的路径。如果路径未关闭,那么 fill() 方法会从路径结束点到开始点之间添加一条线,以关闭该路径,然后填充该路径。例如,图2中蓝色三角形构成的路径并未关闭,调用fill()时,会自动添加直线关闭。

      贝赛尔曲线(Bezier curve)是计算机图形学中相当重要的参数曲线。Canvas API中提供了两个绘制贝塞尔曲线的方法。其中:

       quadraticCurveTo() 方法用于绘制一条二次贝塞尔曲线。

       二次贝塞尔曲线需要两个点。第一个点(cpx,cpy)是用于二次贝塞尔计算中的控制点,第二个点(x,y)是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,需要使用 beginPath() 和 moveTo() 方法来定义开始点。

      bezierCurveTo() 方法用于绘制一条三次贝塞尔曲线。

       三次贝塞尔曲线需要三个点。前两个点(cp1x,cp1y)和(cp2x,cp2y)是用于三次贝塞尔计算中的控制点,第三个点(x,y)是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,同样需要使用 beginPath() 和 moveTo() 方法来定义开始点。

       例5  绘制一条二次贝塞尔曲线和一条三次贝塞尔曲线。

     ctx.beginPath();

     ctx.moveTo(20,20);

     ctx.quadraticCurveTo(20,100,200,20);

     ctx.stroke();

     ctx.beginPath();

     ctx.moveTo(20,120);

     ctx.bezierCurveTo(20,220,200,180,200,120);

     ctx.stroke();

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图3所示的两条贝塞尔曲线。

图3  两条贝塞尔曲线

       例6  使用多个贝塞尔曲线来绘制一个对话气泡。

    ctx.beginPath();

    ctx.moveTo(75,25);

    ctx.quadraticCurveTo(25,25,25,62.5);

    ctx.quadraticCurveTo(25,100,50,100);

    ctx.quadraticCurveTo(50,120,30,125);

    ctx.quadraticCurveTo(60,120,65,100);

    ctx.quadraticCurveTo(125,100,125,62.5);

    ctx.quadraticCurveTo(125,25,75,25);

    ctx.stroke();

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图4所示的对话气泡。

图4 对话气泡

      例7  使用多个贝塞尔曲线来绘制一个红心。 

     ctx.fillStyle="red";

     ctx.beginPath();

     ctx.moveTo(75,40);

     ctx.bezierCurveTo(75,37,70,25,50,25);

     ctx.bezierCurveTo(20,25,20,62.5,20,62.5);

     ctx.bezierCurveTo(20,80,40,102,75,120);

     ctx.bezierCurveTo(110,102,130,80,130,62.5);

     ctx.bezierCurveTo(130,62.5,130,25,100,25);

     ctx.bezierCurveTo(85,25,75,37,75,40);

     ctx.fill();

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图5所示的红心图案。

 

图5  红心

      例8  通过循环绘制一个9行9列的棋盘。

     ctx.strokeStyle="red";

     ctx.lineWidth=3;

     ctx.beginPath();

     for (i=50;i<=450;i+=50)

     {

         ctx.moveTo(i,50);

         ctx.lineTo(i,450);

         ctx.moveTo(50,i);

         ctx.lineTo(450,i);

     }

     ctx.stroke();

      为显示完整的棋盘,请将画布的宽和高均设置为500。在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图6所示的棋盘。

 

图6  棋盘

2.2  绘制矩形

       在Canvas API中,上下文CanvasRenderingContext2D对象提供的与矩形绘制相关的方法如下:

void rect(in float x, in float y, in float w, in float h);      // 建立一个矩形路径

void clearRect(in float x, in float y, in float w, in float h);  // 清除给定矩形区域的内容

void fillRect(in float x, in float y, in float w, in float h);   // 填充给定的矩形区域

void strokeRect(in float x, in float y, in float w, in float h);  // 绘制给定的矩形边框

这个几个方法中给定的四个参数分别为矩形左上角顶点的x坐标、y坐标,以及矩形的宽w和高h。

例9  绘制红绿蓝三个矩形边框。

     ctx.beginPath();

     ctx.lineWidth="8";

     ctx.strokeStyle="red";

     ctx.rect(15,15,150,150);

     ctx.stroke();                // 绘制红色矩形

     ctx.beginPath();

     ctx.lineWidth="3";

     ctx.strokeStyle="#00FF00";

     ctx.strokeRect(30,30,40,50);  // 绘制绿色矩形

     ctx.beginPath();

     ctx.lineWidth="8";

     ctx.strokeStyle="blue";

     ctx.moveTo(80,50);

     ctx.lineTo(80,120);

     ctx.lineTo(140,120);

     ctx.lineTo(140,50);

     ctx.closePath();

     ctx.stroke();   //     //  绘制蓝色矩形

      在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图7所示的三个矩形。

图7  三个矩形边框

      例10  绘制一个边长为100的正方形,边框采用蓝色,内部用红色填充。

     ctx.fillStyle="red";

     ctx.strokeStyle="blue";

     ctx.lineWidth=2;

     ctx.fillRect(50,50,100,100);

     ctx.strokeRect(50,50,100,100);

      在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图8所示的正方形。

 

图8  正方形

      在没有进行坐标旋转的情况下,采用rect()和strokeRect()方法绘制的矩形一定是两条边与x轴平行,两条边与y轴平行。若要绘制与坐标轴不平行的矩形,可以采用绘制4条线的方法完成。

      例11  绘制矩形中的矩形。要求矩形里的矩形其顶点在外面矩形的中点上。

     var x = [50,50,250,250];

     var y = [50,250,250,50];

     ctx.strokeStyle="red";

     ctx.lineWidth=3;

     for (i=1;i<=4;i++)

     {

         ctx.beginPath();

         ctx.moveTo(x[0],y[0]);

         for (k=1;k<=3;k++)

           ctx.lineTo(x[k],y[k]);

         ctx.closePath();

         ctx.stroke();

         var tx=x[0];

         var ty=y[0];

         for (k=0;k<3;k++)

         {

             x[k]=(x[k]+x[k+1])/2;

             y[k]=(y[k]+y[k+1])/2;

         }

         x[3]=(tx+x[3])/2;

         y[3]=(ty+y[3])/2;

     }

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图9所示的正方形。

图9  正方形中的正方形

       例12  绘制国际象棋棋盘。

     for (i=0;i<8;i++)

     {

         for (j=0;j<8;j++)

         {

              if ((i+j)%2==0)   

                 ctx.fillStyle = \'black\';

              else

                 ctx.fillStyle= \'white\';

              ctx.fillRect(j*50,i*50,50,50);

         }

     }

       为显示完整的棋盘,请将画布的宽和高均设置为400。在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图10所示的国际象棋棋盘。

 

图10  国际象棋棋盘 

      例13  根据给定数据绘制柱状图。

     var data = [100, 50, 20, 30, 100];

     var colors = [ "red","orange", "yellow","green", "blue"];

     ctx.fillStyle = "white";

     ctx.fillRect(0,0,canvas.width,canvas.height);

     for(var i=0; i<data.length; i++)

     {

         var dp = data[i];

         ctx.fillStyle = colors[i];

         ctx.fillRect(25+i*50, 280-dp*2, 50, dp*2);

     }

      在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图11所示的柱状图。 

图11  柱状图

2.3  绘制圆形和扇形

      在Canvas API中,上下文CanvasRenderingContext2D对象提供的与圆形和弧等绘制相关的方法如下:

       void arc(in float x, in float y, in float radius, in float startAngle, in float endAngle, in boolean anticlockwise);

       void arcTo(in float x1, in float y1, in float x2, in float y2, in float radius);

      其中,arc方法用来绘制扇形。参数x和y是圆心坐标,radius是半径,startAngle和endAngle则是扇形的起始角度和终止角度(以弧度表示),anticlockwise表示作图时应该逆时针画(true)还是顺时针画(false)。

      arcTo() 方法用于在画布上创建介于两个切线之间的弧/曲线。绘制出子路径最后一个点(x0,y0)和(x1,y1)以及(x1,y1)和(x2,y2)构成的两条直线间半径为radius的最短弧线,并用直线连接(x0,y0)

例14  绘制圆弧。

     ctx.strokeStyle="red";

     ctx.fillStyle="orange";

     for(var i=0;i<3;i++)

     {

        for(var j=0;j<4;j++)

        {

            ctx.beginPath();

            x = 50+j*100;     

            y = 50+i*100;    

            radius = 45;                   

            startAngle = 0;   

            endAngle = Math.PI/2+(Math.PI*j)/2;

            anticlockwise = i%2==0 ? false : true; 

            ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

            if (i>1)

               ctx.fill();

            else

               ctx.stroke();

        }

     }

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图12所示的圆弧。

 

图12  圆弧及填充

      通过这个例子还可以加深理解:当调用fill()函数时,所有没有闭合的形状都会自动闭合,因此可以不调用closePath()函数。但是调用stroke()时不会自动闭合。

      例15  绘制9个大小不一的圆。

     ctx.fillStyle="red";

     ctx.lineWidth=1;

     for (var i=1;i<10;i++)

     {

        ctx.beginPath();

        ctx.arc(i*20,i*20,i*10,0,Math.PI*2,true);

        ctx.closePath();

        ctx.fillStyle=\'rgba(255,0,0,0.25)\';

        ctx.fill();

     }

      在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图13所示的图形。

图13  逐渐放大的圆

      例16  根据给定数据绘制饼图。

     var data = [100, 50, 20, 30, 100];

     ctx.fillStyle = "white";

     ctx.fillRect(0,0,canvas.width,canvas.height);

     var colors = [ "red","orange", "yellow","green", "blue"];

     var total = 0;

     for(var i=0; i<data.length; i++)

         total += data[i];

     var prevAngle = 0;

     for(var i=0; i<data.length; i++) 

     {

         var fraction = data[i]/total;

         var angle = prevAngle + fraction*Math.PI*2;

         ctx.fillStyle = colors[i];

         ctx.beginPath();

         ctx.moveTo(150,150);

         ctx.arc(150,150, 100, prevAngle, angle, false);

         ctx.lineTo(150,150);

         ctx.fill();

         ctx.strokeStyle = "black";

         ctx.stroke();

         prevAngle = angle;

     }

      在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图14所示的饼图。

 

图14  饼图

2.4  绘制文本

      在Canvas API中,上下文CanvasRenderingContext2D对象提供的与文本绘制相关的方法如下:

       void fillText(in DOMString text, in float x, in float y, optionalin float maxWidth);

       void strokeText(in DOMString text, in float x, in float y, optionalin float maxWidth);

      这两个方法用来绘制文本,它的前三个参数分别为文本内容、起点的x坐标、y坐标。其中:fillText方法为绘制填充的文字;strokeText方法为对文字进行描边,不填充内部区域,通常用来添加空心字。

      与文本相关的属性有:

      attribute DOMString font;         // 设置字体,默认为10px sans-serif

      attribute DOMString textAlign;    //  设置对齐方式,有"start", "end", "left", "right", "center"等,默认为"start"

      attribute DOMString textBaseline;  //设置文字对齐基线,有"top", "hanging", "middle", "alphabetic", "ideographic", "bottom" 等取值,默认为”alphabetic"

例17  在画布上添加两行文字。

  ctx.font = "Bold 50px 隶书";

  ctx.fillStyle = "Black";

  ctx.fillText("我们是中国人", 10, 50);

  ctx.strokeText("我们热爱我们的祖国", 10, 150);

      在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图15所示的文本。

 

图15  文本绘制

      注意:fillText方法不支持文本断行,即所有文本出现在一行内。所以,如果要生成多行文本,只有调用多次fillText方法。

例18  绘制包含数据说明的柱状图。

     var data = [100, 50, 20, 30, 100];

     var colors = [ "red","orange", "yellow","green", "blue"];

     ctx.fillStyle = "white";

     ctx.fillRect(0,0,canvas.width,canvas.height);

     for(var i=0; i<data.length; i++)

     {

         var dp = data[i];

         ctx.fillStyle = colors[i];

         ctx.fillRect(25+i*50, 280-dp*2, 50, dp*2);

     }

     ctx.fillStyle = "black";

     ctx.lineWidth = 2;

     ctx.beginPath();

     ctx.moveTo(25,10);

     ctx.lineTo(25,280);

     ctx.lineTo(290,280);

     ctx.stroke();

     ctx.fillStyle = "black";

     for(var i=0; i<6; i++)

     {

         ctx.fillText((5-i)*20 + "",4, i*40+80);

         ctx.beginPath();

         ctx.moveTo(25,i*40+80);

         ctx.lineTo(30,i*40+80);

         ctx.stroke();

     }

     var labels = ["JAN","FEB","MAR","APR","MAY"];

     for(var i=0; i<5; i++)

         ctx.fillText(labels[i], 40+ i*50, 290);   

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图16所示的包含数据说明的柱状图。

 

图16  包含数据说明的柱状图

      还可以为文本等设置阴影。

例19  为文本设置阴影。

  ctx.shadowOffsetX = 3;    // 设置水平位移

  ctx.shadowOffsetY = 3;    // 设置垂直位移

  ctx.shadowBlur = 2;      // 设置模糊度

  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";    // 设置阴影颜色

  ctx.font = "50px 宋体";

  ctx.fillStyle = "Black";

  ctx.fillText("我们是中国人", 10, 50);

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图17所示的文字阴影效果。

图17  文字阴影

2.5  图形剪切

      在Canvas API中,上下文CanvasRenderingContext2D对象提供了一个用于图形剪切的方法。

       void clip();

       剪切(clip)路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏没有遮罩的部分,如图18所示。红边五角星就是裁切路径,所有在路径以外的部分都不会在 canvas 上绘制出来。默认情况下,canvas 有一个与它自身一样大的剪切路径(也就是没有剪切效果)。

 

图18  剪切示意图

例20  一个简单的剪切示例。

   ctx.fillStyle = \'red\';

   ctx.fillRect(0,0,400,300);

   ctx.beginPath();

   ctx.moveTo(200,50);

   ctx.lineTo(100,250);

   ctx.lineTo(300,250);

   ctx.closePath();

   ctx.lineWidth = 10;

   ctx.stroke();

   ctx.clip();

   ctx.fillStyle = \'yellow\';

   ctx.fillRect(0,0,400,150);

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图19所示的三角形剪切。

 

图19  三角形剪切

      在这个例子中,先画了一个与 canvas 一样大小(宽400,高300)的红色方形作为背景,然后用 clip方法创建一个三角形的剪切路径。剪切路径创建之后,所有出现在它里面的东西才会画出来。这样在其后绘制高度为画布一半的矩形填充时,只有三角形剪切路径里面的内容才会绘制出来。

      设定了剪切区域之后,无论在Canvas上绘制什么,只有落在剪切区域内的那部分才能得以显示,其余都会被遮蔽掉。

      例21  cilp方法的进一步理解。

// 绘制第一个圆

ctx.beginPath();   

ctx.fillStyle = \'red\';

ctx.arc(200, 100, 100, 0, Math.PI * 2, false);

ctx.fill();

// 绘制第二个圆

ctx.beginPath();

ctx.fillStyle = \'blue\';

ctx.arc(100, 150, 100, 0, Math.PI * 2, false);

ctx.fill();

// 绘制第三个圆

ctx.beginPath();

ctx.fillStyle = \'green\';

ctx.arc(300, 150, 100, 0, Math.PI * 2, false);

ctx.fill();

// 绘制第四个圆

ctx.beginPath();

ctx.fillStyle = \'brown\';

ctx.arc(200, 200,100, 0, Math.PI * 2, false);

ctx.fill();

ctx.lineWidth = 10;

ctx.strokeStyle=\'black\';

ctx.stroke();

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图20所示的4个圆。

图20  没有使用clip方法的4个圆

      若在第3个圆绘制后插入一条语句“ctx.clip();”,则在画布中绘制出如图21所示的图形。从图21可以看出第4个圆(棕色的),只有落在第3个圆中的部分被绘制出来。

 

图21 在第3个圆之后使用clip()方法

      若clip()方法往上移,放到第2个圆的后面,则在画布中绘制出如图22所示的图形。从图22可以看出第3个圆(蓝色的)完全没有被绘制出来,因为第3个圆与第2个圆相切,没有交集;第4个圆(棕色的)只有落在第2个圆中的部分被绘制出来。

 

图22 在第2个圆之后使用clip()方法

      若再把clip()方法往上移,放到第1个圆的后面,则在画布中绘制出如图23所示的图形。从图23可以看出,第1个圆为剪切区域,第2、3、4个圆只有落在第1个圆中的部分才被绘制出来。

图23 在第1个圆之后使用clip()方法

      当使用剪切函数clip()进行绘图后,可能需要取消该剪切区域或者重新定义剪切区域。在Canvas中,可以通过save()函数和restore()函数来实现。在构建剪切区域之前保存状态,完成剪切区域内的绘图之后进行状态读取。

       例如,在例21的程序中,在第2个圆绘制后插入语句“ctx.save();”和“ctx.clip();”,在第3个圆绘制后插入语句“ctx.restore();”,则在画布中绘制出如图24所示的图形。从图24可以看出,第3个圆(蓝色的)完全没有被绘制出来,因为第3个圆与第2个圆相切,没有交集;第4个圆(棕色的)全部被绘制出来,此时取消了剪切区域。

图24  在第3个圆绘制后取消剪切区域

例22  采用clip实现简单的探照灯效果。

<!DOCTYPE html>

<head>

<title>简单探照灯</title>

</head>

<body>

<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

    var rot=10;

    var canvas=document.getElementById(\'myCanvas\');

    var ctx=canvas.getContext(\'2d\');

    setInterval("draw()",100);

    function draw()

    {

           ctx.clearRect(0,0,400,400);

           ctx.save();

           ctx.fillStyle="black";

           ctx.fillRect(0,0,400,400);

        ctx.beginPath();

        ctx.arc(rot,200,40,0,Math.PI*2,true);

        ctx.closePath();

        ctx.fillStyle="white";

        ctx.fill();

           ctx.clip();

           ctx.font="bold 45px 隶书";

        ctx.textAlign="center";

           ctx.textBaseline="middle";

           ctx.fillStyle="#FF0000";

           ctx.fillText("中国北京欢迎您!",200,200);

           ctx.restore();

        rot=rot+10;

        if (rot>400) rot=10;

    }

</script>

</body>

</html>

       在浏览器中打开包含这段HTML代码的html文件,可以看到在画布中呈现出如图25所示的简单探照灯效果。

 

图25  简单的探照灯

3.图形变换

在图形学中,可以对图形进行平移、缩放和旋转等变换操作。

在Canvas API中,上下文CanvasRenderingContext2D对象提供的与图形变换相关的方法如下:

void translate(in float x, in float y);  // 平移Canvas的原点到指定的坐标点(x,y)

void rotate(in float angle);         //  按给定的弧度angle顺时针旋转

void scale(in float x, in float y);    //  按给定的缩放倍率进行缩放

void setTransform(in float m11, in float m12, in float m21, infloat m22, in float dx, in float dy);                  // 将当前转换重置为单位矩阵

void transform(in float m11, in float m12, in float m21, in floatm22, in float dx, in float dy);                      // 按矩阵进行变换

       在进行图形变换前先保存上下文环境(状态)是一个良好的习惯。大多数情况下,调用 restore()方法比手动恢复原先的状态要简单得多。例如,在一个循环中做平移操作但没有保存和恢复canvas 的状态,很可能到最后会发现有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。

3.1  save和restore方法

       save方法用于保存上下文环境,restore方法用于恢复到上一次保存的上下文环境。

       在Canvas中,每个上下文对象都包含一个绘图状态的堆,绘图状态包含下列内容:

(1)当前的变换矩阵;

(2)当前的剪切区域(clip);

       (3)当前的属性值:fillStyle、font、globalAlpha、globalCompositeOperation、lineCap、 lineJoin、lineWidth、miterLimit、shadowBlur、shadowColor、shadowOffsetX、shadowOffsetY、 strokeStyle、textAlign、textBaseline等。

例23  save方法和restore方法的简单应用示例。

     ctx.fillStyle = "red";

     ctx.fillRect(10,10,80,80);

     ctx.save();

     ctx.shadowOffsetX = 10;

     ctx.shadowOffsetY = 10;

     ctx.shadowBlur = 5;

     ctx.shadowColor = "rgba(0,0,0,0.5)";

     ctx.fillStyle = "blue";

     ctx.fillRect(100,10,80,80);

     ctx.restore();

     ctx.fillRect(200,10,80,80);

     ctx.fillStyle = "orange";

     ctx.fillRect(300,10,80,80);

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图26所示的4个矩形。

 

图26  4个矩形

      这4个矩形中,第1个矩形填充红色,之后保存状态(填充色为红色),第2个矩形是一个有黑色阴影的填充色为蓝色的矩形;接着,使用restore方法,恢复了保存前的设置,绘制了一个没有阴影的填充色为红色的第3个矩形,第4个矩形是一个填充色为橙色的矩形,也没有阴影。

      若去掉代码中的“ctx.restore();”语句,不恢复状态,则绘制的4个矩形如图27所示。体会图27与图26的区别。

图27  不执行“ctx.restore();”绘制的4个矩形

3.2  translate、scale和rotate方法

       translate() 方法实现坐标平移,例如,进行ctx.translate(dx,dy);后,坐标原点移到(dx,dy)处,这样程序绘图时给出的坐标值(x,y),相对于canvas默认的坐标原点(0,0),应该为(x+dx,y+dy)。

例24  translate简单应用示例。

      ctx.fillStyle = "red";

      ctx.fillRect(10,10,80,80);

      ctx.fillStyle = "blue";

      ctx.translate(80,80);

      ctx.fillRect(10,10,80,80);

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图26所示的2个矩形。从图中看出,在坐标位置 (10,10) 处绘制一个红色填充矩形后,将坐标原点平移到(80,80),这样再次绘制填充蓝色的矩形从位置 (90,90) 处开始绘制。

 

图28  平移后的蓝色矩形

  例25  连续坐标平移的示例。

    ctx.fillStyle = \'rgba(255,0,0,0.5)\';

    for (i = 0; i<5; i++)

     {

         ctx.translate(50,50);

         ctx.fillRect(0,0,100,100);

      }

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图29所示的5个矩形。

图29  坐标平移后的5个矩形

       scale() 方法实现图形的缩放。需要注意的是使用scale方法对绘图进行缩放后,所有之后的绘图也会被缩放,包括坐标定位也会被缩放。例如,执行ctx.scale(2,2)后,绘图将定位于距离画布左上角两倍远的位置。

例26  scale简单应用示例。

    ctx.fillStyle = "red";

    ctx.fillRect(10,10,80,80);

    ctx.save();

    ctx.fillStyle = "blue";

    ctx.scale(2,2);

    ctx.fillRect(50,10,80,80);

    ctx.restore();

    ctx.fillStyle = "green";

    ctx.scale(0.5,0.5);

    ctx.fillRect(10,200,80,80);

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图30所示的3个矩形。从图中看出,在坐标位置 (10,10) 处绘制一个红色填充矩形后,ctx.scale(2,2);将绘图放大两倍,这样绘制填充蓝色的矩形从位置 (100,20) 处开始绘制,其宽和高均是红色填充矩形的2倍;恢复上下文环境后,ctx.scale(0.5,0.5);将绘图缩小1倍,这样绘制填充绿色的矩形从位置 (5,100) 处开始绘制,其宽和高均是红色填充矩形的一半。

 

图30  矩形的缩放

        若去掉代码中的“ctx.restore();”语句,不恢复状态,则绘制的3个矩形如图31所示。体会图31与图30的区别。绿色矩形的大小之所以与红色矩形一样,是因为一个东西放大2倍后再缩小1倍,正好恢复原样。

       通过这个示例一定得明白,图形变换的设置一定是在前一个状态的基础上进行的。因此在进行图形变换时,根据需要通过save()方法保存状态,restore()方法恢复状态是非常重要的。

图31  不执行“ctx.restore();”绘制的3个矩形

例27  连续图形放大的示例。

    ctx.fillStyle = \'rgba(255,0,0,0.5)\';

    for (i = 0; i<4; i++)

    {

         ctx.scale(2,2);

         ctx.fillRect(5,5,10,10);

     }

       在浏览器中打开包含这段JavaScript代码的html文件,可以看到在画布中绘制出如图32所示的4个矩形。

 

图32  依次放大后的4个矩形

      rotate()方法实现图形的旋转,其中参数给出的旋转角度angle以弧度计。如需将角度degress转换为弧度,可以使用 degrees*Math.PI/180 公式进行计算。

例28  将矩形旋转45°。

     ctx.fillStyle = "red";

     ctx.fillRect(150,50,80,80);

     ctx.fillStyle = "blue";

     ctx.rotate(45*Math.PI/180);

  

以上是关于JavaScript图形实例:Canvas API的主要内容,如果未能解决你的问题,请参考以下文章

canvas绘图

JavaScript图形实例:纺织物图案

JavaScript图形实例:曲线方程

canvas svg webgl threejs d3js 的区别

canvas API总结

Canvas绘图优化之使用位图--基于createjs库