
目标形态
接入完成后,歌词在三个地方消费同一份数据:
- 侧栏播放器的歌词面板——逐行滚动、点击行跳转进度;
- 悬浮歌词条——屏幕下缘一条始终可见的当前歌词;
- 悬浮控件的胶囊歌词——收起形态下唱片旁边的单行歌词。
三处共用一个解析结果和一个「当前行」计算,播放逻辑零侵入。
LRC 解析器
LRC 本质是「时间标签 + 文本」的行式格式。解析器只做一件事:把 [mm:ss.xx] 抽出来换算成秒并排序。
export interface LrcLine { time: number; // 秒 text: string;}
export function parseLrc(raw: string): LrcLine[] { const lines: LrcLine[] = []; for (const line of raw.split(/\r?\n/)) { // 一行可能有多个时间标签(合并重复句),全部展开 const times = [...line.matchAll(/\[(\d+):(\d+)(?:\.(\d+))?\]/g)]; if (times.length === 0) continue; const text = line.replace(/\[(\d+):(\d+)(?:\.(\d+))?\]/g, "").trim(); for (const m of times) { const time = Number(m[1]) * 60 + Number(m[2]) + Number(`0.${m[3] ?? "0"}`); lines.push({ time, text }); } } return lines.sort((a, b) => a.time - b.time);}「当前行」用一次线性扫描就够了——歌词通常不超过百行,没必要二分:
export function currentLrcIndexAt(lines: LrcLine[], t: number): number { let index = -1; for (let i = 0; i < lines.length; i++) { if (lines[i].time <= t) index = i; else break; } return index;}歌词资源放在内容仓
歌词文件放在内容仓 public/assets/music/lrc/ 下,与曲目在 data/music.ts 里对接:
export const musicTracks = [ { id: "koufue", title: "口笛で愛は歌えない", artist: "Dazbee", cover: "/assets/music/cover/dazbee.webp", src: "/assets/music/audio/koufue.mp3", // 歌词:远端 .lrc 地址或内嵌 LRC 文本均可 lrc: "/assets/music/lrc/koufue.lrc", }, // ...];歌词内容的获取是懒加载的:面板或歌词条第一次需要显示时才 fetch,解析结果按曲目缓存——不听歌的访客不会为歌词付出任何请求。
Meting 平台的歌词透传
本地曲目之外,Meting(在线音乐平台聚合)模式同样支持歌词:接口返回的 lrc 字段原样透传给同一个解析器。这样无论曲目来自本地还是在线平台,下游三处消费逻辑完全一致。
// Meting 曲目解析时保留 lrc 字段if (metingLrc) { track.lrc = metingLrc; // 透传,下游统一懒加载解析}三处消费的取舍
| 消费位置 | 交互 | 备注 |
|---|---|---|
| 歌词面板 | 自动滚动跟随 + 点击跳转 + 手动滚动暂停 3s | 功能最全 |
| 悬浮歌词条 | 只显示当前行 | 常驻屏幕下缘,无滚动 |
| 胶囊歌词 | 只显示当前行,超长省略 | 嵌在悬浮控件里 |
自动滚动跟随的实现要点:歌词行变化时 scrollTo 目标行到容器垂直居中;用户手动滚动(wheel / touchstart)后暂停跟随 3 秒再恢复,避免「人在看上一句、视图被拽走」的对抗。
空歌词的降级链
无 lrc 配置 → 显示「暂无歌词」占位 → 相关按钮隐藏。歌词加载失败同理,占位文案区分「加载中」和「没有歌词」,不留空白区域。
收尾
整个歌词功能约 200 行:一个解析器、一个当前行计算、三处消费、一次懒加载。配置上 showLyrics: false 一行可以让全部歌词 UI 零 DOM;每首歌想不加歌词,不写 lrc 字段就行。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 1 天
部分内容可能已过时