前言
网上冲浪时偶然看到一个粒子汇聚效果的页面,视觉效果非常炫酷,于是决定尝试自己实现一个类似的功能。通过这一实现,不仅包含了刷新页面时自动启动的倒计时功能,还能根据用户输入的内容,让粒子动态地汇聚成对应的文字。整个过程利用了 HTML5 canvas 技术,让粒子动画生动流畅。接下来,我将从专业角度出发,带大家详细介绍这个粒子动画的实现步骤。
核心功能
1. 倒计时粒子动画:页面加载时,粒子自动汇聚成倒计时数字,10秒倒计时结束后,显示 “Time Up”。
2. 输入框动态文本渲染:用户可以通过页面底部的输入框输入任意文本,点击确认后,粒子会重新汇聚成该文本。
3. 粒子动画效果:粒子不仅可以汇聚成指定文本,还会在空闲状态下进行随机漂浮,保持页面的动态感。
效果预览
在实现之前,先来看一下这个效果的预览。刷新页面时,你将看到粒子从10开始倒计时,随着时间减少,粒子逐渐形成对应的数字。当倒计时结束后,粒子会汇聚成 “倒计时”。在底部,你可以输入任意字符或单词,点击确认按钮后,粒子会立刻汇聚成你输入的文本。
实现步骤
1. HTML 结构
首先,我们需要创建一个简单的 HTML 页面,其中包含用于绘制粒子动画的 canvas 和用户输入的文本框、按钮。输入框和按钮放置在页面底部,方便用户输入文本:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>粒子汇聚</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #000;
overflow: hidden;
}
canvas {
display: block;
}
/* 输入框和按钮放在页面底部 */
.controls {
position: absolute;
bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
input[type="text"] {
width: 300px;
padding: 10px;
font-size: 18px;
border-radius: 5px;
border: 1px solid #fff;
background-color: #333;
color: #fff;
}
button {
margin-left: 10px;
padding: 10px 20px;
font-size: 18px;
color: #fff;
background-color: #28a745;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #218838;
}
</style>
</head>
<body>
<!-- 控制区域 -->
<div class="controls">
<input type="text" id="textInput" placeholder="请输入。。。">
<button id="renderButton">确认</button>
</div>
<!-- 粒子 canvas -->
<canvas id="particleCanvas"></canvas>
</body>
</html>
在这段代码中,我们定义了一个 canvas 元素用于绘制粒子效果,输入框和按钮放置在页面底部,确保它们在所有屏幕上都是可见的。
2. JavaScript 实现
在接下来的部分,我们将实现粒子的初始化、倒计时动画、输入框内容的动态渲染,以及粒子的漂浮效果。
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
const inputField = document.getElementById('textInput');
const renderButton = document.getElementById('renderButton');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particles = [];
const particleCount = 1000; // 粒子数量
const fontSize = 150; // 文字大小
let countdownTime = 10; // 倒计时从10秒开始
// 初始化粒子
function initParticles() {
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 3 + 1,
color: 'rgba(255, 255, 255, 0.3)', // 背景粒子淡化
targetX: 0,
targetY: 0,
velocityX: Math.random() * 2 - 1,
velocityY: Math.random() * 2 - 1,
isTargetParticle: false // 标记是否为目标粒子
});
}
}
// 设置粒子目标位置为指定的文字
function setParticleTarget(text) {
// 清除之前的文字绘制
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 设置文字样式
ctx.font = `${fontSize}px Arial`;
ctx.fillStyle = 'white'; // 白色文字
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 绘制文字,位置在画布中央
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
// 获取文字的像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
let count = 0;
for (let y = 0; y < canvas.height; y += 6) {
for (let x = 0; x < canvas.width; x += 6) {
const index = (y * canvas.width + x) * 4;
const alpha = imageData[index + 3];
// 如果该像素点是文字的一部分,则分配给粒子
if (alpha > 128 && count < particles.length) {
particles[count].targetX = x;
particles[count].targetY = y;
particles[count].color = 'rgba(255, 255, 255, 1)'; // 高亮目标粒子
particles[count].isTargetParticle = true; // 标记为目标粒子
count++;
}
}
}
// 将未参与组成文字的粒子设为无规则运动
for (let i = count; i < particles.length; i++) {
particles[i].isTargetParticle = false;
particles[i].color = 'rgba(255, 255, 255, 0.3)'; // 背景粒子
}
}
// 更新粒子位置
function updateParticles() {
particles.forEach(particle => {
if (particle.isTargetParticle) {
// 目标粒子逐渐移动到目标位置
particle.x += (particle.targetX - particle.x) * 0.05;
particle.y += (particle.targetY - particle.y) * 0.05;
} else {
// 非目标粒子进行无规则漂移
particle.x += particle.velocityX;
particle.y += particle.velocityY;
// 如果粒子移出画布则将其重置回到画布中
if (particle.x < 0 || particle.x > canvas.width) {
particle.x = Math.random() * canvas.width;
}
if (particle.y < 0 || particle.y > canvas.height) {
particle.y = Math.random() * canvas.height;
}
}
});
}
// 绘制粒子
function drawParticles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
});
}
// 动画循环
function animate() {
updateParticles();
drawParticles();
requestAnimationFrame(animate);
}
// 倒计时功能
function startCountdown() {
const interval = setInterval(() => {
if (countdownTime === 0) {
clearInterval(interval);
setParticleTarget('倒计时');
} else {
setParticleTarget(countdownTime.toString());
countdownTime--;
}
}, 1000);
}
// 启动粒子系统和倒计时
initParticles();
startCountdown();
animate();
// 当点击确认按钮时,更新粒子目标为输入框的值
renderButton.addEventListener('click', () => {
const inputText = inputField.value.trim(); // 获取输入框中的值
if (inputText) {
setParticleTarget(inputText); // 将粒子目标设置为输入的文字
}
});
解析代码
1. 初始化粒子
initParticles() 函数生成了一定数量的粒子,并将它们随机分布在整个 canvas 画布上。这些粒子有随机的大小、颜色和速度。
function initParticles() {
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 3 + 1,
color: 'rgba(255, 255, 255, 0.3)',
targetX: 0,
targetY: 0,
velocityX: Math.random() * 2 - 1,
velocityY: Math.random() * 2 - 1,
isTargetParticle: false
});
}
}
2. 处理用户输入和倒计时
通过 setParticleTarget() 函数,可以根据倒计时或用户输入的文本,改变粒子的目标位置。这使得粒子能以特定的轨迹逐渐聚集成目标形状。
function setParticleTarget(text) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = `${fontSize}px Arial`;
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
...
}
3. 更新粒子位置和动画循环
通过 updateParticles() 函数不断更新粒子的位置,并通过 animate() 函数实现动画循环。无论粒子是否属于目标形状,它们都会保持动画效果。
总结
通过这个粒子汇聚效果的实现,我进一步加深了对 HTML5 canvas 和 JavaScript 动画的理解。canvas 提供了极其灵活的绘图功能,尤其在处理动态效果时展现了强大的表现力。在本项目中,粒子从随机漂浮到精准汇聚成倒计时和用户输入的文字,整个过程让我更好地理解了如何通过简单的数学公式控制物体运动。
为了保证动画的流畅性,我使用了 requestAnimationFrame,并确保只有必要时才更新粒子的状态,这在处理大量粒子时有效提高了性能。通过增加用户输入交互,增强了页面的互动性和趣味性,同时也让我认识到如何在炫酷的效果与良好的用户体验之间找到平衡。