canvas动画心得

2,927 阅读6分钟

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) 的过渡图。

white->black

大致是变深色,然后变浅,因为最后是透明吗,这里就是对应底色了。所以我们可以得出 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,学习一下别人优秀的作品,提高自己。