歌词滚动效果

新建工程

基础代码结构

  1. 只要把 favicon.ico 图片放到站点根目录, 就可以被识别到, 不建议这样做
  2. 要在HTML文件中通过标签引入favicon.ico
  3. 原生的audio控件的样式是固定的, 调整的代价很大, 所以专业的音乐网站都是用<div>画, 然后通过JS控制功能, 我们这里就用这个控件就行

静态结构

要实现的效果

复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="shortcut icon" href="./assets/favicon.ico" type="image/x-icon">
  <link rel="stylesheet" href="./css/index.css" />
  <title>Document</title>
</head>

<body>
  <audio controls src="./assets/music.mp3"></audio>

  <div class="container">
    <ul class="lrc-list">
      <li>Lorem ipsum dolor sit.</li>
      <li>Cupiditate itaque doloribus nihil?</li>
      <li class="active">Quos molestiae voluptas illo.</li>
      <li>Inventore consequatur pariatur harum?</li>
      <li>Quam voluptatum eligendi aliquam.</li>
      <li>Eos eligendi amet ratione.</li>
      <li>Voluptatem iure labore maiores.</li>
      <li>Soluta deleniti beatae aliquam.</li>
      <li>Corrupti id ipsam laboriosam!</li>
      <li>Tempore enim inventore reiciendis?</li>
      <li>Ad modi placeat similique!</li>
      <li>Eius, cum dignissimos! Corporis.</li>
      <li>Eaque repudiandae minima accusantium.</li>
      <li>Aliquid repudiandae natus dicta.</li>
      <li>Hic, distinctio magnam. Aspernatur.</li>
      <li>Minima quas maxime vero!</li>
      <li>Tempora, animi dolores. Ipsum?</li>
      <li>Aliquid reiciendis et aspernatur?</li>
      <li>Amet neque dolore maiores!</li>
      <li>Nulla error magni sit!</li>
      <li>Repellat similique qui dolorum?</li>
      <li>Eveniet blanditiis itaque deleniti!</li>
      <li>Quod alias fuga mollitia?</li>
      <li>Ab aperiam rem asperiores.</li>
      <li>Ipsam dolor magni repudiandae.</li>
      <li>Dignissimos illum libero hic.</li>
      <li>Doloribus repellendus optio dolores!</li>
      <li>Perspiciatis repellat omnis in?</li>
      <li>Ad, molestiae vero. Et?</li>
      <li>Facilis distinctio totam est?</li>
    </ul>
  </div>
</body>

</html>

* {
  margin: 0;
  padding: 0;
}

body {
  background: #000;
  color: #666;
  /* 让行盒的子元素实现居中, 文字属于行盒 */
  text-align: center;
}

audio {
  width: 450px;
  margin: 30px 0;
}

.container {
  height: 420px;
  overflow: hidden;
}

.container .lrc-list {
  transform: 0.2s;
  /* 引发重排: 元素的几何信息发生改变 */
  /* margin-top: -20px; */
  /* 渲染效率更高 */
  transform: translateY(-20px);
}

.container li {
  height: 35px;
  line-height: 35px;
  /* 过渡属性: 数值类的属性有效 */
  /* width: 0->100 有效 */
  /* opacity: 1-> 0 有效 */
  /* color: #000 -> #fff 有效 */
  /* display: block -> none 无效 */
  /* 通常加加给容器, 双程过渡 */
  /* 加给元素, 类名出现时有过渡, 类名移除时没有过渡 */
  transition: 0.2s;
}

.container li.active {
  color: #fff;
  /* 引发重排: 元素的几何信息发生改变 */
  /* font-size: 30px; */
  /* 渲染效率更高 */
  transform: scale(1.4);
}
  1. lorem乱数假文: 用于测试判断的文字
  2. 快速生成: li*30>lorem4

搞定数据

复制代码
<!DOCTYPE html>
<html lang="en">

<head>
</head>

<body>

  ... ...
 
  <script src="./js/data.js"></script>
  <script src="./js/index.js"></script>
</body>

</html>

// 1.查看数据
// 已有的数据是一个字符串, 不利于后续的操作, 自己处理一下
// console.log(lrc);

/**
 * 处理时间字符串, 把时间换算为秒
 * @param {String} timeStr 
 * @returns 秒值
 */
function parseTime(timeStr) {
  const parts = timeStr.split(":")
  return +parts[0] * 60 + +parts[1]
}


/**
 * 处理歌词字符串, 返回歌词数组
 * [{time:开始时间, words: 歌词内容}]
 */
function parseLrc() {
  const lines = lrc.split("\n")
  const lrcArr = []
  lines.forEach(item => {

    const parts = item.split("]")
    const timeStr = parts[0].substring(1)
    const obj = {
      time: parseTime(timeStr),
      words: parts[1]
    }
    lrcArr.push(obj)
  })
  return lrcArr
}

const lrcData = parseLrc()
console.log(lrcData);



// 2.数据处理完了, 开始考虑数据和功能的关联
// 高亮显示的歌词决定了列表的偏移量

const doms = {
  audio: document.querySelector("audio")
}

/**
 * 根据当前播放器的秒值, 查找lrcData数组中,对应的歌词对象的索引下标
 */
function findIndex() {
  const curTime = doms.audio.currentTime;

  for (let index = 0; index < lrcData.length; index++) {
    if (curTime < lrcData[index].time) {
      return index - 1
    }
  }

  return lrcData.length - 1
}


findIndex()

搞定页面

复制代码
// 1.查看数据
// 已有的数据是一个字符串, 不利于后续的操作, 自己处理一下
// console.log(lrc);

/**
 * 处理时间字符串, 把时间换算为秒
 * @param {String} timeStr 
 * @returns 秒值
 */
function parseTime(timeStr) {
  const parts = timeStr.split(":")
  return +parts[0] * 60 + +parts[1]
}


/**
 * 处理歌词字符串, 返回歌词数组
 * [{time:开始时间, words: 歌词内容}]
 */
function parseLrc() {
  const lines = lrc.split("\n")
  const lrcArr = []
  lines.forEach(item => {

    const parts = item.split("]")
    const timeStr = parts[0].substring(1)
    const obj = {
      time: parseTime(timeStr),
      words: parts[1]
    }
    lrcArr.push(obj)
  })
  return lrcArr
}

const lrcData = parseLrc()


// 2.数据处理完了, 开始考虑数据和功能的关联
// 高亮显示的歌词决定了列表的偏移量

const doms = {
  audio: document.querySelector("audio"),
  ul: document.querySelector("ul"),
  container: document.querySelector(".container")
}

/**
 * 根据当前播放器的秒值, 查找lrcData数组中,对应的歌词对象的索引下标
 */
function findIndex() {
  const curTime = doms.audio.currentTime;

  for (let index = 0; index < lrcData.length; index++) {
    if (curTime < lrcData[index].time) {
      return index - 1
    }
  }

  return lrcData.length - 1
}



// 3. 考虑界面逻辑
/**
 * 创建歌词元素
 */
function createLrcElements() {
  const frag = document.createDocumentFragment() // 文档片段
  lrcData.forEach(item => {
    const li = document.createElement("li")
    li.textContent = item.words
    // doms.ul.append(li) // 改动Dom树
    frag.appendChild(li)
  })
  doms.ul.appendChild(frag)
}

createLrcElements()

const containerHeight = doms.container.clientHeight; // 容器高度
const liHeight = doms.ul.children[0].clientHeight;  // 歌词li高度
const maxOffset = doms.ul.clientHeight - containerHeight; // 最大偏移量

/**
 * 设置 ul元素的偏移量
 */

function setOffset() {
  const index = findIndex()
  let offset = liHeight * index + liHeight / 2 - containerHeight / 2;

  if (offset < 0) {
    offset = 0
  }

  if (offset > maxOffset) {
    offset = maxOffset
  }

  doms.ul.style.transform = `translateY(-${offset}px)`

  let li = doms.ul.querySelector(".active")
  if (li) {
    li.classList.remove("active")
  }

  li = doms.ul.children[index]
  if (li) {
    li.classList.add("active")
  }

}


// 4.考虑事件
doms.audio.addEventListener("timeupdate", setOffset)
相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax