web前端渡一大师课 03 歌词滚动效果

效果图: 歌词会随播放滚动

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

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <link rel="shortcut icon" href="./assets/favicon.ico" type="image/x-icon" />
  <link rel="stylesheet" href="./css/index.css" />
</head>
<style>
  * {
    margin: 0;
    padding: 0;
  }

  body {
    background: #000;
    color: #666;
    text-align: center;
  }

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

  .container {
    height: 420px;
    overflow: hidden;
    /* border: 2px solid #fff; */
  }

  .container ul {
    /* border: 2px solid #fff; */
    transition: 0.6s;
    list-style: none;
  }

  .container li {
    height: 30px;
    /* border: 1px solid #fff; */
    line-height: 30px;
    transition: 0.2s;
  }

  .container li.active {
    color: #fff;
    /* font-size: ; */
    transform: scale(1.2);
  }
</style>

<body>
  <audio controls src="./assets/music.mp3"></audio>
  <div class="container">
    <ul class="lrc-list"></ul>
  </div>
   //引入的歌词文件
  <script src="./js/data.js"></script>
  <!-- <script src="./js/index.js"></script> -->
</body>

<script>
  /**
    * 解析歌词字符串
    * 得到一个歌词对象的数组
    * 每个歌词对象: 
    * {time:开始时间,words:歌词内容}
  */

  // [00:01.06]难念的经
  function parseLrc() {
    console.log(lrc);
    // 1. 分割字符串,得到一个数组
    var lines = lrc.split('\n');
    //存放歌词对象的数组
    var result = [];
    //循环遍历数组,拿到每一行的字符串
    for (var i = 0; i < lines.length; i++) {
      var str = lines[i];
      //分割字符串
      var parts = str.split(']');
      //截取时间
      var timeStr = parts[0].substring(1);
      //添加一个对象
      var obj = {
        time: parseTime(timeStr),
        words: parts[1] || '',
      };
      result.push(obj);
      //得到{time: 229.99, words: '天阔阔雪漫漫共谁同航'}
    }
    return result;
  }
  /**
   * 将时间字符串转换为秒数
   * @param {string} timeStr 时间字符串,格式为"mm:ss"
   * @returns {number} 返回转换后的秒数
   */
  function parseTime(timeStr) {
    var parts = timeStr.split(':');
    return +parts[0] * 60 + +parts[1];
  }

  var lrcData = parseLrc();

  //获取需要的dom 
  var doms = {
    audio: document.querySelector('audio'),
    ul: document.querySelector('.container ul'),
    container: document.querySelector('.container'),

  };

  /**
   * 计算出,在当前播放器播放到第几秒的情况下
   * lrcData数组中,应该高亮显示的歌词下标
   * 如果没有任何一句歌词需要显示,则得到-1
   */
  function findIndex() {
    //获取当前播放器的时间
    var curTime = doms.audio.currentTime;
    //遍历lrcData数组
    for (var i = 0; i < lrcData.length; i++) {
      console.log('i', lrcData[i]);
      //如果当前时间小于lrcData[i].time
      //说明当前时间还没有到达这一句歌词的开始时间
      //此时,应该高亮显示的歌词下标就是i-1
      if (curTime < lrcData[i].time) {
        return i - 1;
      }
    }
    //找遍了都没找到(说明播放到最后一句)
    return lrcData.length - 1;
  }


  //界面 创建歌词元素 li  
  function createLrcElements() {
    //文档片段
    var frag = document.createDocumentFragment();
    for (var i = 0; i < lrcData.length; i++) {
      var li = document.createElement('li');
      li.innerText = lrcData[i].words;
      //添加到ul中, 改动了 dom 树 
      // doms.ul.appendChild(li);
      frag.appendChild(li);
    }
    doms.ul.appendChild(frag);
  }
  createLrcElements();

  //尺寸信息 

  //容器高度
  var containerHeight = doms.container.clientHeight;
  // 每个li的高度
  var liHeight = doms.ul.children[0].clientHeight;
  //最大偏移量
  var maxOffset = doms.ul.clientHeight - containerHeight;


  //设置 ul 元素的偏移量 
  function setOffset() {
    console.log('调用了 setOffset');
    //获取当前需要高亮显示的歌词下标
    var index = findIndex();
    //偏移量计算 
    var offset = liHeight * index + liHeight / 2 -
      //容器的一半  
      containerHeight / 2;
    console.log('偏移量', offset);
    //边界判断 最小值
    if (offset < 0) {
      offset = 0;
    }
    if (offset > maxOffset) {
      offset = maxOffset;
    }
    doms.ul.style.transform = `translateY(-${offset}px)`;

    //去掉之前的active样式
    var li = doms.ul.querySelector('.active');
    if (li) {
      li.classList.remove('active');
    }
    //重新获取需要高亮显示的li 
    li = doms.ul.children[index];
    if (li) {
      li.classList.add('active');
    }
  }

  //时间改变的事件 
  doms.audio.addEventListener('timeupdate', setOffset
    //每次播放器时间更新,都需要重新计算偏移量
  );

  //初始偏移量
  setOffset();
</script>

</html>

歌词

var lrc = `[00:01.06]难念的经

00:03.95\]演唱:周华健 \[00:06.78

00:30.96\]笑你我枉花光心计 \[00:34.15\]爱竞逐镜花那美丽 \[00:36.75\]怕幸运会转眼远逝 \[00:39.32\]为贪嗔喜恶怒着迷 \[00:41.99\]责你我太贪功恋势 \[00:44.48\]怪大地众生太美丽 \[00:47.00\]悔旧日太执信约誓 \[00:49.66\]为悲欢哀怨妒着迷 \[00:52.56\]啊 舍不得璀灿俗世 \[00:57.66\]啊 躲不开痴恋的欣慰 \[01:02.86\]啊 找不到色相代替 \[01:08.09\]啊 参一生参不透这条难题 \[01:13.15\]吞风吻雨葬落日未曾彷徨 \[01:15.73\]欺山赶海践雪径也未绝望 \[01:18.23\]拈花把酒偏折煞世人情狂 \[01:20.90\]凭这两眼与百臂或千手不能防 \[01:23.76\]天阔阔雪漫漫共谁同航 \[01:26.09\]这沙滚滚水皱皱笑着浪荡 \[01:28.68\]贪欢一刻偏教那女儿情长埋葬 \[01:32.38

01:34.09\]吞风吻雨葬落日未曾彷徨 \[01:36.50\]欺山赶海践雪径也未绝望 \[01:39.07\]拈花把酒偏折煞世人情狂 \[01:41.69\]凭这两眼与百臂或千手不能防 \[01:44.68\]天阔阔雪漫漫共谁同航 \[01:46.93\]这沙滚滚水皱皱笑着浪荡 \[01:49.54\]贪欢一刻偏教那女儿情长埋葬 \[01:53.41

02:15.45\]笑你我枉花光心计 \[02:18.53\]爱竞逐镜花那美丽 \[02:21.14\]怕幸运会转眼远逝 \[02:23.76\]为贪嗔喜恶怒着迷 \[02:26.43\]责你我太贪功恋势 \[02:28.98\]怪大地众生太美丽 \[02:31.60\]悔旧日太执信约誓 \[02:34.26\]为悲欢哀怨妒着迷 \[02:36.90\]啊 舍不得璀灿俗世 \[02:42.04\]啊 躲不开痴恋的欣慰 \[02:47.34\]啊 找不到色相代替 \[02:52.52\]啊 参一生参不透这条难题 \[02:57.47\]吞风吻雨葬落日未曾彷徨 \[03:00.05\]欺山赶海践雪径也未绝望 \[03:02.64\]拈花把酒偏折煞世人情狂 \[03:05.27\]凭这两眼与百臂或千手不能防 \[03:08.22\]天阔阔雪漫漫共谁同航 \[03:10.49\]这沙滚滚水皱皱笑着浪荡 \[03:13.06\]贪欢一刻偏教那女儿情长埋葬 \[03:18.45\]吞风吻雨葬落日未曾彷徨 \[03:20.90\]欺山赶海践雪径也未绝望 \[03:23.54\]拈花把酒偏折煞世人情狂 \[03:26.21\]凭这两眼与百臂或千手不能防 \[03:29.07\]天阔阔雪漫漫共谁同航 \[03:31.32\]这沙滚滚水皱皱笑着浪荡 \[03:33.92\]贪欢一刻偏教那女儿情长埋葬 \[03:39.32\]吞风吻雨葬落日未曾彷徨 \[03:41.84\]欺山赶海践雪径也未绝望 \[03:44.38\]拈花把酒偏折煞世人情狂 \[03:47.04\]凭这两眼与百臂或千手不能防 \[03:49.99\]天阔阔雪漫漫共谁同航 \[03:52.20\]这沙滚滚水皱皱笑着浪荡 \[03:54.89\]贪欢一刻偏教那女儿情长埋葬 \[04:00.28\]吞风吻雨葬落日未曾彷徨 \[04:02.68\]欺山赶海践雪径也未绝望 \[04:05.25\]拈花把酒偏折煞世人情狂 \[04:07.90\]凭这两眼与百臂或千手不能防 \[04:10.85\]天阔阔雪漫漫共谁同航 \[04:13.08\]这沙滚滚水皱皱笑着浪荡 \[04:15.75\]贪欢一刻偏教那女儿情长埋葬 \[04:19.48\]\`;

相关推荐
练习前端两年半17 分钟前
🚀 深入Vue3核心:render函数源码解析与实战指南
前端·vue.js
leobertlan17 分钟前
杂篇-有感而发,写于2025年7月
java·前端·程序员
gaze34 分钟前
vueuse的createReusableTemplate函数实现原理
前端·vue.js
小码哥_常36 分钟前
Android开发自救指南:当大图遇上OOM,这波操作能保命!
android·前端
jsonchao38 分钟前
阿里毕业 2 个多月后,我闲着无聊做了 1 个小游戏平台
前端
支撑前端荣耀39 分钟前
十四、Cypress持续集成实践——让测试融入开发流水线
前端
今晚一定早睡41 分钟前
new操作符
前端·javascript·typescript
尘心cx1 小时前
前端-CSS-day6
前端·css
红衣信1 小时前
useContext 与 useReducer 的组合使用
前端·react.js·面试
0wioiw01 小时前
Flutter基础(前端教程①③-单例)
前端·flutter