歌词滚动效果

新建工程

基础代码结构

  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)
相关推荐
J***Q29213 小时前
Vue数据可视化
前端·vue.js·信息可视化
ttod_qzstudio14 小时前
深入理解 Vue 3 的 h 函数:构建动态 UI 的利器
前端·vue.js
_大龄15 小时前
前端解析excel
前端·excel
一叶茶15 小时前
移动端平板打开的三种模式。
前端·javascript
前端大卫15 小时前
一文搞懂 Webpack 分包:async、initial 与 all 的区别【附源码】
前端
Want59515 小时前
HTML音乐圣诞树
前端·html
老前端的功夫16 小时前
前端浏览器缓存深度解析:从网络请求到极致性能优化
前端·javascript·网络·缓存·性能优化
Running_slave16 小时前
你应该了解的TCP滑窗
前端·网络协议·tcp/ip
程序员小寒17 小时前
前端高频面试题之CSS篇(一)
前端·css·面试·css3
颜酱17 小时前
Monorepo 架构以及工具选型、搭建
前端·javascript·node.js