感性认识
canvas元素是HTML5中新增的一个重要元素,专门用来绘制图形,它就像页面的一个画布,利用canvas绘图的步骤很简单:
- 页面放置canvas;
- 获取canvas的context;
- 通过context的api用JavaScript来进行绘画;
- 绘制路径(矩形不需要),设置样式,使用stroke或fill来填充;
首先我们了解canvas的坐标是这样的
先来看下示例
<script type="text/javascript">
var canvas=document.getElementById("mycanvas");
//获取context
var ctx=canvas.getContext("2d");
//设置样式
ctx.strokeStyle="green";
ctx.lineWidth=2;
//绘制矩形(不需要绘制路径)
ctx.strokeRect(10,10,180,60);
//绘制直线
ctx.moveTo(200,30);
ctx.lineTo(280,60);
ctx.stroke();
//绘制圆形
ctx.beginPath();
ctx.fillStyle="blue";
ctx.arc(60,130,50,0,Math.PI*2,true);
ctx.fill();
//绘制三角形
ctx.beginPath();
ctx.moveTo(120,130);
ctx.lineTo(190,190);
ctx.lineTo(280,150);
ctx.closePath();
ctx.stroke();
</script>
绘制的图形如下:
绘制简单图形
- 绘制矩形
fillRect(x, y, width, height) //绘制实心矩形,被填充 strokeRect(x, y, width, height)//绘制空心矩形,只是边框
- 绘制圆形
矩形不用绘制路径,直接fill或stroke,而其他的图形需要先绘制路径,然后在fill或stroke。context.arc(x,y,r,sAngle,eAngle,counterclockwise);
- x:圆的中心的 x 坐标。
- y:圆的中心的 y 坐标。
- r:圆的半径。
- sAngle:起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。
- eAngle:结束角,以弧度计。
- counterclockwise:可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。
- 绘制直线
ctx.moveTo(200,30); //设置路径的起点 ctx.lineTo(280,60); //设置路径的终点 ctx.stroke(); //给路径着色
这是因为没有使用beginPath来重置路径,而且这还牵涉到另一个API:closePath,它的作用是把起点终点链接起来,如绘制三角形,如果你注释掉closePath,将不会绘制成三角形只是折线而已。
这里需要强调的是使用fill时会自动把起点和终点链接起来,就类似隐含先执行了closePath一样。这一点可以这样验证,把示例中的绘制圆形注释掉beginPath,而且只绘半圆,代码如下<!--放置canvas--> <canvas id="mycanvas" width="300" height="200" style="border:1px solid #ccc;"></canvas> <script type="text/javascript"> var canvas=document.getElementById("mycanvas"); //获取context var ctx=canvas.getContext("2d"); //设置样式 ctx.strokeStyle="green"; ctx.lineWidth=2; //绘制矩形(不需要绘制路径) ctx.strokeRect(10,10,180,60); //绘制直线 ctx.moveTo(200,30); ctx.lineTo(280,60); ctx.stroke(); //绘制圆形 //ctx.beginPath(); ctx.fillStyle="blue"; ctx.arc(60,130,50,0,Math.PI,true); ctx.fill(); //绘制三角形 ctx.beginPath(); ctx.moveTo(120,130); ctx.lineTo(190,190); ctx.lineTo(280,150); ctx.closePath(); ctx.stroke(); </script>
是不是感觉奇怪,如果把绘制圆形使用closePath,再把fill改成stroke,你就会发现路径是这样的
所以beginPath和closePath需要要搞懂,会用慎用!
绘制渐变
- 线性渐变
示例:
var canvas=document.getElementById("mycanvas"); //获取context var ctx=canvas.getContext("2d"); //设置样式 ctx.lineWidth=20; var g=ctx.createLinearGradient(0,0,0,100); g.addColorStop(0,'green'); g.addColorStop(0.5,'blue'); g.addColorStop(1,'red'); ctx.strokeStyle=g; ctx.moveTo(0,0); ctx.lineTo(0,100); ctx.stroke();
绘制线性渐变需要使用createLinearGradient和addColorStop方法context.createLinearGradient(x0,y0,x1,y1);
- x0:渐变开始点的 x 坐标
- y0:渐变开始点的 y 坐标
- x1:渐变结束点的 x 坐标
- y1:渐变结束点的 y 坐标
gradient.addColorStop(stop,color);
- stop:介于 0.0 与 1.0 之间的值,表示渐变中开始与结束之间的位置。
- color:在结束位置显示的 CSS 颜色值
- 径向渐变
示例<!--放置canvas--> <canvas id="mycanvas" width="300" height="200" style="border:1px solid #ccc;"></canvas> <script type="text/javascript"> var canvas=document.getElementById("mycanvas"); //获取context var ctx=canvas.getContext("2d"); //设置样式 var g=ctx.createRadialGradient(100,100,5,100,100,30); g.addColorStop(0,'green'); g.addColorStop(0.5,'blue'); g.addColorStop(1,'red'); ctx.fillStyle=g; ctx.arc(100,100,50,0,Math.PI*2,true); ctx.fill(); </script>
context.createRadialGradient(x0,y0,r0,x1,y1,r1);
- x0:渐变的开始圆的 x 坐标
- y0:渐变的开始圆的 y 坐标
- r0:开始圆的半径
- x1:渐变的结束圆的 x 坐标
- y1:渐变的结束圆的 y 坐标
- r1:结束圆的半径
绘制变形图
- 坐标变换
可以通过变化canvas坐标来达到变换图形的效果,坐标有三种变换方式:- 平移:重新映射画布上的 (0,0) 位置
context.translate(x,y);
- 缩放:缩放当前绘图至更大或更小
context.scale(x,y);
- 旋转:旋转当前绘图
context.rotate(angle);
<!--放置canvas--> <canvas id="mycanvas" width="300" height="200" style="border:1px solid #ccc;"></canvas> <script type="text/javascript"> var canvas=document.getElementById("mycanvas"); //获取context var ctx=canvas.getContext("2d"); ctx.strokeStyle="green"; ctx.strokeRect(0,0,50,50); //移动坐标 ctx.translate(10,10); ctx.strokeRect(0,0,50,50); //缩放2倍 ctx.scale(2,2); ctx.strokeRect(10,10,50,50); //旋转10度 ctx.rotate(10*Math.PI/180); ctx.strokeRect(10,10,50,50); </script>
- 平移:重新映射画布上的 (0,0) 位置
- 坐标变换和路径结合的使用
路径一旦绘制好后是不会随着坐标变换而变换的,如var canvas=document.getElementById("mycanvas"); //获取context var ctx=canvas.getContext("2d"); //坐标变换前的路径 ctx.moveTo(0,0); ctx.lineTo(100,20); ctx.lineTo(20,80); //变换坐标 ctx.rotate(20*Math.PI/180); //为路径填色 ctx.strokeStyle="green"; ctx.stroke(); //坐标变换后绘制的图像,路径与上一路径相同 ctx.strokeStyle="red"; ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(100,20); ctx.lineTo(20,80); ctx.stroke();
可见坐标变换对路径是没影响的,一般这种情况我们会把绘图操作封装成一个函数,方便坐标变换后操作。var canvas=document.getElementById("mycanvas"); //获取context var ctx=canvas.getContext("2d"); doPic(ctx,"green"); ctx.rotate(20*Math.PI/180); doPic(ctx,"red"); ctx.rotate(20*Math.PI/180); doPic(ctx,"blue"); function doPic(ctx,color){ ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(100,20); ctx.lineTo(20,80); ctx.strokeStyle=color; ctx.stroke(); }
- 矩阵变换
如果上述都不能满足需求,HTML5还提供一种更复杂的变化:矩阵变换,这块牵涉到矩阵的数学知识,稍后单一节示例。
图像组合
通常当绘制的图像重叠时将以绘制的先后顺序来处理重叠的部分,但HTML5提供了globalCompositeOperation 选项来设置如何处理重叠。
<canvas id="mycanvas" width="300" height="200" style="border:1px solid #ccc;"></canvas>
<script type="text/javascript">
var canvas=document.getElementById("mycanvas");
//获取context
var ctx=canvas.getContext("2d");
ctx.fillStyle="red";
ctx.fillRect(20,20,75,50);
ctx.fillStyle="blue";
ctx.globalCompositeOperation="source-over";
ctx.fillRect(50,50,75,50);
ctx.fillStyle="red";
ctx.fillRect(150,20,75,50);
ctx.fillStyle="blue";
ctx.globalCompositeOperation="destination-over";
ctx.fillRect(180,50,75,50);
</script>
绘制的图像
globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。
- 源图像 = 您打算放置到画布上的绘图。
- 目标图像 = 您已经放置在画布上的绘图。
属性值和描述
- source-over:默认。在目标图像上显示源图像。
- source-atop:在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
- source-in:在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
- source-out:在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
- destination-over:在源图像上方显示目标图像。
- destination-atop:在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
- destination-in:在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
- destination-out:在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
- lighter:显示源图像 + 目标图像。
- copy:显示源图像。忽略目标图像。
- source-over:使用异或操作对源图像与目标图像进行组合。
绘制阴影
HTML5给添加阴影也非常简单,主要涉及到下面几个属性:
- shadowColor:设置或返回用于阴影的颜色,如果取消只需设置为rgba(0,0,0,0);
- shadowBlur:设置或返回用于阴影的模糊级别,一般设置在0-10之间;
- shadowOffsetX:设置或返回阴影距形状的水平距离;
- shadowOffsetY:设置或返回阴影距形状的垂直距离;
ctx.fillStyle="blue";
ctx.shadowColor="#ccc";
ctx.shadowBlur=5;
ctx.shadowOffsetX=10;
ctx.shadowOffsetY=10;
ctx.fillRect(10,10,60,60);
ctx.fillRect(80,80,60,60);
使用图片
-
- 绘制图片
插入图片的方法只有一个,但是有三个重载方法,具体如下:context.drawImage (image, dx, dy) context.drawImage (image, dx, dy, dw, dh) context.drawImage (image, sx, sy, sw, sh, dx, dy, dw, dh)
示例<canvas id="mycanvas" width="300" height="200" style="border:1px solid #ccc;">不支持canvas.</canvas> <script type="text/javascript"> var mycanvas=document.getElementById("mycanvas"); var ctx=mycanvas.getContext("2d"); var img=new Image(); img.src="p.jpg"; ctx.drawImage(img,0,0); </script>
<canvas id="mycanvas" width="300" height="200" style="border:1px solid #ccc;">不支持canvas.</canvas> <script type="text/javascript"> var mycanvas=document.getElementById("mycanvas"); var ctx=mycanvas.getContext("2d"); var img=new Image(); img.src="p.jpg"; img.οnlοad=function(){ ctx.drawImage(img,0,0); ctx.drawImage(img,10,10,50,50,250,0,50,50); }
图像平铺 HTML5提供了一个非常方便的方法
- 绘制图片
createPattern(image, repetitionStyle)
-
- 创建并返回一个 CanvasPattern 对象,该对象表示一个贴图图像所定义的模式。要使用一个模式来勾勒线条或填充区域,可以把一个 CanvasPattern 对象用作 strokeStyle 属性或 fillStyle 属性的值。repetitionStyle 说明图像如何贴图。可能的值如下所示:
- "repeat" - 在各个方向上都对图像贴图。默认值。
- "repeat-x" - 只在 X 方向上贴图。
- "repeat-y" - 只在 Y 方向上贴图。
- "no-repeat" - 不贴图,只使用它一次。
img.οnlοad=function(){
var ptt=ctx.createPattern(img,'repeat');
ctx.fillStyle=ptt;
ctx.fillRect(0,0,300,200);
}
- 裁剪
clip() 方法从原始画布中剪切出任意形状和尺寸。
一旦再勾画出路径后调用clip方法,则所有只有的绘图都会被限制在这个路径区域,从而达到裁剪的效果,可以结合save和restore方法。img.οnlοad=function(){ ctx.beginPath(); ctx.arc(60,60,50,0,Math.PI*2,true); ctx.clip(); ctx.drawImage(img,0,0); }
- 像素
html5还有一个令人赞叹的技术就是可以处理像素,这设计到两个API:var imgData=context.getImageData(x,y,width,height);
- x - 开始复制的左上角位置的 x 坐标。
- y - 开始复制的左上角位置的 y 坐标。
- width - 将要复制的矩形区域的宽度。
- height - 将要复制的矩形区域的高度。
- r - 红色 (0-255)
- g - 绿色 (0-255)
- b - 蓝色 (0-255)
- a - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight);
- imgData - 规定要放回画布的 ImageData 对象。
- x - ImageData 对象左上角的 x 坐标,以像素计。
- y - ImageData 对象左上角的 y 坐标,以像素计。
- dirtyX - 可选。水平值(x),以像素计,在画布上放置图像的位置。
- dirtyY - 可选。水平值(y),以像素计,在画布上放置图像的位置。
- dirtyWidth - 可选。在画布上绘制图像所使用的宽度。
- dirtyHeight - 可选。在画布上绘制图像所使用的高度。
示例:我们把图片上的一部分颜色取反,并把透明度降到60%:img.οnlοad=function(){ ctx.drawImage(img,0,0); var imgData=ctx.getImageData(10,10,80,80); for(var i=0;i<imgData.data.length;i+=4){ imgData.data[i]=255-imgData.data[i+2]; imgData.data[i+1]=255-imgData.data[i+1]; imgData.data[i+2]=255-imgData.data[i]; imgData.data[i+3]=imgData.data[i+3]*0.6; } ctx.putImageData(imgData,150,100); }
PS:在chrome等浏览器中这个API函数设计到跨域的问题,因此不能通过本地浏览(即file:///),需要通过webserver(即http://)才能看到效果,否则报类似下面的异常:
但firefox是好用的。
绘制文字
在canvas中也可以绘制文字,这涉及到两个API:
context.fillText(text,x,y,maxWidth);
context.strokeText(text,x,y,maxWidth);
- text - 规定在画布上输出的文本。
- x - 开始绘制文本的 x 坐标位置(相对于画布)。
- y - 开始绘制文本的 y 坐标位置(相对于画布)。
- maxWidth - 可选。允许的最大文本宽度,以像素计。
在绘制之前可以先设置文字的相关属性:
- font属性:设置文字的字体,语法与 CSS font 属性相同,可以按顺序设置font-style、font-variant、font-weight、font-size/line-height、font-family;
- textAlign属性:设置文字的水平对齐方式,可选值start、end、left、right、center,默认是start;
- textBaseline属性:设置文字的垂直对齐方式,可选值top、hanging、middle、alphabetic、ideographic、bottom,默认是alphabetic;
有时为了方便定位,需要获取文字的宽度,html5提供了一个API:
context.measureText(text)
返回的TextMetrics对象的width属性表示使用当前指定的字体后文字的总长度。
ctx.font="italic 30px sans-serif";
ctx.textBaseline="top";
ctx.fillStyle="red";
var txt='爱我中华';
ctx.fillText(txt,0,0);
var tml = ctx.measureText(txt);
ctx.strokeStyle="green";
ctx.strokeText(txt,tml.width+20,0);
善后工作
- 保存及恢复状态
在绘制过程中,可能临时要改变一下属性设置,当临时绘制结束又想回到之前的状态,这就涉及到恢复,涉及到两个API:save() //把当前环境的状态压入栈 restore() //状态出栈
ctx.fillText(txt,0,0); ctx.save();//状态入栈 ctx.fillStyle="green"; ctx.textBaseline="bottom"; ctx.fillText(txt,0,80); ctx.restore();//状态出栈 ctx.fillText(txt,0,120);
- 保存文件
在canvas绘制后可以使用toDataURL方法把它保存成浏览器能识别的data URL数据ctx.font="italic 30px sans-serif"; ctx.textBaseline="top"; ctx.fillStyle="red"; var txt='爱我中华'; ctx.fillText(txt,0,0); document.getElementById("img").src=mycanvas.toDataURL("image/jpeg");
- 简单动画
可以在canvas重复绘制、擦除、绘制的动作来实现动画,这其中涉及到一个擦除的APIcontext.clearRect(x,y,width,height);
setInterval(code,millisec[,"lang"])
例如我们来绘制一个运动的小圆:var i=0; var intervalId=setInterval(doarc,1000); function doarc(){ ctx.clearRect(0,0,mycanvas.width,mycanvas.height); ctx.beginPath(); ctx.arc(i,i,20,20,Math.PI*2,true); ctx.fillStyle="blue"; ctx.fill(); i+=30; if(i>200){clearInterval(intervalId);} }