小栋博客

  • 技术
  • 软件
  • 资讯
  • 源码
  • 快讯
  • 邻居
文章
文章用户商铺文档快讯圈子网址导航供求信息

{{userData.name}}已认证

文章

评论

关注

粉丝

¥{{role.user_data.money}}
{{role.user_data.credit}}
您已完成今天任务的
  • 私信列表所有往来私信

  • 财富管理余额、积分管理

  • 任务中心每日任务

    NEW
  • 成为会员购买付费会员

  • 认证服务申请认证

    NEW
  • 我的订单查看我的订单

  • 我的设置编辑个人资料

  • 小黑屋关进小黑屋的人

    NEW
  • 进入后台管理

HTML使用js实现的歌词滚动效果

  • 网站源码
  • 24年6月3日
  • 编辑
Mores站长

HTML使用js实现的歌词滚动效果

歌词滚动是音乐播放器里最常见的小功能:音乐播一句,歌词高亮一句,当前行始终停在中间。原理其实不复杂——解析 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() 把整个列表往上推,让高亮行停在中间;用 CSS transition 让滚动平滑过渡。
  • 性能:只在"当前行索引发生变化"时才操作 DOM——timeupdate 一秒会触发好几次,每次都重排会很浪费。

把示例里写死的 lrcText 换成用 fetch 加载的真实 .lrc 文件,再配上自己的歌曲,一个能跟唱的歌词滚动效果就成了。

—— 别看了 · 2026
下载权限
查看
  • ¥
    免费下载
    评论并刷新后下载
    登录后下载
查看演示
  • {{attr.name}}:
您当前的等级为
登录后免费下载登录 小黑屋反思中,不准下载! 评论后刷新页面下载评论 支付以后下载 请先登录 您今天的下载次数(次)用完了,请明天再来 支付积分以后下载立即支付 支付以后下载立即支付 您当前的用户组不允许下载升级会员
您已获得下载权限 您可以每天下载资源次,今日剩余次
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
源码
网站源码

WordPress自动采集插件WP-AutoPostPro 汉化版 V3.6.2

2024-5-22 11:20:44

网站源码

Python少帅下飞机意味着什么

2024-10-16 11:03:05

1 条回复 A文章作者 M管理员

您必须登录或注册以后才能发表评论

  1. Cmlessss
    Cmlessss 学前班lv0
    25年1月30日

    搞

关于作者

Mores

博导lv7 钻石会员

文章

7566

评论

15

关注

0

粉丝

8
[文章] 我在 TypeScript 里定义了一个 interface 描述配置对象本以为它就是我写的那几个字段,结果某天发现它莫名其妙多出了几个我从没声明过的字段、传值时少给这些字段还报错,排查很久才搞懂项目里别处有一个同名的 interface 而 TypeScript 把这两个同名接口悄悄合并成了一个的深度复盘
[文章] 我给一个公共库里的方法把可选参数的默认超时从 30 秒改成了 60 秒只重新编译发布了这个库的 dll、本以为所有调用方不用动就自动用上新默认值,结果线上一查那些没重新编译的调用方还在用 30 秒的旧默认,排查很久才搞懂 C# 里可选参数的默认值是编译时常量早被内联进了调用方的程序集里的深度复盘
[文章] 我给服务做了配置热更新不重启就能改限流阈值这些参数、本以为很丝滑,可有一次我同时改了限流的阈值和时间窗口两个相关参数推下去、那一瞬间线上限流就乱套了有请求按新阈值配旧窗口的奇怪组合被误杀,排查很久才搞懂我的热更新是一个字段一个字段改的并发请求正好读到了一半新一半旧的中间态的深度复盘
[文章] 我的 RAG 问答在单轮提问时召回又准又好可一进多轮对话就拉胯,用户问完一个问题再追问一句它呢或那这个怎么办、检索就召回一片空白或牛头不对马嘴的内容,排查很久才搞懂我直接拿用户那句带着指代和省略的原始追问去做向量检索而那句话脱离了对话历史根本就没承载足够的语义的深度复盘
Ta的全部动态

最新评论

PREVNEXT
  • 来自:
❯

解锁会员权限

开通会员

解锁海量优质VIP资源

立刻开通

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索
客服
  • 扫码打开当前页

返回顶部
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分

今日签到

连续签到

  • {{item.credit}}
  • 连续{{item.count}}天
查看所有
我的优惠劵
  • ¥优惠劵
    使用时效:无法使用
    使用时效:

    之前

    使用时效:永久有效
    优惠劵ID:
    ×
    限制以下商品使用: 限制以下商品分类使用: 不限制使用:
    [{{ct.name}}]
    所有商品和商品类型均可使用
没有优惠劵可用!

购物车
  • ×
    删除
购物车空空如也!

清空购物车 前往结算
您有新的私信
没有新私信
写新私信 查看全部
Copyright © 2026 小栋博客
・豫ICP备2023004560号
查询 20 次,耗时 0.2166 秒
首页专题认证
搜索菜单我的