canvas入门里,你没注意到的那些知识
与看各种文章相比,我更喜欢数学里的逻辑;与学习各种日新月异的框架相比,我更喜欢基础扎实带给人的那种踏实;与拼凑页面页面来回跳转相比,我更喜欢动画,图形在页面中表现的直观。
也许你和我一样,冲着对H5的好奇,冲着对图形的热爱,学了一下canvas,没有熟练,只是简单入了个门,或许你在入门的门槛上就绊倒了,同学不哭,站起来继续撸。我把我入门两天的积累写下来,有些门槛,绊倒了数百次,有些应该也适合你。下文的ctx指的是canvas创建的2d图形化实例,代码:
var canvas = document.querySelector('#test'); //test是Html中一个canvas元素的ID,其宽为600,高为400; var ctx = canvas.getContext('2d');
说件重要的事
2d上下文(画布)坐标开始于<canvas>元素的左上角,其顶点为坐标原点,x轴正方向指向屏幕右侧,Y轴正方向指向屏幕下方。所以x值越大越靠右,Y值越大越靠下。这和我们传统的坐标轴是有很大区别的,下面一张图,也许能看出。Y轴反向也许很好理解,但顺时针正0度在坐标轴的那个位置,就值得我们好好记住了,使用arc,熟悉这个常识很重要。
四个基本方法
描边:stroke与strokeStyle
ctx.strokeStyle = 'red'; ctx.arc(200,200,50,0,PI*2,false); ctx.stroke();
lineTo,moveTo方法,很基础,这里特别说一下arc(x,y,r,startAngle,endAngle,direction)画圆的方法,这个方法接受6个参数,前五个是必须的,分别是圆心位置(x坐标,y坐标),然后是圆的半径r,再然后是起始位置角和终止位置角,这两个很重要,结合前面那张图理解更,我们可知,上面这段代码绘制的第一个点是(0,50),终止位置也是(0,50)。后面direction是一个可选参数,默认为false,表示顺时针绘制,为true时,为逆时针绘制。
特别说明:
- 红宝书上说绘制路径,都应该以beginPath()方法开始,但实践证明,这并不需要,你只需要在stroke这个方法来结束这段路径的绘制,但加了也不坏事,所以还是加上吧;
- 只调用一次lineTo(0,50)方法,你是看不到一条直线绘制出来的,因为(0,0)并不是默认的起点,除非你先用moveTo()方法设置一个起点,这个简单的道理叫两点确定一条直线;
- closePath()并不是一个与beginPath对照使用的方法,其作用是绘制闭合路径,比如下图所示。可以看到我们只调用了两次lineTo方法,但最后绘制除了三条线,最后这一条线就是closePath作用出来的。但需要注意的是,closePath需在stroke使用前调用。
填充:fillStyle与fill
ctx.fillStyle = 'red' ctx.arc(200,200,50,0,PI/3,false); ctx.fill();
与描边对应的就是填充,但这个方法很不讲究,通常来讲,只有封闭区间才有资格填充,但fill这个方法不需要,只要你的路径不是一条直线,那它就能被填充,也就是不在同一条直线上的三点,能确定一个面,这个方法真的很牛逼,我是必须服气的。
绘制文本:stokeText与fillText
以上两个方法都能写出一个字,但推荐的用法是fillText,因为其写出来的是实心字,而stokeText写出来的是描边空心字,绝大多数艺术字会采用这种写法。当然,你愿意的话也可以两者结合着用。
ctx.fillStyle = 'red' ctx.font= 'bold 12px Arial'; ctx.fillText('Test',300,200); ctx.strokeStyle = 'red' ctx.font= 'bold 12px Arial'; ctx.strokeText('Test',400,200);
绘制图像:drawImage
相信很多入门的,都看不到这个地方,canvas不就是绘制图像的嘛,啊,不准确,canvas是绘制图形的。具体说来就是drawImage,它不只能把图片绘制到画布上,他还能绘制canvas图形,这个在星空,雨滴等案列中应用最多,也是canvas离屏优化用的最多的,看下面这个示例。
var cache = document.createElement('canvas');
cache.width = 50;
cache.height = 50;
var cacheCtx = cache.getContext('2d'); //这是一块虚拟画布,因为未添加到dom节点中;
cacheCtx.beginPath(); cacheCtx.strokeStyle = 'red'; cacheCtx.moveTo(5,5); cacheCtx.lineTo(20,40); cacheCtx.lineTo(40,20); cacheCtx.closePath(); cacheCtx.stroke(); ctx.drawImage(cache,50,50); ctx.drawImage(cache,50,100); ctx.drawImage(cache,100,50); ctx.drawImage(cache,100,100);
通过上例,我们就能看出drawImage在离屏优化中的应用。为什么这样可以,因为,像上面一样,我们要在一块画布上,画四个相同的三角形,这在浏览器表现上,你看不出任何卡顿,那如果你是要在屏幕上绘制一千个或万个这样的图形,且每一帧还要有规律的变换他们的位置,这时你就能明显感觉出浏览器的卡顿,因为你一直在操作dom中的元素,使浏览器一直在不停的重新绘制,渲染网页。但如果你在每一帧绘制下一个状态前,在js中,通过在虚拟的canvas中先绘制好下一帧的背景状态,然后通过drawImage这个方法,来更新浏览器中的画布状态,这样网页在一帧中就只需要渲染一次,而不是时时在渲染,这就达到了离屏优化的目的,所以这也是drawImage用的最多的场景。
过完以上知识,就算是复习一下入门知识了,至于为什么没提fillRect与strokeRect,其实那就是stroke与fill提供给绘制矩形的一个语法糖。
奇妙的变换
也许你和我一样,不习惯自己的坐标系是从左上角开始的,我们更习惯坐标原点在左下角或者正中心。
坐标原点变换 translate方法
原点变换到画布中心:ctx.translate(WIDTH/2,HEIGHT/2),WIDTH与HEIGHT分别指画布的宽与高,为了将坐标变换表现得更明显,我在同一块区域,画了两块大小相似的画布,然后让一块使用原始坐标系,一块采用translate变换后的,具体看代码,和结果图;
<div class="red"> <canvas id="test" width="600px" height="400px"></canvas> <canvas id="testa" width="598px" height="398px"></canvas> </div> var ctx = document.querySelector('#test').getContext('2d'); var crx = document.querySelector('#testa').getContext('2d'); ctx.beginPath(); ctx.strokeStyle = 'blue'; ctx.arc(0,0,5,0,PI*2,false); ctx.moveTo(0,0); ctx.lineTo(0,150); ctx.moveTo(0,0); ctx.lineTo(150,0); ctx.stroke(); crx.beginPath(); crx.translate(300,200); crx.arc(0,0,5,0,PI*2,false); crx.strokeStyle = 'red'; crx.moveTo(0,0); crx.lineTo(0,150); crx.moveTo(0,0); crx.lineTo(150,0); crx.stroke();
坐标系旋转变换 rotate方法
也许你接触canvas很久了,但rotate方法始终没有机会用,只记得红宝书上在绘制钟表时针,分针时,提到了rotate的用法,但只要你熟悉,用法就能千奇百怪。API明确的说rotate(angle),是指围绕原点图像旋转angle弧度。所以,你需要记住两点。第一:图像是围绕原点旋转;第二:与其说是图像旋转,不如说是整个坐标系旋转了。看示例,还是和上面一样,有对比,才有说服力:
ctx.beginPath(); ctx.strokeStyle = 'blue'; ctx.translate(300,200); ctx.arc(0,0,5,0,PI*2,false); ctx.moveTo(0,0); ctx.lineTo(0,150); ctx.moveTo(0,0); ctx.lineTo(150,0); ctx.stroke(); ctx.moveTo(0,0); ctx.lineTo(0,-150); ctx.stroke(); crx.beginPath(); crx.strokeStyle = 'red'; crx.translate(300,200); crx.rotate(PI/3); //旋转60* crx.arc(0,0,5,0,PI*2,false); crx.moveTo(0,0); crx.lineTo(0,150); crx.moveTo(0,0); crx.lineTo(150,0); crx.stroke(); crx.moveTo(0,0); crx.lineTo(0,-150); crx.stroke();
与上个图相比,我将两块画布的中心都先变换到了画布的中心,作为对比,红色画笔的做了旋转变换,在分别绘制了正x,正y轴以后,在stoke方法结束一段绘制以后,又绘制了负y轴,可以看到,上一段绘制路径的rotate仍然起作用,所以rotate是对画布坐标系做了变换,而不是狭义上的绘制图像,这个区别很重要。后面在上一段代码,不用sin,cos计算,就能绘制一个正多边形,这里以6边形为例。
ctx.beginPath(); ctx.translate(300,200); ctx.strokeStyle = 'red'; var a=0; for(var i=1;i<7;i++){ ctx.lineTo(0,150); ctx.rotate(PI/3); a =a + PI/3 ; } ctx.closePath(); ctx.stroke(); ctx.lineTo(0,0); ctx.stroke();
不重要,但很点题的API
绘制一个普通的图形,也许上面的API也已足够,但要让你的图形有亮点,你可能还需要多了解一些,我个人觉得非常有用的两个就是阴影的绘制(shadow)与渐变色的添加(createLinearGradient)。在用canvas做星星背景时,或者飞行的流星周围的闪光,这些都是用shadow做出来的特效。而消逝的流星,头部亮,尾部暗的效果就是用渐变色做的,在以后的系列中,会结合实例多讲一些。