HTML部分
- 基本结构包括
<html>
,<head>
, 和<body>
标签。 <title>
标签设置了页面标题为“优化版3D粒子星系”。<style>
块定义了一些基本样式:body
:无边距,隐藏滚动条,黑色背景,禁用触摸动作以提高性能。canvas
:设置鼠标指针为移动图标(暗示用户可以拖动)。
JavaScript部分
配置与类定义
-
配置对象 (
config
):- 定义了粒子数量、引力强度、连线范围等参数,用于调整粒子系统的行为。
-
Vector3 类:
- 使用
Float32Array
存储三维向量数据,提供对x, y, z分量的访问方法,有助于提升计算性能。
- 使用
-
ParticleSystem 类:
- 构造函数:初始化画布、上下文、粒子数组和其他必要属性。
- init 方法:初始化粒子系统,包括粒子生成、事件监听器绑定和动画启动。
- resize 方法:处理窗口大小变化时的重绘逻辑,使用防抖技术避免频繁重绘。
- draw 方法:核心渲染逻辑,利用离屏Canvas进行绘制以提高性能,并对每个粒子进行位置更新和投影计算。
- animate 方法:动画循环,调用
draw
方法并请求下一帧动画。 - destroy 方法:清理资源,取消动画帧请求并移除事件监听器。
关键特性与优化点
-
性能优化:
- 减少粒子数量 (
PARTICLE_COUNT: 150
) 和降低引力强度 (GRAVITY: 0.3
) 以减少计算负荷。 - 使用
Float32Array
代替普通数组来存储数值,提高数值计算效率。 - 禁用透明通道 (
alpha: false
) 提高绘图性能。 - 利用离屏Canvas (
bufferCanvas
) 进行绘制,减少主Canvas的重绘次数。 - 使用防抖处理窗口大小变化 (
resize
方法),防止频繁触发重绘。
- 减少粒子数量 (
-
用户体验优化:
- 支持鼠标和触摸设备的交互,使粒子系统在不同设备上都能良好工作。
- 页面隐藏时自动暂停动画 (
visibilitychange
事件监听),节省资源。
-
视觉效果:
- 动态计算每个粒子的位置、角度和距离,模拟出3D旋转效果。
- 根据时间动态调整粒子颜色 (
color: hsl(${Math.random()*360}, 70%, 50%)
),增加视觉层次感。 - 实现粒子的缩放和投影效果,增强立体感。
<!DOCTYPE html>
<html>
<head>
<title>优化版3D粒子星系</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
touch-action: none;
}
canvas {
cursor: move;
/* 移除滤镜提升性能 */
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// 性能优化配置
const config = {
PARTICLE_COUNT: 150, // 减少粒子数量
GRAVITY: 0.3, // 降低引力强度
LINE_THRESHOLD: 80, // 缩小连线范围
ZOOM_SPEED: 0.0005,
ROTATION_SPEED: 0.0001,
COLOR_CYCLE: 0.0002
};
// 使用Float32Array提升计算性能
class Vector3 {
constructor() {
this.data = new Float32Array(3);
}
set x(v) { this.data[0] = v; }
set y(v) { this.data[1] = v; }
set z(v) { this.data[2] = v; }
get x() { return this.data[0]; }
get y() { return this.data[1]; }
get z() { return this.data[2]; }
}
class ParticleSystem {
constructor() {
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d', { alpha: false }); // 关闭透明通道
this.particles = [];
this.core = new Vector3();
this.mouse = new Vector3();
this.animationFrame = null;
this.init();
}
init() {
// 初始化粒子
for(let i=0; i<config.PARTICLE_COUNT; i++) {
this.particles.push({
pos: new Vector3(),
vel: new Vector3(),
angle: Math.PI * 2 * Math.random(),
distance: 100 + Math.random() * 400,
mass: 0.5 + Math.random(),
color: `hsl(${Math.random()*360}, 70%, 50%)`
});
}
// 节流处理
this.resize();
window.addEventListener('resize', () => this.resize());
this.canvas.addEventListener('mousemove', e => this.handleMove(e));
this.canvas.addEventListener('touchmove', e => this.handleTouch(e), {passive: true});
this.animate();
}
// 使用防抖优化resize
resize = () => {
cancelAnimationFrame(this.animationFrame);
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.core.x = this.canvas.width/2;
this.core.y = this.canvas.height/2;
this.animationFrame = requestAnimationFrame(this.animate);
}
draw() {
// 使用离屏Canvas提升渲染性能
if(!this.bufferCanvas) {
this.bufferCanvas = document.createElement('canvas');
this.bufferCtx = this.bufferCanvas.getContext('2d');
this.bufferCanvas.width = this.canvas.width;
this.bufferCanvas.height = this.canvas.height;
}
const ctx = this.bufferCtx;
ctx.fillStyle = 'rgb(0,0,0)';
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
const time = performance.now();
this.particles.forEach(particle => {
// 优化后的运动计算
particle.angle += config.ROTATION_SPEED / particle.mass;
const x = Math.cos(particle.angle) * particle.distance;
const z = Math.sin(particle.angle) * particle.distance;
const y = Math.sin(time*0.001 + particle.angle) * 30;
// 投影计算
const scale = 150 / (150 + z);
const px = x * scale + this.core.x;
const py = y * scale + this.core.y;
// 绘制优化
ctx.beginPath();
ctx.arc(px, py, 1.5, 0, Math.PI*2);
ctx.fillStyle = particle.color;
ctx.fill();
});
// 单次绘制到主Canvas
this.ctx.drawImage(this.bufferCanvas, 0, 0);
}
animate = () => {
this.draw();
this.animationFrame = requestAnimationFrame(this.animate);
}
// 添加销毁方法
destroy() {
cancelAnimationFrame(this.animationFrame);
window.removeEventListener('resize', this.resize);
}
}
const system = new ParticleSystem();
// 页面隐藏时自动暂停
document.addEventListener('visibilitychange', () => {
if(document.hidden) system.destroy();
});
</script>
</body>
</html>