歌词滚动效果

新建工程

基础代码结构

  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)
相关推荐
法医2 小时前
和文心快码做朋友,让编程像“说话”一样简单
前端·文心快码
前端小巷子2 小时前
JS 打造「放大镜 + 缩略图」一体组件
前端·javascript·面试
陈随易2 小时前
适合中国宝宝的AI编程神器,文心快码
前端·后端·node.js
知识分享小能手2 小时前
React学习教程,从入门到精通,React AJAX 语法知识点与案例详解(18)
前端·javascript·vue.js·学习·react.js·ajax·vue3
UrbanJazzerati2 小时前
掌握 DOM 的基础属性与方法:从操作元素到构建动态效果
前端·面试
hashiqimiya3 小时前
html实现右上角有个图标,鼠标移动到该位置出现手型,点击会弹出登录窗口。
前端·html
古夕3 小时前
前端文件下载的三种方式:a标签、Blob、ArrayBuffer
前端·javascript·vue.js
纯真时光3 小时前
Vue3中pinia状态管理库的使用(Composition API 风格)
前端
万少3 小时前
可可图片编辑 HarmonyOS(5)滤镜效果
前端