初识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();
}
}
}
}
动画
动画实现起来非常简单,我们只需要一直移动点
的位置,并且通过Graph
的draw
方法重新画点
、连线
,
⚠注意点在移动的时候要处理超过边界的情况。
完整代码
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