
歌词滚动是音乐播放器里最常见的小功能:音乐播一句,歌词高亮一句,当前行始终停在中间。原理其实不复杂——解析 LRC 歌词里的时间戳,跟着 audio 的 timeupdate 事件走就行。下面给一份纯 HTML + 原生 JS 的完整实现,复制就能用。
一、LRC 歌词格式
LRC 文件每一行的格式是 [分:秒.毫秒]歌词文本,例如:
[00:01.00]Lyrics Demo [00:03.50]第一句歌词 [00:07.20]第二句歌词 [00:11.80]第三句歌词
二、HTML 结构
一个 audio 播放器,加一个用来装歌词的容器:
<audio id="audio" src="song.mp3" controls></audio> <div id="lyric" class="lyric"> <ul id="lyric-list"></ul> </div>
三、CSS 样式
容器固定高度并隐藏溢出,歌词列表用 transition 做平滑滚动,当前行加 .active 高亮:
.lyric {
height: 300px;
overflow: hidden;
position: relative;
text-align: center;
}
#lyric-list {
list-style: none;
margin: 0;
padding: 0;
transition: transform .6s ease-out; /* 滚动动画 */
}
#lyric-list li {
height: 30px;
line-height: 30px;
color: #888;
transition: color .3s, font-size .3s;
}
#lyric-list li.active {
color: #1db954;
font-size: 18px;
}
四、JavaScript:解析 + 滚动同步
const audio = document.getElementById('audio');
const list = document.getElementById('lyric-list');
const LINE_HEIGHT = 30; // 与 CSS 中 li 的高度一致
const CONTAINER_H = 300; // 与 CSS 中 .lyric 高度一致
let lyrics = []; // [{ time: 秒, text: '...' }]
let curIndex = -1;
// 1. 解析 LRC 文本
function parseLrc(lrc) {
const lines = lrc.split('\n');
const result = [];
const reg = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/;
for (const line of lines) {
const m = line.match(reg);
if (!m) continue;
const time = (+m[1]) * 60 + (+m[2]) + (+m[3]) / (m[3].length === 3 ? 1000 : 100);
const text = line.replace(reg, '').trim();
if (text) result.push({ time, text });
}
return result.sort((a, b) => a.time - b.time);
}
// 2. 渲染歌词列表
function renderLrc() {
list.innerHTML = lyrics.map(l => `<li>${l.text}</li>`).join('');
}
// 3. 找到当前应高亮的行
function findIndex(time) {
for (let i = lyrics.length - 1; i >= 0; i--) {
if (time >= lyrics[i].time) return i;
}
return -1;
}
// 4. 跟随播放进度滚动
function update() {
const index = findIndex(audio.currentTime);
if (index === curIndex) return; // 行没变就不动 DOM
curIndex = index;
const items = list.children;
for (let i = 0; i < items.length; i++) {
items[i].classList.toggle('active', i === index);
}
// 让当前行停在容器正中间
const offset = index * LINE_HEIGHT - CONTAINER_H / 2 + LINE_HEIGHT / 2;
list.style.transform = `translateY(${-Math.max(offset, 0)}px)`;
}
audio.addEventListener('timeupdate', update);
// 5. 加载歌词(这里写死示例,实际可用 fetch 加载 .lrc 文件)
const lrcText = `
[00:01.00]Lyrics Demo
[00:03.50]第一句歌词
[00:07.20]第二句歌词
[00:11.80]第三句歌词
`;
lyrics = parseLrc(lrcText);
renderLrc();
实现要点
- 解析时间戳:用正则把
[mm:ss.xx]拆出来,转成秒数,和歌词文本组成数组并按时间排序。 - 定位当前行:在
timeupdate里从后往前找,第一个时间小于等于当前播放时间的行就是当前行。 - 居中滚动:给歌词容器一个固定高度,通过
transform: translateY()把整个列表往上推,让高亮行停在中间;用 CSStransition让滚动平滑过渡。 - 性能:只在"当前行索引发生变化"时才操作 DOM——
timeupdate一秒会触发好几次,每次都重排会很浪费。
把示例里写死的 lrcText 换成用 fetch 加载的真实 .lrc 文件,再配上自己的歌曲,一个能跟唱的歌词滚动效果就成了。
—— 别看了 · 2026
搞