canvas入門里,你沒注意到的那些知識
前言
本文寫在七月底,進來不加班就整理了一下,一些非常基礎的知識,對于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);</code></pre>
繪制圖像: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);</code></pre>

通過上例,我們就能看出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,PI2,false);
crx.strokeStyle = 'red';
crx.moveTo(0,0);
crx.lineTo(0,150);
crx.moveTo(0,0);
crx.lineTo(150,0);
crx.stroke();</code></pre>

坐標系旋轉變換 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,PI2,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();</code></pre>

與上個圖相比,我將兩塊畫布的中心都先變換到了畫布的中心,作為對比,紅色畫筆的做了旋轉變換,在分別繪制了正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做出來的特效。而消逝的流星,頭部亮,尾部暗的效果就是用漸變色做的,在以后的系列中,會結合實例多講一些。
來自:https://segmentfault.com/a/1190000012515382