likes
comments
collection
share

初识canvas动画

作者站长头像
站长
· 阅读数 28

引言:

前端技术的迅猛发展为我们带来了一个充满创意和想象力的世界。在这个世界中,我们经常会遇到一些令人惊叹的代码片段,它们能够通过精妙的算法和创造性的设计,为用户呈现出绚丽的动态效果。本文将带您深入解读一个令人赞叹的代码片段,它展示了如何利用随机数和画布技术创建一个迷人的图形效果。

概述

今天看到了一个早时候遇见的粒子动画的功能实现,就跟着动手做了一下,记录一下实现过程,整理思路。

初识canvas动画 我觉得其中最重要的几个点

  • 点的创建
  • 点与点之间的连线
  • 动画

点的创建

首先封装一个class用来生成一个点,这个类拥有一个x和y属性用来表示点的坐标,可以利用随机数来使每次生成的都不在同一位置。然后在声明一个方法调用canvas的API来把这个点画出来。

// canvas 为 canvas dom对象 , 
class Point {
    constructor() {
        // 利用随机数随机生成一个坐标
        this.x = getRandom(0, this.canvas.width);
        this.y = getRandom(0, this.canvas.height);
        this.color = “#fff”;
    }
    
    draw() {
        // 根据坐标、半径画出 点
        this.ctx.beginPath();
        this.ctx.arc(this.x, this.y, 6, 0, 2 * Math.PI);
        this.ctx.fillStyle = this.config.color;
        this.ctx.fill();
    }
}

图像类

声明一个图像类,用来统一创建,并将他们连线,图中可知两个点距离越远线的颜色越浅,当超出一定距离时两个点之间不连线。我们可用勾股定理A2+B2=C2A^2 + B^2 = C^2A2+B2=C2求出2点之间的直线距离与最大距离的比例,值越大 距离越远 颜色越浅

class Graph {
    constructor() {
        this.canvas.width = container.clientWidth;
        this.canvas.height = container.clientHeight;
        this.maxDis = 300;
        // 实例化点 生成一个集合
        this.points = new Array(40).fill(0).map(() => new Point());
    }
    
    draw() {
        // 遍历点集合 将点画在画布上
        for (let i = 0; i < this.points.length; i++) {
            const point1 = this.points[i];
            point1.draw();
            // 将点连成线
            for (let j = i + 1; j < this.points.length; j++) {
                const point2 = this.points[j];
                // 勾股定理求出2个点的直线距离
                const dis = Math.sqrt(Math.abs(point1.x - point2.x) ** 2 + Math.abs(point1.y - point2.y) ** 2);
                // 大于 “可连线的最宽距离” 时 不连线
                if (dis > this.config.maxDis) {
                    continue;
                }
                // 将两个点连接起来
                this.ctx.beginPath();
                this.ctx.moveTo(point1.x, point1.y);
                this.ctx.lineTo(point2.x, point2.y);
                this.ctx.strokeStyle = `rgba(255,255,255,${1 - dis / this.maxDis})`; // 设置透明度 距离越远 颜色越浅
                // 设置整体线宽
                this.ctx.lineWidth = this.config.lineWidth;
                this.ctx.stroke();
                this.ctx.closePath();
            }
        }
    }
}

动画

动画实现起来非常简单,我们只需要一直移动的位置,并且通过Graphdraw方法重新画点连线, ⚠注意点在移动的时候要处理超过边界的情况。

完整代码
function getRandom(min, max, fixedLen = 2) {
  const minValue = Math.min(min, max);
  const maxValue = Math.max(min, max);

  let randomNumber;

  if (minValue >= 0 && maxValue > 0) {
    randomNumber = Number((Math.random() * (maxValue - minValue) + minValue).toFixed(fixedLen));
  } else if (minValue < 0 && maxValue >= 0) {
    randomNumber = Number((Math.random() * (maxValue - minValue) + minValue).toFixed(fixedLen));
  } else if (minValue < 0 && maxValue <= 0) {
    randomNumber = Number(0 - (Math.random() * Math.abs(maxValue - minValue) + Math.abs(maxValue)).toFixed(fixedLen));
  }

  return randomNumber;
}

class Point {
  constructor(canvas, ctx, config = { color: "#fff", radius: 6 }) {
    this.canvas = canvas;
    this.ctx = ctx;
    // 获取配置
    this.config = config;

    // 利用随机数随机生成一个坐标
    this.x = getRandom(0, this.canvas.width);
    this.y = getRandom(0, this.canvas.height);
    this.color = this.config.color;

    // 设置移动速度
    this.xSpeed = getRandom(-50, 50);
    this.ySpeed = getRandom(-50, 50);
    this.lastDrawTime = null; // 最后一个更改的时间
  }

  draw() {
    if (this.lastDrawTime) {
      // 距离上一次动画的间隔
      const elapsedTime = (Date.now() - this.lastDrawTime) / 1000;

      //   间隔乘以运动速度,计算出移动的距离
      let xDis = elapsedTime * this.xSpeed,
        yDis = elapsedTime * this.ySpeed;

      // 获取移动后的点坐标x,y
      let x = this.x + xDis,
        y = this.y + yDis;

      // 判断点是否超出边界,超出时改变方向 反向运动
      if (x > this.canvas.width - this.config.radius / 2) {
        x = this.canvas.width - this.config.radius / 2;
        this.xSpeed = -this.xSpeed;
      } else if (x < 0) {
        x = 0;
        this.xSpeed = -this.xSpeed;
      }

      if (y > this.canvas.height - this.config.radius / 2) {
        y = this.canvas.height - this.config.radius / 2;
        this.ySpeed = -this.ySpeed;
      } else if (y < 0) {
        y = 0;
        this.ySpeed = -this.ySpeed;
      }

      // 重新设置点坐标;
      this.x = x;
      this.y = y;
    }

    // 根据坐标、半径画出 点
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, this.config.radius * devicePixelRatio, 0, 2 * Math.PI);
    this.ctx.fillStyle = this.config.color;
    this.ctx.fill();

    this.lastDrawTime = Date.now(); // 记录当前时间
  }
}

class Graph {
  constructor(containerId, config = { bgColor: "#000", lineWidth: 1, pNumbers: 40, maxDis: 200, pointConfig: {} }) {
    // 获取容器
    const container = document.querySelector(containerId);
    // 创建canvas并设置样式
    this.canvas = document.createElement("canvas");
    this.canvas.style.cssText = `position:absolute;left:0;top:0;background:${config.bgColor};`;
    this.canvas.width = container.clientWidth * devicePixelRatio;
    this.canvas.height = container.clientHeight * devicePixelRatio;
    container.appendChild(this.canvas);

    // 获取配置
    this.config = config;

    // 获取canvas上下文
    this.ctx = this.canvas.getContext("2d");

    // 实例化点 生成一个集合
    this.points = new Array(40).fill(0).map(() => new Point(this.canvas, this.ctx, this.config.pointConfig));
    
  }

  /** 画点连线 */
  draw() {
    // 添加动画 每一帧重绘一次
    requestAnimationFrame(() => {
      this.draw();
    });

    // 清空画布
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

    // 遍历点集合 将点画在画布上
    for (let i = 0; i < this.points.length; i++) {
      const point1 = this.points[i];
      point1.draw();

      // 将点连成线
      for (let j = i + 1; j < this.points.length; j++) {
        const point2 = this.points[j];

        // 勾股定理求出2个点的直线距离
        const dis = Math.sqrt(Math.abs(point1.x - point2.x) ** 2 + Math.abs(point1.y - point2.y) ** 2);

        // 大于 “可连线的最宽距离” 时 不连线
        if (dis > this.config.maxDis) {
          continue;
        }

        // 将两个点连接起来
        this.ctx.beginPath();
        this.ctx.moveTo(point1.x, point1.y);
        this.ctx.lineTo(point2.x, point2.y);
        this.ctx.strokeStyle = `rgba(255,255,255,${1 - dis / this.config.maxDis})`; //  设置透明度 距离越远 颜色越浅
        // 设置整体线宽
        this.ctx.lineWidth = this.config.lineWidth * devicePixelRatio;
        this.ctx.stroke();
        this.ctx.closePath();
      }
    }
  }
}

// template
<div id="container"></div>

const graph = new Graph("#container", {
    bgColor: "#000",
    lineWidth: 1,
    pNumbers: 100,
    maxDis: 200,
    pointConfig: { color: "#fff", radius: 6 },
});
graph.draw();
转载自:https://juejin.cn/post/7233387064274845757
评论
请登录