canvas的一些心得
因為最近課比較少,所以在codepen上逛的時間比較多,藉此可以學習一些優秀的作品,昨天看到了一個很炫的星空,先給大家看看效果。
原始碼出奇的短,總共不過一百餘行,程式碼的邏輯也寫的淺顯易懂,主要思路就是不同軌道的星星繞著中心點旋轉,其次就是半徑越大的星星在視覺上距離我們越近,因為中心點是最遠點,所以最遠點應該旋轉的速率最快,越清晰,更大。讀這段程式碼的時候,並沒有花費很多時間,初讀的時候還是停留在邏輯層面。
- 倒是作者原始碼裡有一段註釋,還是很有必要的,這裡引用一下。
// Thanks @jackrugile for the performance tip! https://codepen.io/jackrugile/pen/BjBGoM // Cache gradient
我們再引用一下注釋下面的程式碼
var canvas2 = document.createElement('canvas'),
ctx2 = canvas2.getContext('2d');
canvas2.width = 100;
canvas2.height = 100;
var half = canvas2.width/2,
gradient2 = ctx2.createRadialGradient(half, half, 0, half, half, half);
gradient2.addColorStop(0.025, '#fff');
gradient2.addColorStop(0.1, 'hsl(' + hue + ', 61%, 33%)');
gradient2.addColorStop(0.25, 'hsl(' + hue + ', 64%, 6%)');
gradient2.addColorStop(1, 'transparent');
ctx2.fillStyle = gradient2;
ctx2.beginPath();
ctx2.arc(half, half, half, 0, Math.PI * 2);
ctx2.fill();
複製程式碼
這段程式碼的意思是在新的canvas 裡畫漸變,對應的是天上的星星。結合作者的註釋,目的就是將不變的元素放在一個離屏的canvas裡,然後在主canvas裡通過drawImage裡引用,以此減少繪製。也是效能調優的一個策略,其實對稍微有點經驗的同學來說,這也是很普通的技巧了,這裡特地拿出來,也是希望能給新同學一點建議。
- 第二點就是色彩
我相信大多數前端愛好者都是和我一樣,本碩都是計算機,對色彩沒有理解,往深處分析了一下作者的程式碼,有很多很有意思的trick,你能感受到作者很有意思的想象力。
星星是會閃爍的,怎麼突出星星的閃爍呢? canvas裡 有shadowBlur的屬性,類似css裡的box-shadow,當然可以用這種方法來突出閃爍,但是這樣的效果並不好!作者的方法很巧妙。作者的思路是用徑向漸變,放上程式碼。
gradient2 = ctx2.createRadialGradient(half, half, 0, half, half, half);
gradient2.addColorStop(0.025, '#fff');
gradient2.addColorStop(0.1, 'hsl(' + hue + ', 61%, 33%)');
gradient2.addColorStop(0.25, 'hsl(' + hue + ', 64%, 6%)');
gradient2.addColorStop(1, 'transparent');
複製程式碼
個人覺得這段程式碼真是神來之筆,這裡還有一個點,就是作者對body背景色的設定
body {
background: #060e1b;
overflow: hidden;
}
複製程式碼
這裡把 #060e1b 轉化成hsl(217, 64%, 6%) 包括後面作者fillRect清屏的時候,填充色也是選擇 hsl(217, 64%, 6%)。這個顏色正是漸變色的第三種顏色。
我們現在來分析作者為什麼要分別選取這漸變的四種顏色呢? 從上往下分別是
#fff 白色
hsl(217, 61%, 33%) 墨藍 夜裡的天空
hsl(217, 61%, 6%) 更墨的藍 因為亮度減少了
rgba(0,0,0,0) //注意注意,原來transparent裡的色值是 rgba(0,0,0,0);
複製程式碼
第一種顏色白色很好理解,是星星的光,第二種顏色是明亮的背景色,第三種因為光少了,所以暗了,最後逐漸變深。 很多人會說最後的rgba(0,0,0,0)有什麼用?
這裡要結合後面部分的程式碼來看
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 0.8;
ctx.fillStyle = 'hsla(' + hue + ', 64%, 6%, 1)';
ctx.fillRect(0, 0, w, h)
ctx.globalCompositeOperation = 'lighter';
複製程式碼
這樣就茅塞對開了,作者用了hsla(' + hue + ', 64%, 6%, 1) 平鋪背景後,用了globalCompositeOperation = 'lighter' 讓星星和背景的顏色融合,這樣最後由於transparent 就完全融入了背景。
而在 hsl(217, 61%, 6%) -> rgba(0,0,0,0) 的變化呢?
我們把 hsl(217, 61%, 6%) 轉換成rgb為 rgb(6, 14, 25) 這個和 rgba(0,0,0) 很接近呀,肉眼看不出來呀。
我們放上 #fff -> rgba(0,0,0,0) 的過渡圖。
大致是變深色,然後變淺,因為最後是透明嗎,這裡就是對應底色了。所以我們可以得出 hsl(217, 61%, 6%) -> rgba(0,0,0,0) 的變化,就是深一點的背景色->背景色的變化,因為兩種顏色相近,真是區別不大。
作者在繪製開頭用了 ctx.globalCompositeOperation = 'source-over'; 來避免星星過多,過亮,用了 ctx.globalAlpha = 0.8 來平鋪背景色,讓有星星的幀,會有一點餘光透過去,好像星星變暗了,都是很棒的想法。
所以以後我們要做閃爍的物體可以參照上面的方法,比如煙火,鐳射之類的
學以致用
受到啟發後,自己下午就寫了一個demo,還是先上效果圖吧。
這裡發光的方案都是來自前面的方案,嘿嘿,作者感覺蠻不錯的,有一種**‘黑黑的天空低垂,亮亮的繁星相隨’**的感覺,好了給大家分析一下我的一些trick吧。這裡把一些不錯的的點給大家分享一下,因為這裡元素比較多,肯定不能按照前面的程式碼組織方式了。
我的組織方式
var Render = {
startCount: 100,
starList: [],
cacheCanvas: {}
init: () => {},
drawFigure: () => {}
}
var Star = () => {};
Star.prototype.draw = () => {}
複製程式碼
其中 Render控制螢幕中的整個動畫,init 方法用於生成星星,草,路燈等。drawFigure用於做動畫,cancheCanvas 用於快取不變的物體,比如星星等。
給大家看看我畫星星的方法吧,大家就明白了我的方案了,我的星星是橫向移動的,所以要考慮星星飛出螢幕的情況,一旦飛出螢幕,要再補上星星。
-
生成星星
對應init方法
for (var i = 0; i < this.startCount; i++) { this.starList.push(new Star(random(w), random(0, h), this.radius)); } 複製程式碼
-
繪製星星
對應drawFigure方法
ctx.globalCompositeOperation = 'source-over'; ctx.globalAlpha = 0.8; ctx.fillStyle = 'hsla(' + hue + ', 64%, 6%, 1)'; ctx.fillRect(0, 0, w, h); ctx.globalCompositeOperation = 'lighter'; for (var i = 0; i < this.starList.length; i++) { if (this.starList[i].draw()) { //星星超出邊界 this.starList.splice(i, 1); this.starList.push(new Star(random(w), random(h), this.radius, 1)); } } 複製程式碼
-
星星的移動
對應star的draw方法
this.x = this.x + 0.5 * Math.random() * Math.random(); var twinkle = random(10); if (twinkle === 1 && this.alpha < 0) { this.alpha += 0.05; } if (twinkle === 2 && this.alpha > 1) { this.alpha -= 0.05; } ctx.globalAlpha = this.alpha; ctx.drawImage(Render.cacheCanvas.star, this.x - this.radius / 2, this.y - this.radius / 2, this.radius, this.radius); return this.x > w || this.y > h ? true : false 複製程式碼
動畫裡的草是貝塞爾曲線繪製的,這個沒有訣竅,只能自己一點點調,我們要讓小草隨風搖擺這裡的訣竅是把畫布旋轉到小草的根部,然後旋轉畫布,貼出程式碼。
ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.theta); ctx.globalCompositeOperation = 'source-over'; 複製程式碼
至於檯燈和月亮就是自己一點點畫,一點點調了,沒有捷徑。canvas就是一塊畫布,你可以在上面發揮你的無盡想象力。課餘多逛逛codepen,學習一下別人優秀的作品,提高自己。