Canvas下雪效果实现

适逢双旦,也许有些眼尖的网友已经发现本博悄悄上线的下雪效果,这里简单的记录一下其实现。

其实用js来写一个下雪效果并不难,但是如果要兼顾到效果的逼真度,流畅度,用户体验那就需要好好思考一下了,至少有几个问题需要想清楚:

1、绘制是用dom来做还是canvas等别的技术?

如果使用dom来绘制,那么会有性能上的瓶颈,在动态控制的节点较少时,看不太明显,但是满屏的雪花肯定是会有一些能感知的卡顿的,但反观canvas,其运行会被硬件加速,流畅度会好很多。

2、雪花素材是使用图片还是用canvas亦或是svg?

如果选择用dom来做,那么最简单的做法就是使用图片,当然也可以使用canvas或者svg,这种情况这里不做详细说明,重点说一下选用canvas我们应该选用图片素材还是直接绘制,利用canvas我们也是可以轻松绘制出雪花的,可参考我之前的一片文章《js分形艺术-koch雪花曲线》,但是这种绘制雪花的方法是通过迭代来实现的,其性能肯定不好,想一想满屏的雪花呀,cpu不得被疯吃,所以这里建议采用图片素材。

想明白了这些,其实现就相对简单了,下雪无非就是坐标的更新罢了,当然为了尽可能逼真中间还是有一些小细节需要注意,比如雪花的飘落方向,有可能忽左忽右吧,比如雪花有可能有大有小吧等等, 下面贴出我的实现代码:

(function(window, document, undefined) {
  // 存储所有的雪花
  const snows = [];

  // 下落的加速度
  const G = 0.01;

  const fps = 60;

  // 速度上限,避免速度过快
  const SPEED_LIMIT_X = 1;
  const SPEED_LIMIT_Y = 1;

  const W = window.innerWidth;
  const H = window.innerHeight;

  let tickCount = 150;
  let ticker = 0;
  let lastTime = Date.now();
  let deltaTime = 0;

  let canvas = null;
  let ctx = null;

  let snowImage = null;

  window.requestAnimationFrame = (function() {
    return window.requestAnimationFrame ||
           window.webkitRequestAnimationFrame ||
           window.mozRequestAnimationFrame ||
           window.oRequestAnimationFrame ||
           window.msRequestAnimationFrame ||
           function (callback) {
              setTimeout(callback, 1000/ fps);
            }
  })();

  init();

  function init() {
    createCanvas();
    canvas.width = W;
    canvas.height = H;
    canvas.style.cssText = 'position: fixed; top: 0; left: 0; pointer-events: none;';
    document.body.appendChild(canvas);
    // 小屏幕时延长添加雪花时间,避免屏幕上出现太多的雪花
    if (W < 768) {
      tickCount = 350;
    }

    snowImage = new Image();
    snowImage.src = 'snow.png';

    loop();
  }

  function loop() {
    requestAnimationFrame(loop);

    ctx.clearRect(0, 0, W, H);

    const now = Date.now();
    deltaTime = now - lastTime;
    lastTime = now;
    ticker += deltaTime;

    if (ticker > tickCount) {
      snows.push(
        new Snow(Math.random() * W, 0, Math.random() * 5 + 5)
      );
      ticker %= tickCount;
    }

    const length = snows.length;
    snows.map(function(s, i) {
      s.update();
      s.draw();
      if (s.y >= H) {
        snows.splice(i, 1);
      }
    });
  }

  function Snow(x, y, radius) {
    this.x = x;
    this.y = y;
    this.sx = 0;
    this.sy = 0;
    this.deg = 0;
    this.radius = radius;
    this.ax = Math.random() < 0.5 ? 0.005 : -0.005;
  }

  Snow.prototype.update = function() {
    const deltaDeg = Math.random() * 0.6 + 0.2;

    this.sx += this.ax;
    if (this.sx >= SPEED_LIMIT_X || this.sx <= -SPEED_LIMIT_X) {
      this.ax *= -1;
    }

    if (this.sy < SPEED_LIMIT_Y) {
      this.sy += G;
    }

    this.deg += deltaDeg;
    this.x += this.sx;
    this.y += this.sy;
  }

  Snow.prototype.draw = function() {
    const radius = this.radius;
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.rotate(this.deg * Math.PI / 180);
    ctx.drawImage(snowImage, -radius, -radius, radius * 2, radius * 2);
    ctx.restore();
  }

  function createCanvas() {
    canvas = document.createElement('canvas');
    ctx = canvas.getContext('2d');
  }

})(window, document);

使用方式只需要将上面代码保存为js文件,然后在任意一个html文件中引用就可以了.

这里就不提供演示地址了,因为博客中的雪花一直在飘落...

最后,附上雪花素材:

  • 支付宝二维码 支付宝
  • 微信二维码 微信
相关文章