使用Canvas绘制动态粒子背景

前言

浏览这个网站后,发现它的动态粒子背景效果真不错。

使用Canvas绘制动态粒子背景_第1张图片

起初我以为这个效果是用视频背景实现的,后来发现其粒子可以根据滚动条的位置而加速,并不简单。F12查看后发现其使用了Canvas。于是,想着编写一个JS库来实现类似的“动态粒子背景”效果。

基础知识

Canvas

动态粒子是通过Canvas绘制的,因此需要了解Canvas的一些常用的API方法:

1、创建Canvas元素
<canvas id="bg">canvas>
2、获取Canvas上下文
const canvas = document.getElementById("bg");  
const ctx = canvas.getContext("2d");
3、绘制圆形
ctx.beginPath();  
ctx.arc(100, 100, 50, 0, Math.PI * 2);  
ctx.stroke();
4、设置颜色、填充等效果
ctx.fillStyle = "#ff0000"; // 设置填充颜色为红色  
ctx.strokeStyle = "#0000ff"; // 设置描边颜色为蓝色  
ctx.lineWidth = 5; // 设置线条宽度为5像素  
ctx.font = "24px Arial"; // 设置字体、大小和字体族

节流

节流(throttle)用于减少函数的执行次数。Canvas元素的宽度高度要跟随浏览器宽度高度变化,若不使用节流则会频繁触发绘制的相关参数,影响到浏览器性能。

闭包

通过闭包封装好内部变量,使其不影响外部的变量。

实践

内部变量

//Canvas元素
let canvasElement;
//Canvas 2D对象
let canvasContext;
//Canvas 宽度
let canvasWidth;
//Canvas 高度
let canvasHeight;
//星星列表
let starList;
//星星颜色列表,rgb格式:"255, 255, 255"
let starColorList;
//星星半径大小
let starRadius;
//焦距等级,与canvasWidth相乘,必须大于0
let focalDistanceLevel;
//星星数量等级,与canvasWidth相乘,必须大于0
let starCountLevel;
//星星速度等级,与焦距相乘,必须大于0
let starSpeedLevel;
//焦距
let focalDistance;
//星星数量
let starCount;
//执行动画
let rAF;

内部方法

init初始化

定义初始化方法,初始化的参数必须是一个Canvas元素对象。初始化的内容是设置Canvas元素的初始背景色、Canvas画布的宽度和高度、粒子的颜色列表、粒子的半径、焦距的等级、粒子的数量等级、粒子的速度等级。

	    canvasElement = canvas_element;
        canvasElement.width = canvasElement.clientWidth;
        canvasElement.height = canvasElement.clientHeight;
        canvasElement.style.backgroundColor = "black";
        canvasContext = canvasElement.getContext("2d");
        canvasWidth = canvasElement.clientWidth;
        canvasHeight = canvasElement.clientHeight;
        starColorList = ["255, 255, 255"];
        starRadius = 1;
        focalDistanceLevel = 0.4;
        starCountLevel = 0.2;
        starSpeedLevel = 0.0005;
        focalDistance = canvasWidth * focalDistanceLevel;
        starCount = Math.ceil(canvasWidth * starCountLevel);

生成每个粒子的属性

	    starList = [];
        for (let i = 0; i < starCount; i++) {
          starList[i] = {
            x: canvasWidth * (0.1 + 0.8 * Math.random()),
            y: canvasHeight * (0.1 + 0.8 * Math.random()),
            z: focalDistance * Math.random(),
            color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
          }
        }}

粒子的x、y、z属性是随机确定的。x、y设置初次渲染在距离屏幕边界的10%的范围内,而不至于刚刚渲染出现就要离开屏幕范围。z表示粒子距离屏幕面的距离。

注册浏览器窗口尺寸变化事件

	    const self = this;
        window.addEventListener("resize", self.throttle(function () {
          canvasElement.width = canvasElement.clientWidth;
          canvasElement.height = canvasElement.clientHeight;
          canvasWidth = canvasElement.clientWidth;
          canvasHeight = canvasElement.clientHeight;
          focalDistance = canvasWidth * focalDistanceLevel;

          const starCount2 = Math.ceil(canvasWidth * starCountLevel);
          if (starCount > starCount2) {
            starList.splice(starCount2);
          } else {
            let num = starCount2 - starCount;
            while (num--) {
              starList.push({
                x: canvasWidth * (0.1 + 0.8 * Math.random()),
                y: canvasHeight * (0.1 + 0.8 * Math.random()),
                z: focalDistance * Math.random(),
                color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
              });
            }
          }
          starCount = Math.ceil(canvasWidth * starCountLevel);
        }, 200));
render渲染
	 const starSpeed = canvasWidth * focalDistanceLevel * starSpeedLevel;
      //清空画布
      canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
      //计算位置
      for (let i = 0; i < starList.length; i++) {
        const star = starList[i];
        const star_x = (star["x"] - canvasWidth / 2) * (focalDistance / star["z"]) + canvasWidth / 2;
        const star_y = (star["y"] - canvasHeight / 2) * (focalDistance / star["z"]) + canvasHeight / 2;
        star["z"] -= starSpeed;
        if (star["z"] > 0 && star["z"] <= focalDistance && star_x >= -20 && star_x <= canvasWidth + 20 && star_y >= -20 && star_y <= canvasHeight + 20) {
          const star_radius = starRadius * (focalDistance / star["z"] * 0.8);
          const star_opacity = 1 - 0.8 * (star["z"] / focalDistance);
          canvasContext.fillStyle = "rgba(" + star["color"] + ", " + star_opacity + ")";
          canvasContext.shadowOffsetX = 0;
          canvasContext.shadowOffsetY = 0;
          canvasContext.shadowColor = "rgb(" + star["color"] + ")";
          canvasContext.shadowBlur = 10;
          canvasContext.beginPath();
          canvasContext.arc(star_x, star_y, star_radius, 0, 2 * Math.PI);
          canvasContext.fill();
        } else {
          const z = focalDistance * Math.random();
          star["x"] = canvasWidth * (0.1 + 0.8 * Math.random());
          star["y"] = canvasHeight * (0.1 + 0.8 * Math.random());
          star["z"] = z;
          star["color"] = starColorList[Math.ceil(Math.random() * 1000) % starColorList.length];
        }
      }
      const self = this;
      rAF = window.requestAnimationFrame(function () {
        self.render();
      });

粒子的动态变化是由于z值的变化而引起的,根据焦距与z的比值等于实时xx与初始xx比值计算出此时粒子的位置。

速度是根据粒子的z减少值确定,速度越快则z值变小得越快。

粒子是否在屏幕范围是根据粒子实时的x、y、z确定,x、y、z若不满足设定条件则判定此粒子已离开屏幕范围,需要重置粒子的属性。

粒子持续变化需要递推调用render方法,为什么不使用setInterval或setTimeout?使用这两个页面会存在卡顿,效果不好,而requestAnimationFrame 是一个浏览器的 API,用于创建平滑的动画效果。它允许你请求浏览器在下一次重绘之前调用指定的函数来更新动画。这比使用 setIntervalsetTimeout 更高效,因为它会在屏幕刷新的时刻执行回调,从而确保动画的流畅性。requestAnimationFrame 的一个重要特性是,它会在页面不可见或者在后台标签页时暂停,这可以节省CPU资源。

destroy销毁
	  window.cancelAnimationFrame(rAF);
      starList = [];
      canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
      canvasElement.width = 0;
      canvasElement.height = 0;

需要将starList清空释放内存,同时取消动画更新,释放资源。

setStarColorList设置粒子颜色列表
	setStarColorList: function (color, mode = false) {
      if (typeof color === 'object') {
        starColorList = color;
      } else if (typeof color === 'string') {
        starColorList.push(color);
      }
      if (mode) {
        for (let i = 0; i < starList.length; i++) {
          starList[i]["color"] = starColorList[Math.ceil(Math.random() * 1000) % starColorList.length];
        }
      }
    }

此方法接受两个参数,第一个参数表示颜色,第二个参数表示是否立刻同步颜色。

setStarSpeedLevel设置粒子速度等级
	setStarSpeedLevel: function (star_speed_level = 0.0005) {
      starSpeedLevel = star_speed_level
    }

效果

演示地址

使用Canvas绘制动态粒子背景_第2张图片

项目代码

StarrySky

你可能感兴趣的:(html,javascript,css,前端)