canvas動畫心得

NO IMAGE

canvas的一些心得

因為最近課比較少,所以在codepen上逛的時間比較多,藉此可以學習一些優秀的作品,昨天看到了一個很炫的星空,先給大家看看效果。

canvas動畫心得

源碼出奇的短,總共不過一百餘行,代碼的邏輯也寫的淺顯易懂,主要思路就是不同軌道的星星繞著中心點旋轉,其次就是半徑越大的星星在視覺上距離我們越近,因為中心點是最遠點,所以最遠點應該旋轉的速率最快,越清晰,更大。讀這段代碼的時候,並沒有花費很多時間,初讀的時候還是停留在邏輯層面。

  • 倒是作者源碼裡有一段註釋,還是很有必要的,這裡引用一下。

// 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) 的過渡圖。

canvas動畫心得

大致是變深色,然後變淺,因為最後是透明嗎,這裡就是對應底色了。所以我們可以得出 hsl(217, 61%, 6%) -> rgba(0,0,0,0) 的變化,就是深一點的背景色->背景色的變化,因為兩種顏色相近,真是區別不大。

作者在繪製開頭用了 ctx.globalCompositeOperation = ‘source-over’; 來避免星星過多,過亮,用了 ctx.globalAlpha = 0.8 來平鋪背景色,讓有星星的幀,會有一點餘光透過去,好像星星變暗了,都是很棒的想法。

所以以後我們要做閃爍的物體可以參照上面的方法,比如煙火,激光之類的

學以致用

受到啟發後,自己下午就寫了一個demo,還是先上效果圖吧。

canvas動畫心得

這裡發光的方案都是來自前面的方案,嘿嘿,作者感覺蠻不錯的,有一種**‘黑黑的天空低垂,亮亮的繁星相隨’**的感覺,好了給大家分析一下我的一些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,學習一下別人優秀的作品,提高自己。

相關文章

JAVA併發之多線程基礎(1)

寫一個簡易中間件

200行代碼寫一個簡易的dva

從傳統動畫到react動畫過渡