canvas 是html5新增的一个比较强大的功能,简直是一个画图工具一样。
canvas的操作基本是用js来完成的,下面开始讲一些基本用法。
一.最基本的用法
要用画布,先要写出画布标签。
<canvas height="500" width="700" style="border:1px solid black" id="canvas">浏览器不支持canvas</canvas>
这里要注意,定义canvas宽高时建议用标签的属性height、width。经测试,如果在style内设置width,height会使绘图环境(下面会讲什么是绘图环境)缩放,使得绘图环境不好控制。
要在canvas上画画,要用到js
<script> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); //开始一个绘画路径 ctx.beginPath(); //描边颜色 ctx.strokeStyle = "red"; ctx.fillStyle = "blue"; ctx.moveto(20,20); ctx.lineto(50,50); ctx.lineto(40,20); //连接回最初的起点 ctx.closePath(); //对路径进行描边 ctx.stroke(); ctx.fill(); </script>这里用getContext()来获得一个绘图环境。后面就是用这个绘图环境做各种绘图。
beginPath()是标志开始一段新的路径绘画。strokeStyle是定义描线的颜色。fillStyle是定义填充的颜色。moveto(x,y)是定义路径起点。lineto(x,y)是画路径。这里之所说是画路径,而不是说画线,是因为lineto(包括后面说到的rect,arc)只是程序定义了一个作图的路线,并没有在这条路线上做任何动作,所以上面的程序如果没有后面的stroke()或fill(),画布内是不会显示任何东西的。这里一定要理解好,不然后面你会凌乱的。
closePath()是闭合当前路径(并不是关闭),即把当前路径的终点和路径起点连接起来。然后就是上面说到的stroke(),stroke()是对当前的路径进行描边(即画线)。当前路径指的是从beginPath()开始,所以每画一个图形的开头,最好都加上beginPath(),这样可以防止第一个图形的路径终点连接到第二个图形的路径起点。
fill()是填充当前路径的闭合区域。
二.画基本图形
<script> //矩形 ctx.beginPath(); ctx.fillStyle = "red"; //填充一个矩形(x,y,宽,高) //方法一: ctx.fillRect(20,50,100,70); //方法二: ctx.rect(130,70); ctx.fill(); </script>上面的方法一跟方法二效果是一样的(只有矩形的坐标不同)。fillRect直接生产一个填充了的矩形,而rect是定义了一个矩形的路径,还没有画出来。
在实际应用中,建议先把图形的路径画好了,再去填充或描线。因为描边和填充会消耗大量资源,所以尽量把路径都弄好一次过描边或填充。
<script> //圆形 ctx.beginPath(); ctx.fillStyle = "red"; //圆形(x,半径,起始角,结束角,绘图方向:true逆时针,false顺时针) ctx.arc(40,220,2*Math.PI,false); ctx.fill(); </script>arc是一个画圆弧的方法,参数的介绍代码中已经有了。
<script> //椭圆形 ctx.beginPath(); ctx.save(); ctx.scale(1,0.5); ctx.fillStyle = "blue"; ctx.arc(240,360,false); ctx.fill(); ctx.restore(); </script>
没有直接画椭圆的方法,上面代码是通过把一个圆给压缩得到一个椭圆。
save()是保存当前画面的各种状态包括画笔颜色、填充颜色、画面旋转角度、画面基点坐标、画面缩放等……。restore()则是恢复到save()保存的状态。代码中可以出现多个save(),restore()是把画面恢复到离它最近的一个save()的状态。
注意这里我说的是画面,而不是画布。画布即canvas标签,这里的画面指的是getContext("2d")对象,即上面说的绘图环境。
scale(x,y)是对画面进行缩放,后面再来介绍。
三.绘图环境的缩放、旋转、基点坐标改变。
1.改变基点(translate)
先说一下基点坐标。默认的基点坐标是(0,0),即画布的左上角坐标。上面说到的rect()方法中的前两个参数x,y就是相对于基点坐标(0,0)而言的。
<script> //改变基点坐标 ctx.beginPath(); ctx.save(); ctx.rect(300,300,90); ctx.translate(20,20); ctx.rect(300,90); ctx.stroke(); ctx.restore(); </script>可以看到,上面两个rect()的参数是一样的,但是画出来的矩形却不在同一位置上。因为第二个矩形在定义路径之前,绘图环境的基点坐标被改变了,所以第二个rect的前两个参数是基于新的基点坐标来定位。
注意,这里是先改变基点坐标,再去画路径。包括后面的缩放和旋转都是要先改变,再画路径。
(从这里我猜测每次画路径,系统都会去读一次绘图环境的各种参数。这个我还没去研究。)
2.缩放(scale)
<script> //绘图环境缩放 ctx.beginPath(); ctx.save(); ctx.strokeStyle = "green"; ctx.strokeRect(20,20,25,15); ctx.scale(2,2); ctx.strokeRect(20,2); ctx.strokeRect(20,15); ctx.restore(); </script>
这是直接从W3C那边复制过来的代码。如果对绘图环境进行缩放,所有之后的操作也会被缩放。定位也会被缩放。
3.旋转(rotate)
<script> //旋转(基于基点来旋转) ctx.beginPath(); ctx.save(); ctx.strokeRect(20,170,120); ctx.rotate(Math.PI/5); ctx.strokeRect(20,120); ctx.restore(); </script>就像代码中注释的,旋转是基于基点来旋转的。改变基点后旋转的效果是不同的。
四.填充渐变色
<script> //线性渐变(起点之前是起点色,终点之后是终点色) var linear = ctx.createLinearGradient(200,200,350,330); linear.addColorStop(0,"#fff"); linear.addColorStop(0.5,"#f8f"); linear.addColorStop(1,"#f0f"); ctx.fillStyle = linear; ctx.fillRect(200,150,130); //径向渐变 ctx.beginPath(); var radial = ctx.createradialGradient(200,480,10,450,50); radial.addColorStop(0,"#fff"); radial.addColorStop(0.5,"#f8f"); radial.addColorStop(1,"#f0f"); ctx.fillStyle = radial; ctx.arc(200,Math.PI*2,0); ctx.fill(); </script>
渐变填充分线性渐变和径向渐变。
createLinearGradient(x1,y1,x2,y2)创建一个线性渐变对象,(x1,y1)和(x2,y2)连线部分就是渐变色区域。(这里很难表达清楚,看效果更容易理解)
addColorStop()是在区域中添加渐变色。第一个参数在0~1范围之间,表示在线段的哪个位置加渐变色。
设置好渐变对象后记得把渐变对象赋值给填充状态就好了。
createradialGradient(x1,r1,y2,r2)创建径向渐变对象,所谓径向就像水上的涟漪,由圆心往外扩展。(x1,r1)和(x2,r2)分别表示两个圆,渐变色就是从第一个圆开始颜色渐变到第二个圆。
渐变色这部分很难解释清楚,多用一下就好理解了。
最后附上时钟代码
<html> <canvas height="500" width="500" style="border:1px solid black;position:absolute;" id="clock_bg"></canvas> <canvas height="500" width="500" style="border:1px solid black;position:absolute;" id="clock_point"></canvas> <script> var ctx1 = document.getElementById("clock_bg").getContext("2d"); var ctx2 = document.getElementById("clock_point").getContext("2d"); //画钟背景 function print_bg(){ ctx1.save(); ctx1.beginPath(); ctx1.translate(200,200); ctx1.arc(0,2,0); ctx1.fill(); ctx1.beginPath(); ctx1.arc(0,105,0); var len = 0; for(var i=1;i<=60;i++) { ctx1.save(); ctx1.rotate(2*Math.PI*i/60); if(i%5){ len = 5; }else{ len = 10; ctx1.fillText(i,-5,-110); } ctx1.moveto(100,0); ctx1.lineto(100-len,0); ctx1.restore(); } ctx1.stroke(); ctx1.restore(); } //移动指针 function move_point(){ var n_date = new Date(); var hour = n_date.getHours(); var min = n_date.getMinutes(); var sec = n_date.getSeconds(); with(ctx2){ clearRect(0,500,500); save(); translate(200,200); save(); //时针 beginPath(); linewidth = 3; rotate(2*Math.PI*hour/12); moveto(0,0); lineto(0,-45); stroke(); restore(); save(); //分针 beginPath(); linewidth = 2; rotate(2*Math.PI*min/60); moveto(0,-60); stroke(); restore(); save(); //秒针 beginPath(); rotate(2*Math.PI*sec/60); moveto(0,-70); stroke(); restore(); restore(); } } print_bg(); move_point(); setInterval(function(){move_point()},1000); </script> </html>