😉 你好呀,我是爱编程的Sherry,很高兴在这里遇见你!我是一名拥有十多年开发经验的前端工程师。这一路走来,面对困难时也曾感到迷茫,凭借不懈的努力和坚持,重新找到了前进的方向。我的人生格言是——认准方向,坚持不懈,你终将迎来辉煌!欢迎关注我😁,我将在这里分享工作中积累的经验和心得,希望我的分享能够给你带来帮助。
效果展示
实现逻辑
实现一个动态画布效果,通常指的是在HTML5的<canvas>
元素上绘制随时间变化的图形或动画。JavaScript是控制这些动画的关键。下面是创建一个简单动态画布效果的基本思路:
1. 设置Canvas
首先,在HTML文档中引入一个标签,它将作为绘图区域。通过JavaScript获取这个元素,并初始化其上下文对象,这是所有绘图操作的基础。
<canvas></canvas>
<script>
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
</script>
2. 设置Canvas样式
为了让画布充满整个视窗并且有一个明显的背景色,我们为添加CSS样式
canvas {
position: absolute;
left: 0;
top: 0;
z-index: 0;
background-color: steelblue;
}
3. 初始化画布尺寸
确保画布大小适应当前窗口,并在窗口调整大小时重新设置。
// 初始化
function init() {
cvs.width = window.innerWidth;
cvs.height = window.innerHeight;
}
init();
4. 创建动画元素
确定你想要在画布上显示什么类型的动画元素。这可以是简单的几何形状,比如圆形、矩形,也可以是更复杂的图案或对象。对于每个元素,你需要定义其属性,如位置、速度、颜色等。
class Point {
constructor() {
this.r = 5; // 半径
this.x = getRandom(0, cvs.width - this.r / 2); // x坐标
this.y = getRandom(0, cvs.height - this.r / 2); // y坐标
this.xSpeed = getRandom(-80, 80); // x坐标移动速度
this.ySpeed = getRandom(-80, 80); // y坐标移动速度
this.lastDrawTime = null; // 上一次画的时间
}
draw() {
// 更新坐标
if (this.lastDrawTime) {
// 计算新的坐标
const duration = (Date.now() - this.lastDrawTime) / 1000;
const xDis = this.xSpeed * duration;
const yDis = this.ySpeed * duration;
let x = this.x + xDis;
let y = this.y + yDis;
// 限制移动范围
if (x > cvs.width - this.r / 2) {
x = cvs.width - this.r / 2;
this.xSpeed = -this.xSpeed;
} else if (x < this.r / 2) {
x = this.r / 2;
this.xSpeed = -this.xSpeed;
}
if (y > cvs.height - this.r / 2) {
y = cvs.height - this.r / 2;
this.ySpeed = -this.ySpeed;
} else if (y < this.r / 2) {
y = this.r / 2;
this.ySpeed = -this.ySpeed;
}
// 更新坐标
this.x = x;
this.y = y;
}
// 画小球
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = 'rgb(200,200,200)'; // 小球颜色
ctx.fill();
this.lastDrawTime = Date.now(); // 记录这次绘画时间
}
}
class Graph {
// pointNumber: 小球初始数量
// maxDis: 小球之间最大的距离
constructor(pointNumber = 50, maxDis = 350) {
this.points = new Array(pointNumber).fill(0).map(() => new Point());
this.maxDis = maxDis;
}
// 绘画
draw() {
// 在浏览器下一次重绘之前调用指定的回调函数,以确保动画的流畅性和性能
requestAnimationFrame(() => {
this.draw();
})
ctx.clearRect(0, 0, cvs.width, cvs.height);
// 遍历所有小球
for (let i = 0; i < this.points.length; i++) {
// 把该小球画出来
const p1 = this.points[i];
p1.draw();
// 接下来遍历其他小球,根据小球之间距离画出直线
for (let j = i + 1; j < this.points.length; j++) {
const p2 = this.points[j];
const d = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) // 两点距离公式
// 如果两点距离超出最大距离,则不画线
if (d > this.maxDis) {
continue;
}
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.strokeStyle = `rgba(200,200,200, ${1 - d / this.maxDis})`; // 根据小球之间距离决定颜色深浅
ctx.stroke();
}
}
}
addPoint(e) {
const p = new Point();
p.x = e.clientX;
p.y = e.clientY;
this.points[this.points.length++] = p;
p.draw();
}
}
定义一个Point类来表示每个动画中的点。每个点都有自己的位置、速度以及边界反弹逻辑。同时,为了简化代码结构,还定义了一个Graph类来管理这些点及其之间的连接。
5. 随机数生成器
function getRandom(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
6. 增加交互性
添加事件监听器来响应用户的动作,例如鼠标移动、点击等,以改变动画元素的行为或者状态。
const g = new Graph();
g.draw();
cvs.addEventListener('click', e => {
// 处理逻辑
g.addPoint(e);
});
7. 完整代码
<!DOCTYPE html>
<html lang="utf-8">
<head>
<title>利用canvas画布实现动态背景效果</title>
<style>
canvas {
position: absolute;
left: 0;
top: 0;
z-index: 0;
background-color: steelblue;
}
</style>
</head>
<body>
<canvas></canvas>
<script>
const cvs = document.querySelector('canvas');
const ctx = cvs.getContext('2d');
// 初始化
function init() {
cvs.width = window.innerWidth;
cvs.height = window.innerHeight;
}
init();
function getRandom(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
class Point {
constructor() {
this.r = 5; // 半径
this.x = getRandom(0, cvs.width - this.r / 2); // x坐标
this.y = getRandom(0, cvs.height - this.r / 2); // y坐标
this.xSpeed = getRandom(-80, 80); // x坐标移动速度
this.ySpeed = getRandom(-80, 80); // y坐标移动速度
this.lastDrawTime = null; // 上一次画的时间
}
draw() {
// 更新坐标
if (this.lastDrawTime) {
// 计算新的坐标
const duration = (Date.now() - this.lastDrawTime) / 1000;
const xDis = this.xSpeed * duration;
const yDis = this.ySpeed * duration;
let x = this.x + xDis;
let y = this.y + yDis;
// 限制移动范围
if (x > cvs.width - this.r / 2) {
x = cvs.width - this.r / 2;
this.xSpeed = -this.xSpeed;
} else if (x < this.r / 2) {
x = this.r / 2;
this.xSpeed = -this.xSpeed;
}
if (y > cvs.height - this.r / 2) {
y = cvs.height - this.r / 2;
this.ySpeed = -this.ySpeed;
} else if (y < this.r / 2) {
y = this.r / 2;
this.ySpeed = -this.ySpeed;
}
// 更新坐标
this.x = x;
this.y = y;
}
// 画小球
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = 'rgb(200,200,200)'; // 小球颜色
ctx.fill();
this.lastDrawTime = Date.now(); // 记录这次绘画时间
}
}
class Graph {
// pointNumber: 小球初始数量
// maxDis: 小球之间最大的距离
constructor(pointNumber = 50, maxDis = 350) {
this.points = new Array(pointNumber).fill(0).map(() => new Point());
this.maxDis = maxDis;
}
// 绘画
draw() {
// 在浏览器下一次重绘之前调用指定的回调函数,以确保动画的流畅性和性能
requestAnimationFrame(() => {
this.draw();
})
ctx.clearRect(0, 0, cvs.width, cvs.height);
// 遍历所有小球
for (let i = 0; i < this.points.length; i++) {
// 把该小球画出来
const p1 = this.points[i];
p1.draw();
// 接下来遍历其他小球,根据小球之间距离画出直线
for (let j = i + 1; j < this.points.length; j++) {
const p2 = this.points[j];
const d = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) // 两点距离公式
// 如果两点距离超出最大距离,则不画线
if (d > this.maxDis) {
continue;
}
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.strokeStyle = `rgba(200,200,200, ${1 - d / this.maxDis})`; // 根据小球之间距离决定颜色深浅
ctx.stroke();
}
}
}
addPoint(e) {
const p = new Point();
p.x = e.clientX;
p.y = e.clientY;
this.points[this.points.length++] = p;
p.draw();
}
}
const g = new Graph();
g.draw();
cvs.addEventListener('click', e => {
// 处理逻辑
g.addPoint(e);
});
</script>
</body>
</html>
总结
将上面代码保存为.html文件,在浏览器打开即可查看效果。