Based on my curiosity about Canvas, plus having nothing to do on a typhoon day, I saw a YouTube video about Canvas particles, so I started this research.
I'll expand on the following points:
Synchronized lyrics display + lyrics embedded in canvas
Canvas is a powerful graphics drawing API provided by HTML5 that allows developers to dynamically draw graphics and animations on web pages. Through JavaScript, you can draw 2D or 3D graphics on Canvas to achieve various visual effects.
To achieve synchronization between lyrics and audio, lyrics are stored with corresponding timestamps in an array. Each lyrics object contains time (in seconds) and text.
const lyrics = [
{ time: 0, text: '(Sad Bar city pop)' },
{ time: 21, text: 'Cold light, mournful songs, drinkers have no heart' },
// More lyrics...
];
By listening to the audio's timeupdate event, we can continuously check the current time during playback and display corresponding lyrics.
audio.addEventListener('timeupdate', updateLyrics);
function updateLyrics() {
const currentTime = audio.currentTime;
for (let i = 0; i < lyrics.length; i++) {
if (lyrics[i].time <= currentTime) {
currentLineIndex = i;
}
}
if (currentLineIndex !== previousLineIndex) {
createParticles(lyrics[currentLineIndex].text);
previousLineIndex = currentLineIndex;
}
}
This ensures that when the current playback time reaches a certain line's time marker, it dynamically updates to display new lyrics and creates corresponding particle effects in the canvas.
Particle effects typically consist of many small particles that move according to specific physical rules, creating dynamic visual effects. In this case, particles represent each character or word of the lyrics and perform animated displays as the music plays.
Use requestAnimationFrame to create a smooth animation loop that continuously updates and draws particles.
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
}
requestAnimationFrame(animate); // Usually calls the callback function at about 60 frames per second (FPS), depending on browser and device performance
}
To convert lyrics into particles, we need to render text to a temporary Canvas, then extract pixel data to generate particles.
function createParticles(text) {
particles = []; // Clear existing particle array
// Create a temporary Canvas element for rendering text and extracting pixels
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = canvas.width; // Set temporary Canvas width same as main Canvas
tempCanvas.height = canvas.height; // Set temporary Canvas height same as main Canvas
// Set font size, dynamically adjusted based on Canvas width
fontSize = canvas.width / 15;
tempCtx.font = `${fontSize}px 'Microsoft YaHei', sans-serif`; // Set font style
tempCtx.textAlign = 'center'; // Center text horizontally
tempCtx.textBaseline = 'middle'; // Center text vertically
const maxWidth = canvas.width * 0.8; // Set maximum text width to 80% of Canvas width
const lineHeight = fontSize * 1.2; // Set line height to 1.2 times font size
const lines = wrapText(tempCtx, text, maxWidth); // Perform automatic text wrapping
textHeight = lines.length * lineHeight; // Calculate total text height
tempCtx.fillStyle = '#FFFFFF'; // Set fill color to white
// Draw each line of text on temporary Canvas
for (let i = 0; i < lines.length; i++) {
tempCtx.fillText(
lines[i],
tempCanvas.width / 2, // x coordinate set to Canvas center
tempCanvas.height / 2 - textHeight / 2 + i * lineHeight + lineHeight / 2 // y coordinate calculation, ensuring text is vertically centered
);
}
textWidth = maxWidth; // Set text width to maximum width
// Get image data from temporary Canvas
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
// Iterate through each pixel of image data, checking every 2 pixels
for (let y = 0; y < tempCanvas.height; y += 2) {
for (let x = 0; x < tempCanvas.width; x += 2) {
const index = (y * tempCanvas.width + x) * 4; // Calculate pixel index in data
const alpha = imageData.data[index + 3]; // Get pixel's transparency value
if (alpha > 128) {
// If pixel is opaque (transparency greater than 128)
const color = '#FFFFFF'; // Set particle color to white
// Create new particle, position adjusted based on Canvas center
const particle = new Particle(
x - canvas.width / 2 + textWidth / 2, // Adjust x coordinate to center particle
y - canvas.height / 2 + textHeight / 2, // Adjust y coordinate to center particle
color // Particle color
);
particles.push(particle); // Add particle to particle array
}
}
}
}
Ensures lyrics wrap neatly on Canvas, maintaining readability.
function wrapText(context, text, maxWidth) {
const words = text.split(' '); // Split text by spaces into word array
const lines = []; // Initialize empty array to store lines after wrapping
let currentLine = words[0]; // Set first word as start of current line
// Iterate through all words starting from the second word
for (let i = 1; i < words.length; i++) {
const word = words[i]; // Get current word
// Calculate total width after adding current word to current line
const width = context.measureText(`${currentLine} ${word}`).width;
// If total width is less than maximum width, add word to current line
if (width < maxWidth) {
currentLine += ` ${word}`;
} else {
// Otherwise, push current line to lines array and start a new line
lines.push(currentLine);
currentLine = word;
}
}
// Add last line to lines array
lines.push(currentLine);
return lines; // Return wrapped text array
}
Particle Count Control: Balance performance and visual effects by adjusting particle spacing and size.
Efficient Rendering: Use requestAnimationFrame to ensure smooth animation loops.
Resource Management: When handling large amounts of text or high particle counts, manage memory and processing power well to avoid performance bottlenecks.
Through this Canvas particle practice, I implemented an interactive particle effect player with synchronized lyrics. I accelerated the development experience through ChatGPT-4o-mini and Perplexity, and combined functionalities through my own knowledge of JavaScript.
Overall, it was quite interesting! 🌟
The next article will focus on drawing fluid particle effects based on the improvement points!