前言

要制作一个可以作为背景的特效,那么首要的条件就是这是全屏的背景,充满整个屏幕。

为了使背景贴合好看,一般来说都是纯色或者渐变的。

渐变背景网站 uigradients

这个网站可以自己生成渐变色,你的配色也可以跟大家分享,可以保存为图片,也可以导出为 CSS 样式。

实现随机粒子

粒子特效的特点

  • 数量多
  • 运动
  • 随机

我们来使用 Canvas 实现随机粒子效果。各项参数都是随机生成的。

创建全屏 Canvas

首先,我们需要一个全屏的 Canvas 画布。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    html,
    body {
      margin: 0;
      overflow: hidden;
      width: 100%;
      height: 100%;
      cursor: none;
      background: black;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>

  <script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        round = [],
        WIDTH,
        HEIGHT;

    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;

    ctx.width = WIDTH;
    ctx.height = HEIGHT;

  </script>
</body>

</html>

我们使用 WIDTHHEIGHT 两个常量储存屏幕宽度和高度。

设置 Round_item 类

在创建了一个全屏的 Canvas 之后,再来创建单个的 Round_item 类。

首先我们 Round_item 类需要有什么参数呢?

  • 位置随机
  • 透明度随机
  • 半径随机的圆
  • 为了区分不同的圆,我们还应该设置一个唯一的 index 参数。

所以我们需要的参数有:

  • x 坐标
  • y 坐标
  • 半径
  • 透明度
  • index

根据上面这些可以得出我们的 Round_item 类:

function Round_item(index, x, y) {
  this.index = index;
  this.x = x;
  this.y = y;
  this.r = Math.random() * 2 + 1;
  var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
  this.color = 'rgba(255,255,255,' + alpha + ')';
}

这里我们使用了构造函数的方式来创建单个的圆,我们还需要一个变量 initRoundPopulation 来设置 round 的个数,然后我们便可以通过 for 循环创建出 initRoundPopulation 个圆。

设置 draw() 方法

在设置了单个的 Round_item 类之后,我们还要给每一个 round 设置 draw() 方法,所以我们需要将 draw() 方法设置在 Round_item 的原型中,这样我们创建出来的每一个 Round_item 实例对象都拥有了 draw() 方法。

Round_item.prototype.draw = function() {
  content.fillStyle = this.color;
  content.shadowBlur = this.r * 2;
  content.beginPath();
  content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
  content.closePath();
  content.fill();
};

设置初始化 init() 函数

然后我们就需要设置初始化 init() 函数了,在 init() 函数中,我们的主要任务是创建单个的 round,然后使用其 draw() 方法。

function init() {
  for (var i = 0; i < initRoundPopulation; i++) {
    round[i] = new Round_item(i, Math.random() * WIDTH, Math.random() * HEIGHT);
    round[i].draw();
  }
}

至此,我们已经完成了随机粒子的实现,完整的代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    html,
    body {
      margin: 0;
      overflow: hidden;
      width: 100%;
      height: 100%;
      cursor: none;
      background: black;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>

  <script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        round = [],
        WIDTH,
        HEIGHT,
        initRoundPopulation = 80;


    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;

    ctx.width = WIDTH;
    ctx.height = HEIGHT;

    function Round_item(index, x, y) {
      this.index = index;
      this.x = x;
      this.y = y;
      this.r = Math.random() * 2 + 1;
      var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
      this.color = "rgba(255,255,255," + alpha + ")";
    }

    Round_item.prototype.draw = function () {
      content.fillStyle = this.color;
      content.shadowBlur = this.r * 2;
      content.beginPath();
      content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
      content.closePath();
      content.fill();
    };

    function init() {
      for (var i = 0; i < initRoundPopulation; i++) {
        round[i] = new Round_item(i, Math.random() * WIDTH, Math.random() * HEIGHT);
        round[i].draw();
      }

    }

    init();
  </script>
</body>

</html>

让随机粒子运动

animate() 函数

Canvas 制作动画是一个不断擦除再重绘的过程,我们需要一个 animate() 函数,这个函数的作用是帮助我们形成动画,我们在这个函数中首先需要清除当前屏幕,这里的清除函数用到的是 content.clearRect() 方法。

context.clearRect(x,y,width,height);

  • x:要清除的矩形左上角的 x 坐标
  • y:要清除的矩形左上角的 y 坐标
  • width:要清除的矩形的宽度
  • height:要清除的矩形的高度

需要清除的区域是整个屏幕,所以 content.clearRect() 的参数就是 content.clearRect(0, 0, WIDTH, HEIGHT);,这里我们就用到了之前获取的屏幕宽度和高度的常量:WIDTHHEIGHT。这样我们就将屏幕上的所有内容都清除了。

清除之后需要重新绘制,重新绘制的图形是需要和原图形之间有一定的关系,先让粒子匀速上升,也就是 y 坐标在不断地变化,既然是匀速的,那么也就是在相同的时间位移是相同的。

将粒子位移的变化函数 move() 写在 Round_item 的原型上。

重绘完图形之后,还需要不断地进行清除再重绘。

 首先想到的是的 setTimeout() 方法,但是 setTimeoutsetInterval 有个问题,了解 js 运行机制的应该明白,它们的时间间隔参数实际上只是指定了把动画添加到线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。

我们需要使用另外一个函数 —— requestAnimationFrame()

window.requestAnimationFrame() 方法告诉浏览器,你希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。

requestAnimationFrame 采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

所以我们就使用 requestAnimationFrame() 函数递归的调用 animate() 函数来实现动画的效果。

function animate() {
  content.clearRect(0, 0, WIDTH, HEIGHT);

  for (var i in round) {
    round[i].move();
  }
  requestAnimationFrame(animate);
}

创建 move() 函数

move() 方法写在 Round_item 的原型上,这样我们创建的每一个 round 都具有了 move() 方法。

move() 方法中,我们只需要改变 round 的 y 坐标即可,并且设置边界条件,当 y 坐标的值小于 -10(也可以是其他负值),代表该 round 已经超出了屏幕,这个时候我们要将其移动到屏幕的最底端,这样才能保证我们创建的粒子数不变,一直是 initRoundPopulation 的值。

这样就是一个粒子在不断地上升,上升到了最顶端再移动到最底端的循环过程,看起来像是有源源不断的粒子,但其实总数是不变的。

在 y 坐标的变化之后,我们还需要使用新的 y 坐标再来重新绘制一下该 round。

Round_item.prototype.move = function () {
  this.y -= 0.15;
  if (this.y <= -10) {
    this.y = HEIGHT + 10;
  }
  this.draw();
}

init() 中加入 animate()

我们想要实现动画的效果,还需要在 init() 中加入 animate() 函数。

完整实现代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    html,
    body {
      margin: 0;
      overflow: hidden;
      width: 100%;
      height: 100%;
      cursor: none;
      background: black;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>

  <script>
    var ctx = document.getElementById('canvas'),
        content = ctx.getContext('2d'),
        round = [],
        WIDTH,
        HEIGHT,
        initRoundPopulation = 80;


    WIDTH = document.documentElement.clientWidth;
    HEIGHT = document.documentElement.clientHeight;

    ctx.width = WIDTH;
    ctx.height = HEIGHT;

    function Round_item(index, x, y) {
      this.index = index;
      this.x = x;
      this.y = y;
      this.r = Math.random() * 2 + 1;
      var alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
      this.color = "rgba(255,255,255," + alpha + ")";
    }

    Round_item.prototype.draw = function () {
      content.fillStyle = this.color;
      content.shadowBlur = this.r * 2;
      content.beginPath();
      content.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
      content.closePath();
      content.fill();
    };

    function animate() {
      content.clearRect(0, 0, WIDTH, HEIGHT);

      for (var i in round) {
        round[i].move();
      }
      requestAnimationFrame(animate)
    }

    Round_item.prototype.move = function () {
      this.y -= 0.15;
      if (this.y <= -10) {
        this.y = HEIGHT + 10;
      }
      this.draw();
    };


    function init() {
      for (var i = 0; i < initRoundPopulation; i++) {
        round[i] = new Round_item(i, Math.random() * WIDTH, Math.random() * HEIGHT);
        round[i].draw();
      }
      animate();

    }

    init();
  </script>
</body>

</html>