<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas粒子图形变形动画特效</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #121212;
overflow: hidden;
font-family: 'Arial', sans-serif;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.controls {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 100;
color: white;
}
.credit {
position: absolute;
bottom: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
z-index: 100;
}
.credit a {
color: #4CAF50;
text-decoration: none;
transition: color 0.3s;
}
.credit a:hover {
color: #FFD700;
}
button {
background: rgba(76, 175, 80, 0.7);
color: white;
border: none;
padding: 8px 15px;
margin: 5px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
button:hover {
background: rgba(76, 175, 80, 0.9);
}
label {
display: block;
margin-top: 10px;
color: white;
}
input[type="range"] {
width: 200px;
margin-top: 5px;
}
select {
padding: 5px;
margin-top: 5px;
width: 200px;
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
</style>
</head>
<body>
<canvas id="particleCanvas"></canvas>
<div class="controls">
<button id="resetBtn">重置动画</button>
<button id="changeColorBtn">改变颜色</button>
<button id="morphBtn">变形</button>
<label for="shapeSelect">选择形状:</label>
<select id="shapeSelect">
<option value="circle">圆形</option>
<option value="square">方形</option>
<option value="triangle">三角形</option>
<option value="star">星形</option>
<option value="heart">心形</option>
<option value="text">文字</option>
</select>
<label for="particleCount">粒子数量: <span id="particleCountValue">1000</span></label>
<input type="range" id="particleCount" min="100" max="5000" value="1000" step="100">
<label for="speedControl">变形速度: <span id="speedValue">50</span></label>
<input type="range" id="speedControl" min="10" max="100" value="50">
</div>
<div class="credit">
Canvas粒子图形变形动画特效 | 访问 <a href="http://www.0ww.net" target="_blank">0ww.net</a> 获取更多资源
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
// 设置画布大小为窗口大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 粒子系统参数
let particles = [];
let particleCount = 1000;
let speed = 50;
let colorHue = 200;
let animationId;
let currentShape = 'circle';
let targetShape = 'circle';
let morphProgress = 0;
let morphing = false;
// 形状定义
const shapes = {
circle: {
getPoints: (count, width, height) => {
const points = [];
const centerX = width / 2;
const centerY = height / 2;
const radius = Math.min(width, height) * 0.4;
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2;
points.push({
x: centerX + Math.cos(angle) * radius,
y: centerY + Math.sin(angle) * radius
});
}
return points;
}
},
square: {
getPoints: (count, width, height) => {
const points = [];
const centerX = width / 2;
const centerY = height / 2;
const size = Math.min(width, height) * 0.4;
const sideCount = count / 4;
// 四条边
for (let i = 0; i < sideCount; i++) {
// 上边
points.push({
x: centerX - size + (i / sideCount) * size * 2,
y: centerY - size
});
// 右边
points.push({
x: centerX + size,
y: centerY - size + (i / sideCount) * size * 2
});
// 下边
points.push({
x: centerX + size - (i / sideCount) * size * 2,
y: centerY + size
});
// 左边
points.push({
x: centerX - size,
y: centerY + size - (i / sideCount) * size * 2
});
}
return points.slice(0, count); // 确保数量正确
}
},
triangle: {
getPoints: (count, width, height) => {
const points = [];
const centerX = width / 2;
const centerY = height / 2;
const size = Math.min(width, height) * 0.4;
const sideCount = count / 3;
// 计算三角形顶点
const top = { x: centerX, y: centerY - size };
const right = { x: centerX + size * Math.cos(Math.PI / 6), y: centerY + size * Math.sin(Math.PI / 6) };
const left = { x: centerX - size * Math.cos(Math.PI / 6), y: centerY + size * Math.sin(Math.PI / 6) };
// 三条边
for (let i = 0; i < sideCount; i++) {
// 上到右
points.push({
x: top.x + (right.x - top.x) * (i / sideCount),
y: top.y + (right.y - top.y) * (i / sideCount)
});
// 右到左
points.push({
x: right.x + (left.x - right.x) * (i / sideCount),
y: right.y + (left.y - right.y) * (i / sideCount)
});
// 左到上
points.push({
x: left.x + (top.x - left.x) * (i / sideCount),
y: left.y + (top.y - left.y) * (i / sideCount)
});
}
return points.slice(0, count); // 确保数量正确
}
},
star: {
getPoints: (count, width, height) => {
const points = [];
const centerX = width / 2;
const centerY = height / 2;
const outerRadius = Math.min(width, height) * 0.4;
const innerRadius = outerRadius * 0.5;
const spikes = 5;
const step = Math.PI / spikes;
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2;
const radius = i % 2 === 0 ? outerRadius : innerRadius;
points.push({
x: centerX + Math.cos(angle * spikes) * radius,
y: centerY + Math.sin(angle * spikes) * radius
});
}
return points;
}
},
heart: {
getPoints: (count, width, height) => {
const points = [];
const centerX = width / 2;
const centerY = height / 2;
const size = Math.min(width, height) * 0.3;
for (let i = 0; i < count; i++) {
const t = (i / count) * Math.PI * 2;
const x = 16 * Math.pow(Math.sin(t), 3);
const y = -(13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t));
points.push({
x: centerX + x * size / 16,
y: centerY + y * size / 16
});
}
return points;
}
},
text: {
getPoints: (count, width, height) => {
// 文字形状需要更复杂的实现,这里简化为圆形
return shapes.circle.getPoints(count, width, height);
}
}
};
// 粒子类
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 2 + 1;
this.color = `hsl(${colorHue + Math.random() * 60}, 100%, 50%)`;
this.baseX = x;
this.baseY = y;
this.density = Math.random() * 30 + 1;
this.targetX = x;
this.targetY = y;
}
update() {
// 计算到目标位置的距离
const dx = this.targetX - this.x;
const dy = this.targetY - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// 根据速度参数移动粒子
const speedFactor = speed / 50;
if (distance > 0.5) {
this.x += dx * 0.05 * speedFactor;
this.y += dy * 0.05 * speedFactor;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
// 初始化粒子
function initParticles() {
particles = [];
const shapePoints = shapes[currentShape].getPoints(particleCount, canvas.width, canvas.height);
for (let i = 0; i < particleCount; i++) {
const point = shapePoints[i % shapePoints.length];
particles.push(new Particle(
Math.random() * canvas.width,
Math.random() * canvas.height
));
particles[i].targetX = point.x;
particles[i].targetY = point.y;
}
}
// 变形到新形状
function morphToShape(shape) {
if (morphing || currentShape === shape) return;
targetShape = shape;
morphing = true;
morphProgress = 0;
const shapePoints = shapes[shape].getPoints(particleCount, canvas.width, canvas.height);
for (let i = 0; i < particles.length; i++) {
const point = shapePoints[i % shapePoints.length];
particles[i].targetX = point.x;
particles[i].targetY = point.y;
}
currentShape = shape;
morphing = false;
}
// 动画循环
function animate() {
// 清除画布
ctx.fillStyle = 'rgba(18, 18, 18, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新和绘制粒子
particles.forEach(particle => {
particle.update();
particle.draw();
});
animationId = requestAnimationFrame(animate);
}
// 初始化并开始动画
initParticles();
animate();
// 窗口大小调整时重置画布
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
initParticles();
});
// 控制按钮事件
document.getElementById('resetBtn').addEventListener('click', function() {
cancelAnimationFrame(animationId);
initParticles();
animate();
});
document.getElementById('changeColorBtn').addEventListener('click', function() {
colorHue = Math.random() * 360;
particles.forEach(particle => {
particle.color = `hsl(${colorHue + Math.random() * 60}, 100%, 50%)`;
});
});
document.getElementById('morphBtn').addEventListener('click', function() {
const shape = document.getElementById('shapeSelect').value;
morphToShape(shape);
});
document.getElementById('shapeSelect').addEventListener('change', function() {
const shape = this.value;
morphToShape(shape);
});
document.getElementById('particleCount').addEventListener('input', function() {
particleCount = parseInt(this.value);
document.getElementById('particleCountValue').textContent = particleCount;
initParticles();
});
document.getElementById('speedControl').addEventListener('input', function() {
speed = parseInt(this.value);
document.getElementById('speedValue').textContent = speed;
});
});
</script>
</body>
</html>