Vấn đề là nhiều bản ngoài kia làm quá tay: particle quá nhiều, canvas nặng, CPU lên cao, nhìn thì vui nhưng không dùng được trong sản phẩm thật.
Bài này làm lại theo hướng khác: particle gọn, logic rõ ràng, chỉ dùng canvas cho đúng mục đích, hover hoặc click là thấy hiệu ứng ngay.
Không thư viện. Không shader. Không màu mè dư thừa.
Tư duy đúng khi làm Text Particle
Bản chất của hiệu ứng này gồm 2 bước:
- Vẽ chữ lên canvas
- Lấy dữ liệu pixel để tạo particle
Particle không cần nhiều. Quan trọng là:
- Vị trí chính xác
- Chuyển động mượt
- Có trạng thái quay về
Nếu làm đúng, canvas sẽ chỉ vẽ lại những gì cần thiết.
HTML: Một canvas duy nhất
<canvas id="particleText" width="600" height="200"></canvas>
Không cần thêm div hay layer phụ.
JavaScript: Vẽ chữ và tạo particle
const canvas = document.getElementById('particleText');
const ctx = canvas.getContext('2d');
const text = 'INIT HTML';
const particles = [];
const gap = 4;
ctx.fillStyle = '#fff';
ctx.font = 'bold 64px sans-serif';
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);
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let y = 0; y < canvas.height; y += gap) {
for (let x = 0; x < canvas.width; x += gap) {
const index = (y * canvas.width + x) * 4;
if (imageData.data[index + 3] > 128) {
particles.push({
x,
y,
baseX: x,
baseY: y,
vx: 0,
vy: 0
});
}
}
}
Mỗi particle lưu vị trí gốc để có thể quay về hình chữ ban đầu.
Vẽ và animate particle
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
p.vx *= 0.9;
p.vy *= 0.9;
p.x += p.vx;
p.y += p.vy;
const dx = p.baseX - p.x;
const dy = p.baseY - p.y;
p.x += dx * 0.05;
p.y += dy * 0.05;
ctx.fillStyle = '#fff';
ctx.fillRect(p.x, p.y, 2, 2);
});
requestAnimationFrame(animate);
}
animate();
Particle luôn có lực kéo nhẹ về vị trí ban đầu, nên chữ tự động “liền lại”.
Tương tác hover / click
canvas.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
particles.forEach(p => {
const dx = p.x - mx;
const dy = p.y - my;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 60) {
p.vx += dx * 0.05;
p.vy += dy * 0.05;
}
});
});
canvas.addEventListener('click', () => {
particles.forEach(p => {
p.vx += (Math.random() - 0.5) * 20;
p.vy += (Math.random() - 0.5) * 20;
});
});
Hover làm particle đẩy ra nhẹ. Click làm chữ “vỡ” mạnh hơn.
Vì sao hiệu ứng này chạy mượt
- Particle số lượng vừa đủ
- Dùng
requestAnimationFrame - Không tạo object mới trong loop
- Canvas chỉ vẽ pixel đơn giản
Canvas được dùng đúng chỗ, không lạm dụng.
Điều chỉnh cho dự án thật
- Giảm kích thước chữ trên mobile
- Tăng
gapđể giảm particle - Chỉ bật hover, tắt click nếu không cần
Hiệu ứng này nên dùng như điểm nhấn, không nên lặp lại nhiều lần trên cùng trang.
Lưu ý quan trọng
- Không dùng quá nhiều canvas cùng lúc
- Không đặt animation ở background liên tục
- Nên dừng animation khi canvas ra khỏi viewport
Kết luận
Text Particle Effect nhìn thì “ảo”, nhưng bản chất chỉ là xử lý pixel một cách có kiểm soát. Khi particle ít, chuyển động có lực kéo và canvas được dùng đúng vai trò, hiệu ứng sẽ vừa wow vừa đủ nhẹ để dùng trong sản phẩm thật.
Làm cho vui thì dễ. Làm để dùng lâu dài mới khó.
Bình luận