JavaScript基础——使用Canvas绘图

时间:2021-12-06 22:17:00

HTML5添加的最受欢迎的功能就是<canvas>元素。这个元素负责在页面中设定一个区域,然后就可以通过JavaScript动态地在这个区域中绘制图形。<canvas>元素最早是由苹果公司推出的,当时主要用在其Dashboard中。很快,HTML5也加入了这个元素,主流浏览器开始支持它。

基本用法

要使用<canvas>元素,必须先设置其width和height属性,指定可以绘图的区域大小。出现在开始和结束标签中的内容是后备信息,如果浏览器不支持<canvas>元素,就会显示这些信息。

<canvas width="800" height="800"style="background-color:antiquewhite">A drawing of something.</canvas>

与其他元素一样,<canvas>元素对应的DOM元素也有width和height属性,可以随意修改。而且,也能通过CSS为该元素添加样式,如果不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的。

要在这块画布上绘图,需要取得绘图上下文。而取得绘图上下文对象的引用,需要调用getContext()方法并传入上下文的名字。传入”2d”,就可以取得2D上下文对象。

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    //更多代码

}

在使用<canvas>元素之前,首先要检测getContext()方法是否存在,这一步非常重要。有些浏览器会为HTML规范之外的元素创建默认的HTML元素对象。在这种情况下,即使drawing变量中保存着一个有效的元素引用,也检测不到getContext()方法。

使用toDataURL()方法,可以导出在<canvas>元素上绘制的图像。这个方法接受一个参数,即图像的MIME类型格式,而且适用于创建图像的任何上下文。比如,要取得画布中的一幅PNG格式的图像,可以使用以下代码。

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    //取得图像的数据URI

    var imageURI = drawing.toDataURL("image/png");

    var image = document.createElement("img");

    image.src=imageURI;

    document.body.appendChild(image);

}

2D上下文

使用2D绘图上下文提供的方法,可以绘制简单的2D图形,比如矩形、弧形和路径。2D上下文的坐标开始于<canvas>左上角,原始坐标是(0,0)。所有坐标值都基于这个原点计算,x值越大表示靠右,y值越大表示越靠下。默认情况下,width和height表示水平和垂直两个方向上可用的像素数目。

填充和描边

2D上下文的两种基本绘图操作是填充和描边。填充,就是用指定的样式(颜色、渐变或图像)填充图形;描边,就是只在图形的边缘画线。大多数2D上下文操作都会细分为填充和描边两个操作,而操作的结果取决于两个属性:fillStyle和strokeStyle。

这两个属性的值可以是字符串。渐变对象或模式对象,而且它们的默认值都是”#000000”。如果为它们指定表示颜色的字符串,可以使用CSS中指定颜色值的任何格式,包括颜色名、十六进制码、rgb、rgba、hsl或hsla。举个例子:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    context.strokeStyle="red";

    context.fillStyle="#0000ff";

   

}

绘制矩形

矩形是唯一一种可以直接在2D上下文中绘制的形状。与矩形有关的方法包括fillRect()、strokeRect()和clearRect()。这三个方法都接受4个参数:矩形的x坐标、矩形的y坐标、矩形宽度和矩形高度。这些参数的单位都是像素。

首先,fillRect()方法在画布上绘制的矩形会填充指定的颜色。填充的颜色通过fillStyle属性指定,比如:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    context.fillStyle="#ff0000";

    context.fillRect(10,10,50,50);

    context.fillStyle="rgba(0,0,255,0.5)";

    context.fillRect(30,30,50,50);

}

JavaScript基础——使用Canvas绘图

strokeRect()方法在画布上绘制的矩形会使用指定的颜色描边。描边颜色通过strokeStyle属性指定。比如:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    context.strokeStyle="#ff0000";

    context.strokeRect(10,10,50,50);

    context.strokeStyle="rgba(0,0,255,0.5)";

    context.strokeRect(30,30,50,50);

}

JavaScript基础——使用Canvas绘图

最后,clearRect()方法用于清除画布上的矩形区域。本质上,这个方法可以把绘制上下文中的某一矩形变透明。通过绘制形状然后再清除指定区域,就可以生成有意思的效果,例如把某个形状切掉一块。如:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    context.fillStyle="#ff0000";

    context.fillRect(10,10,50,50);

    context.fillStyle="rgba(0,0,255,0.5)";

    context.fillRect(30,30,50,50);

    context.clearRect(40,40,10,10);

}

JavaScript基础——使用Canvas绘图

绘制路径

2D上下文支持很多在画布上绘制路径的方法。通过路径可以创造出复杂的形状和线条。要绘制路径,首先必须调用beginPath()方法,表示要开始绘制新路径。然后,再通过调用下列方法来实际地绘制路径。

arc(x,y,radius,startAngle,endAngle,counterclockwise):以(x,y)为圆心绘制一条弧线,弧线半径为radius,起始和结束角度(用弧度表示)分别为startAngle和endAngle。最后一个参数表示startAngle和endAngle是否按逆时针方向计算,值为false表示按顺时针方向计算。

arcTo(x1,y1,x2,y2,radius):从上一点开始绘制一条弧线,到(x2,y2)为止,并且以给定的半径radius穿过(x1,y1)。

bezierCurveTo(c1x,c1y,c2x,c2y,x,y):从上一点开始绘制一条曲线,到(x,y)为止,并且以(c1x,c1y)和(c2x,c2y)为控制点。

lineTo(x,y):从上一点开始绘制一条直线,直到(x,y)为止。

moveTo(x,y):将绘图游标移动到(x,y),不画线。

quadraticCurveTo(cx,cy,x,y):从上一点开始绘制一条二次曲线,到(x,y)为止,并且以(cx,cy)作为控制点。

rect(x,y,width,height):从点(x,y)开始绘制一个矩形,宽度和高度分别由width和height指定。这个方法绘制的是矩形路径,而不是strokeRect()和fillRect()所绘制的独立的形状。

创建了路径后,接下来有集中可能的选择。如果想绘制一条连接到路径起点的线条,可以调用closePath()。如果路径已经完成,你想用fillStyle填充它,可以调用fill()方法。另外,还可以调用stroke(0方法对路径描边,描边使用的是strokeStyle。最后还可以调用clip(),这个方法可恶意在路径上创建一个剪切区域。

下面看一个例子,即绘制一个不带数字的时钟表盘。

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    //开始路径

    context.beginPath();

    //绘制外圆

    context.arc(100,100,99,0,2*Math.PI,false);

    //绘制内圆

    context.arc(100,100,94,0,2*Math.PI,false);

    //绘制分针

    context.moveTo(100,100);

    context.lineTo(100,15);

    //绘制时针

    context.moveTo(100,100);

    context.lineTo(35,100);

    //描边路径

    context.stroke();

}

JavaScript基础——使用Canvas绘图

在2D绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制。由于路径的使用很频繁,所以就有了一个名为isPointInPath()的方法。这个方法接收x和y坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径上,例如:

if(context.isPointInPath(100,100)){

    alert("Point (100,100) is in the path.");

}

绘制文本

文本与图形总是如影随行。为此,2D绘图上下文也提供了绘制文本的方法。绘制文本主要有两个方法:fillText()和strokeText()。这两个方法都可以接收4个参数:要绘制的文本字符串、x坐标、y坐标和可选的最大像素宽度。而且,这两个方法都以下列3个属性为基础:

font:表示文本样式、大小及字体,用CSS中指定字体的格式来指定,例如“10px Arial”。

textAlign:表示文本对齐方式。可能的值有“start”、“end”、“left”、“right”和“center”。建议使用“start”和“end”,不要使用“left”和“right”,因为前两者的意思更稳妥,能同时适合从左到右和从右到左显示的语言。

textBaseline:表示文本的基线。可能的值有“top”、“hanging”、“middle”、“alphabetic”、“ideographic”和“buttom”。

这几个属性都有默认值,因此没必要每次使用它们都重新设置一遍值。fillText()方法使用fillStyle属性绘制文本,而strokeText()方法使用strokeStyle属性为文本描边。相对来说,还是使用fillText()的时候更多,因为该方法模仿了在网页中正常显示文本。例如,在上面的表盘中绘制数字12.

    context.font="bold 14px Arial";

    context.textAlign="center";

    context.textBaseline="middle";

    context.fillText("12",100,20);

JavaScript基础——使用Canvas绘图

因为这里把textAlign设置为center,把textBaseline设置为“middle”,所以坐标(100,20)表示的是文本水平和垂直中点的坐标。如果将textAlign设置为start,则x坐标表示的是文本左端的距离;设置为end,则x坐标表示的是文本有段的位置。

变换

通过上下文的变换,可以把处理后的图像绘制到画布上。2D绘制上下文支持各种基本的绘制变换。创建绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵下,了所有处理都按描述直接绘制。为绘制上下文应用变换,会导致使用不同的变换矩阵应用处理,从而产生不同的结果。

可以通过如下方法来修改变换矩阵。

rotate(angle):围绕原点旋转angle弧度。

scale(csaleX,scaleY):缩放图像,在x方向乘以scaleX,在y方向乘以scaleY。scaleX和scaleY的默认值都是1.0。

translate(x,y):将坐标原点移动到(x,y)。执行这个变换之后,坐标(0,0)会变成之前由(x,y)表示的点。

transform(m1_1,m1_2,m2_1,m2_2,dx,dy):直接修改变换矩阵,方式是乘以如下矩阵。

m1_1        m1_2        dx

m2_1        m2_2        dy

0                0                1

setTransform(m1_1,m1_2,m2_1,m2_2,dx,dy) :将变换矩阵重置为默认状态,然后再调用transform()。

变换有可能很简单,但也有可能很复杂,这都要视情况而定。比如,就拿前面例子中绘制表针来说,如果把原点变换到表盘的中信,然后再绘制表针就容易多了。如:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    //开始路径

    context.beginPath();

    //绘制外圆

    context.arc(100,100,99,0,2*Math.PI,false);

    //绘制内圆

    context.arc(100,100,94,0,2*Math.PI,false);

    //变换原点

    context.translate(100,100);

    //绘制分针

    context.moveTo(0,0);

    context.lineTo(0,-85);

    //绘制时针

    context.moveTo(0,0);

    context.lineTo(-65,0);

    //描边路径

    context.stroke();

    context.font="bold 14px Arial";

    context.textAlign="center";

    context.textBaseline="middle";

    context.fillText("12",0,-85);

}

JavaScript基础——使用Canvas绘图

还可以像下面这样使用rotate()方法旋转时钟的表针。

……

    //变换原点

    context.translate(100,100);

    context.rotate(1);

……

无论是刚才执行的变换,还是fillStyle、strokeStyle等属性,都会在当前上下文中一直有效,除非再对上下文进行什么修改。对冉没有什么办法把上下文中的一切都重置回默认值,但有两个方法可以跟踪上下文的状态变化。如果你知道将来还要返回某组属性与变换的组合,可以调用save()方法。调用这个方法后,当时的所有设置都会进入一个栈结构,得意妥善保管。然后可以对上下文进行其他修改。等想要回到之前保存的设置时,可以调用restore()方法,在保存设置的栈结构中向前返回一级,恢复之前的状态。连续调用save()可以把更多设置保存到栈结构中,之后再连续调用restore()则可以一级一级返回。如:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    context.fillStyle="#ff0000";

    context.save();

    context.fillStyle="#00ff00";

    context.translate(100,100);

    context.save();

    context.fillStyle="#0000ff";

    context.fillRect(0,0,100,100);//从点(100,100)开始绘制蓝色矩形

    context.restore();

    context.fillRect(10,10,100,100);//从点(110,110)开始绘制绿色矩形

    context.restore();

    context.fillRect(0,0,100,100);//从点(0,0)开始绘制红色矩形

}

JavaScript基础——使用Canvas绘图

需要注意的是,save()方法保存的只是对绘图上下文的设置和变换,不会保存绘图上下文的内容。

绘制图像

2D绘图上下文内置了对图像的支持。如果你想把一幅图像绘制到画布上,可以使用drawImage()方法。根据期望的最终结果不同,调用这个方法时,可以使用三种不同的参数组合。最简单的调用方式是传入一个HTML<img>元素,以及绘制该图像的起点的x和y坐标。例如:

var image = document.images[0];

context.drawImage(image,10,10);

这两行代码取得了文档中的第一幅图像,然后将它绘制到上下文中,起点为(10,10)。绘制到画布上的图像与原始大小一样。如果你想改变绘制后图像的大小,可以再多传入两个参数,分别表示目标宽度和目标高度。通过这种方式来缩放图像并不影响上下文的矩阵变换。

context.drawImage(image,50,10,20,30);

执行代码后,绘制出来的图像大小会变成20x30像素。

除了上述两种方式,还可以把图像中的某个区域绘制到上下文中。drawImage()方法的这种调用方式总共需要传入9个参数:要绘制的图像、源图像的x坐标、源图像的y坐标、源图像的宽度、源图像的高度、目标图像的x坐标、目标图像的y坐标、目标图像的宽度、目标图像的高度。这样调用drawImage()方法可以获得最多的控制。

前面的文章中有模仿购物网站中放大图片的效果,这里使用canvas来实现就更加简单了,也不需要准备两张图片:

<styletype="text/css">

    #drawing{

        background-color:antiquewhite;

    }

    #image_{

        max-width:220px;

    }

</style>

<scripttype="text/javascript">

    function showBig(image){

        var drawing = document.getElementById("drawing");

        if(drawing.getContext){

            var context = drawing.getContext("2d");

            var ptX=event.offsetX;

            var ptY=event.offsetY;

            context.drawImage(image,ptX*2.5,ptY*2.5,50,50,0,0,320,320);

        }

    }

</script>

<div>

    <imgid="image_"src="files/images/cymini.jpg"onmousemove="showBig(this)"></img>

    <canvasid="drawing"width="320"height="320">A drawing of something.</canvas>

</div>

JavaScript基础——使用Canvas绘图

阴影

2D上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。

shadowColor:用CSS颜色格式表示的阴影颜色,默认为黑色。

shadowOffsetX:形状或路径X轴方向的阴影偏移量,默认为0。

shadowOffsetY:形状或路径Y轴方向的阴影偏移量,默认为0。

shadowBlur:模糊的像素数,默认0,即不模糊。

这些属性都可以通过context对象来修改。

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    context.shadowOffsetX=5;

    context.shadowOffsetY=5;

    context.shadowBlur=4;

    context.shadowColor="rgba(0,0,0,0.5)"

    context.fillStyle="#ff0000";

    context.fillRect(10,10,50,50);

    context.fillStyle="rgba(0,0,255,0.5)";

    context.fillRect(30,30,50,50);

}

JavaScript基础——使用Canvas绘图

渐变

渐变由CanvasGradient实例表示,很容易通过2D上下文来创建和修改。要创建一个新的线性渐变,可以调用createLinearGradient()方法。这个方法接收4个参数:起点的x坐标、起点的y坐标、终点的x坐标、终点的y坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回CanvasGradient对象的实例。

创建了渐变对象后,下一步就是使用addColorStop()方法来指定色标。这个方法接收两个参数:色标为止和CSS颜色值。色标为止是一个0(开始的颜色)到1(结束的颜色)之间的数字。例如:

var drawing =document.getElementById("drawing");

//确定浏览器支持<canvas>元素

if(drawing.getContext){

    var context = drawing.getContext("2d");

    var gradient = context.createLinearGradient(30,30,70,70);

    gradient.addColorStop(0,"white");

    gradient.addColorStop(1,"black");

    context.shadowOffsetX=5;

    context.shadowOffsetY=5;

    context.shadowBlur=4;

    context.shadowColor="rgba(0,0,0,0.5)"

    context.fillStyle="#ff0000";

    context.fillRect(10,10,50,50);

    context.fillStyle=gradient;

    context.fillRect(30,30,50,50);

}

JavaScript基础——使用Canvas绘图

要创建径向渐变(或放射渐变),可以使用createRadialGradient()方法。这个方法接收6个参数,对应着两个圆的圆心和半径。前三个参数指定的是起点圆心和半径,后三个参数指定的是终点圆的圆心和半径。可以把径向渐变想象成一个长圆通,而这6个参数定义的正式这个桶的两个圆形开口的为止,就可达到像旋转这个圆锥体一样的效果。

var gradient =context.createRadialGradient(55,55,10,55,55,35);

JavaScript基础——使用Canvas绘图

模式

模式其实就是重复的图像,可以用来填充或描边图形。要创建一个新模式,可以调用createPattern()方法并传入两个参数:一个HTML<img>元素和一个表示如何重复图像的字符串。其中,第二个参数的值与CSS的background-repate属性值相同,包括“repeat”、“repeat-x”、“repeat-y”和“no- repeat”。

var drawing =document.getElementById("drawing");

    //确定浏览器支持<canvas>元素

    if(drawing.getContext){

        var context = drawing.getContext("2d");

        var img = document.images[0];

        var pattern = context.createPattern(img,"repeat");

        context.fillStyle = pattern;

        context.fillRect(10,10,100,100);

    }

JavaScript基础——使用Canvas绘图

createPattern()方法的第一个参数也可以是一个<vedio>元素,或者另一个<canvas>元素。

使用图像数据

2D上下文的一个明显的长处就是,可以通过getImageData()取得原始图像数据。这个方法接收4个参数:要取得其数据的画面区域的x和y坐标以及该区域的像素宽度和高度。例如,要取得左上角坐标为(10,5),大小为50x50像素的图像数据,可以使用如下代码:

var imageData =context.getImageData(10,5,50,50);

这里返回的对象是ImageData的实例。每个ImageData对象都有三个属性:width、height和data。其中data属性是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示红、绿、蓝和透明度值。因此,第一个像素的数据就保存数据的第0到第3个元素中。数组中每个元素的值都介于0到255之间。能够直接访问到原始图像数据,就能够以各种方式来操作这些数据。例如,通过修改图像数据,可以像下面这样创建一个简单的灰阶过滤器。

    var drawing = document.getElementById("drawing");

    //确定浏览器支持<canvas>元素

    if(drawing.getContext){

        var context = drawing.getContext("2d");

        var img = document.images[0],

        imageData,data,i,len,average,

        red,green,blue,alpha;

        context.drawImage(img,0,0);

        imageData =context.getImageData(0,0,img.width,img.height);

        data = imageData.data;

        for(i=0,len=data.length; i<len; i+=4){

            red=data[i];

            green=data[i+1];

            blue=data[i+2];

            alpha=data[i+3];

            average=Math.floor((red+green+blue)/3);

            data[i]=average;

            data[i+1]=average;

            data[i+2]=average;

        }

        imageData.data=data;

        context.putImageData(imageData,0,0);

    }

JavaScript基础——使用Canvas绘图

合成

还有两个会应用到2D上下问中所有绘制操作的属性:globalAlpha和globalCompositionOperation。其中,globalAlpha是一个介于0到1之间的值,用于指定所有绘制的透明度。默认值为0.如果所有后续操作都要基于相同的透明度,就可以先把globalAlpha设置为适当的值。然后绘制,最后再把它设置回默认值0.

第二个属性globalCompositionOperation表示后绘制的图形怎样与先绘制的图形结合。这个属性的值是字符串,可能的值如下:

source-over(默认值):后绘制的图形位于先绘制的图形上方。

source-in:后绘制的图形与先绘制的图形重叠的部分可见,两者其他部分完全透明。

source-out:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形完全透明。

source-atop:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形不受影响。

destination-over:后绘制的图形位于先绘制的图形下方,只有之前透明像素下的部分才可见。

destination-in:后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。

destination-out:后绘制的图形擦除与先绘制的图形重叠的部分。

destination-atop:后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的图形会变透明。

lighter:后绘制的图形与先绘制的图形重叠部分的值相加,使该部分变亮。

copy:后绘制的图形完全替代与之重叠的先绘制图形。

xor:后绘制的图形与先绘制的图形重叠的部分执行“异或”操作。

WebGL

WebGL是针对Canvas的3D上下文。与其他Web技术不同,WebGL并不是W3C制定的标准,而是由KhronosGroup制定的。学习网址:www.learningwebgl.com