js实现歌词同步
实现歌词同步的基本思路
歌词同步通常需要将歌词文本与音频时间轴对齐,根据当前播放时间动态高亮显示对应歌词。核心步骤包括解析歌词格式、监听音频时间更新、匹配并渲染当前歌词。
解析歌词格式
常见歌词格式为LRC,每行包含时间标签和歌词文本。例如:
[00:00.00]歌曲名
[00:01.00]第一句歌词
[00:03.50]第二句歌词
解析时需要将时间转换为秒数:
function parseLRC(lrcText) {
const lines = lrcText.split('\n');
const result = [];
const timeRegex = /\[(\d{2}):(\d{2})\.(\d{2})\]/;
lines.forEach(line => {
const matches = timeRegex.exec(line);
if (matches) {
const min = parseInt(matches[1]);
const sec = parseInt(matches[2]);
const ms = parseInt(matches[3]) / 100;
const time = min * 60 + sec + ms;
const text = line.replace(timeRegex, '').trim();
result.push({ time, text });
}
});
return result;
}
监听音频播放进度
通过<audio>元素或Web Audio API获取当前播放时间:
const audio = document.querySelector('audio');
const lyrics = parseLRC(lrcText);
audio.addEventListener('timeupdate', () => {
const currentTime = audio.currentTime;
updateLyrics(currentTime);
});
匹配并高亮当前歌词
根据当前时间查找应显示的歌词行:
function updateLyrics(currentTime) {
let activeIndex = 0;
for (let i = 0; i < lyrics.length; i++) {
if (lyrics[i].time <= currentTime) {
activeIndex = i;
} else {
break;
}
}
// 渲染逻辑
const lyricsContainer = document.getElementById('lyrics');
lyricsContainer.innerHTML = lyrics.map((line, index) =>
`<div class="${index === activeIndex ? 'active' : ''}">${line.text}</div>`
).join('');
}
滚动定位优化
确保当前歌词始终在可视区域:
const activeLine = document.querySelector('.active');
if (activeLine) {
activeLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
样式增强
通过CSS实现视觉反馈:
#lyrics {
height: 300px;
overflow-y: auto;
}
#lyrics div {
padding: 8px;
transition: all 0.3s;
}
#lyrics div.active {
color: #1db954;
font-weight: bold;
transform: scale(1.05);
}
处理空白歌词行
过滤无意义的空行:
result = result.filter(item => item.text);
性能优化建议
对于长音频,避免频繁DOM操作:
let lastActiveIndex = -1;
function updateLyrics(currentTime) {
let activeIndex = -1;
// ...查找逻辑...
if (activeIndex !== lastActiveIndex) {
renderLyrics(activeIndex);
lastActiveIndex = activeIndex;
}
}
扩展功能
支持双语歌词显示:
function parseBilingualLRC(lrcText1, lrcText2) {
const lyrics1 = parseLRC(lrcText1);
const lyrics2 = parseLRC(lrcText2);
return lyrics1.map((item, index) => ({
...item,
text2: lyrics2[index]?.text || ''
}));
}
错误处理
增加格式校验:
if (!timeRegex.test(line)) {
console.warn(`Invalid LRC format: ${line}`);
continue;
}
完整示例
HTML结构:
<audio src="song.mp3" controls></audio>
<div id="lyrics-container">
<div id="lyrics"></div>
</div>
整合后的JavaScript:
document.addEventListener('DOMContentLoaded', () => {
const audio = document.querySelector('audio');
const lyricsData = parseLRC(lrcText);
let lastActiveIndex = -1;
audio.addEventListener('timeupdate', () => {
const currentTime = audio.currentTime;
const activeIndex = findActiveLyric(currentTime);
if (activeIndex !== lastActiveIndex) {
renderLyrics(activeIndex);
lastActiveIndex = activeIndex;
}
});
function findActiveLyric(currentTime) {
for (let i = lyricsData.length - 1; i >= 0; i--) {
if (lyricsData[i].time <= currentTime) {
return i;
}
}
return 0;
}
function renderLyrics(activeIndex) {
const lyricsElement = document.getElementById('lyrics');
lyricsElement.innerHTML = lyricsData.map((line, index) => `
<div class="${index === activeIndex ? 'active' : ''}">
${line.text}
</div>
`).join('');
const activeLine = document.querySelector('.active');
if (activeLine) {
activeLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
});






