实现歌词滚动效果

歌曲滚动效果的实现

话不多说,先看效果图:

在这里插入图片描述
歌词随着时间进行变化,那这种效果我们应该怎么去做呢?实现的思路又是怎样的呢?

1.先找需要的材料

我把材料发放在了网盘里,大家通过链接获取,里面有对应的mp3文件及数据

链接:https://pan.baidu.com/s/1RdDty0ctUl3k-7o-43-0nw?pwd=388p
提取码:388p

2,对得到的资料进行分析,进行数据处理

在这里插入图片描述

这个是我们获取到的数据,由于我是在vue2项目里面做的,所以我用了export将需要的数据引出,然后在对应的页面进行处理

那么问题来了:我们需要的数据是什么?什么样的数据我们才能让歌词与时间联系起来?

answer:其实就是歌词对应在MP3文件中对应的播放时间,所以我们需要获取对应歌曲每句歌词所对应的时间

接下来,我们在对应的文件中引入我们歌曲数据

import lrcSong from "../assets/js/song";

然后对取到的数据进行处理:将取到的歌词数据变成对象数组

对于取到的数据,我们只需要lyric部分,这部分是每句歌词对应的时间的字符串

由于我们取到的数据是字符串形式的,所以需要对字符串进行拆分,理由很简单,我们最后需要的数据是字符数组的形式

所以parseLrc(lrcSong)函数是对取到的数据变换成对象数组的函数,其中调用了parseTime函数,这个函数是我们用来将字符串的时间转换成秒数的格式,

parseLrc(lrcSong) {
      let songdata = lrcSong.parseLrc(lrcSong) {
      let songdata = lrcSong.lyric;
      let songArr = songdata.split("\n");
      let result = [];
      for (var i = 0; i < songArr.length; i++) {
        // console.log(songArr[i]);
        let str = songArr[i];
        var parts = str.split("]");
        let timeStr = parts[0].substring(1);
        let obj = {
          time: this.parseTime(timeStr),
          words: parts[1]
        };
        result.push(obj);
      }
      this.song = result; //我们最后需要的数组对象,如果用纯js的话,那就直接return就行,最后找个变量接收
    },
    // 处理时间问题
    parseTime(time) {
      let resultArr = time.split(":");
      return +resultArr[0] * 60 + +resultArr[1];
    },

通过这两个函数对数据的处理,我们最后得到了一个对象数组,打印出来是这样的:

在这里插入图片描述

3.联系dom元素,将歌词填充到dom元素里面

HTML的代码

 <div>
    <!-- 这种做法不能播放 -->
    <!-- <audio src="../assets/1480251493.mp3" controls></audio> -->
    <audio controls>
      <source src="../assets/1480251493.mp3" />
    </audio>
    <div class="container">
      <ul></ul>
    </div>
  </div>

注:先开始写的时间第一中audio的写法不能播放,第二种写法就可以,实战的时候需要注意一下

我们将处理好的数据放入到对应的dom元素里面

所以我们先获取dom元素

getDom() {
      let tempdom = {
        audio: document.querySelector("audio"),
        ul: document.querySelector(".container ul"),
        container: document.querySelector(".container")
      };
      this.doms = tempdom;//如果用纯js的话,那就直接return就行,最后找个变量接收
    },

这样的话,我们就获取到了我们的dom元素,下一步就是将歌词填充到对应的ul标签

// 创建歌词元素
    createLi() {
      // 为了提高效率,采用文档片段,因为这种做法不涉及reflow,对浏览器渲染主线程不影响
      let temp = document.createDocumentFragment();
      let data = this.song;
      for (var i = 0; i < data.length; i++) {
        let li = document.createElement("li");
        li.textContent = data[i].words;
        console.log(data[i].words);
        temp.appendChild(li);
      }
      this.doms.ul.appendChild(temp);
    },

通过这种方法我们就将歌词填充到了ul标签中这样我们就可以看到ul里面li了

样式代码:

* {
  margin: 0;
  padding: 0;
}
body {
  background-color: #000000;
  color: #666;
  text-align: center;
}
audio {
  width: 420px;
  margin: 30px 0;
}
.container {
  overflow: hidden;
  height: 420px;
}
.container ul {
  transition: 0.8s;
  list-style: none;
}
.container li {
  height: 30px;
  line-height: 30px;
  transition: 0.2s;
}

现在我们将歌词放入到ul标签了,最后一步也就是最重要的,我们如何与mp3文件进行联系,形成滚动效果呢?

4.将歌词与时间联系起来

首先我们需要完成一个工作,就是需要知道什么时间到了哪句歌词

 // 看歌词到了那个位置
    findindex() {
      let data = this.song;
      let currentTime = this.doms.audio.currentTime;
      for (var i = 0; i < data.length; i++) {
        if (currentTime < data[i].time) {
          return i - 1;
        }
      }
      return data.length - 1;
    },

让audio当前播放时间与处理好的数据进行对比,获取对应的index,这样我们就知道当前唱到了第几局句歌词

我们获取到了第几句歌词之后,我们就对歌词进行样式渲染了,就实现我们想要的滚动效果了

在实现这个之间,我们需要思考一个问题,滚动的效果是怎么实现的呢?

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C86180%5CAppData%5CRoaming

看这张图,我们可以知道,其实就是ul标签的滚动,container位置不进行变化,让ul标签向上移动,使得两个元素产生相对移动,形成这种滚动的效果,所以我们对于偏移值的把握是十分重要的

 setOffsetAndliStyle() {
      let containerHeight = this.doms.container.clientHeight;
      let liheight = this.doms.ul.children[0].clientHeight;
      let index = this.findindex();
      let maxOffset = this.doms.ul.clientHeight - containerHeight;
      let offset = liheight * index + liheight / 2 - containerHeight / 2;
      if (offset < 0) {
        offset = 0;
      }
      if (offset > maxOffset) {
        offset = maxOffset;
      }
      let li = this.doms.ul.querySelector(".active");
      if (li) {
        li.classList.remove("active");
      }
      li = this.doms.ul.children[index];
      if (li) {
        li.classList.add("active");
      }
      console.log(offset);
      this.doms.ul.style.transform = `translateY(-${offset}px)`;
    },

当我们实现ul移动的时候,移动的多少是一个问题,还要考虑对应的临界值

偏移值的计算:

在这里插入图片描述

蓝线就是代表当前高亮显示的标签到ul元素顶部的距离

liheight * index + liheight / 2

棕线就是代表container元素的一半

这样我们就可以计算出偏移值了

let offset = liheight * index + liheight / 2 - containerHeight / 2;

但现在还没有结束,我们还需要对偏移值判断

 if (offset < 0) {
        offset = 0;
      }
      if (offset > maxOffset) {
        offset = maxOffset;
      }

这里面的maxOffset是最大的偏移值,代表到了最后一句歌词的位置不能上移了,凭个人喜好,加不加都可

那么我们就差最后一步对li标签的渲染了

let li = this.doms.ul.querySelector(".active");
      if (li) {
        li.classList.remove("active");
      }
      li = this.doms.ul.children[index];
      if (li) {
        li.classList.add("active");
      }

active样式

.container li.active {
  color: #fff;
  transform: scale(1.4);
}

这样我们就做好歌词的滚动效果了!下面是我的源码

<template>
  <div>
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
    <!-- 这种做法不能播放 -->
    <!-- <audio src="../assets/1480251493.mp3" controls></audio> -->
    <audio controls>
      <source src="../assets/1480251493.mp3" />
    </audio>
    <div class="container">
      <ul></ul>
    </div>
  </div>
</template>

<script>
import lrcSong from "../assets/js/song";
export default {
  name: "HelloWorld",
  data() {
    return {
      song: [],
      doms: {},
      currentIndex: -1
    };
  },
  mounted() {
    this.parseLrc(lrcSong);
    this.getDom();
    this.createLi();
    this.audioStart();
  },
  methods: {
    parseLrc(lrcSong) {
      let songdata = lrcSong.lyric;
      let songArr = songdata.split("\n");
      let result = [];
      for (var i = 0; i < songArr.length; i++) {
        // console.log(songArr[i]);
        let str = songArr[i];
        var parts = str.split("]");
        let timeStr = parts[0].substring(1);
        let obj = {
          time: this.parseTime(timeStr),
          words: parts[1]
        };
        result.push(obj);
      }
      this.song = result;
      console.log(this.song);
    },
    // 处理时间问题
    parseTime(time) {
      let resultArr = time.split(":");
      return +resultArr[0] * 60 + +resultArr[1];
    },
    // 获取dom
    getDom() {
      let tempdom = {
        audio: document.querySelector("audio"),
        ul: document.querySelector(".container ul"),
        container: document.querySelector(".container")
      };
      this.doms = tempdom;
    },
    // 创建歌词元素
    createLi() {
      // 为了提高效率,采用文档片段,因为这种做法不涉及reflow,对浏览器渲染主线程不影响
      let temp = document.createDocumentFragment();
      let data = this.song;
      for (var i = 0; i < data.length; i++) {
        let li = document.createElement("li");
        li.textContent = data[i].words;
        console.log(data[i].words);
        temp.appendChild(li);
      }
      this.doms.ul.appendChild(temp);
    },
    // 看歌词到了那个位置
    findindex() {
      let data = this.song;
      let currentTime = this.doms.audio.currentTime;
      for (var i = 0; i < data.length; i++) {
        if (currentTime < data[i].time) {
          return i - 1;
        }
      }
      return data.length - 1;
    },
    setOffsetAndliStyle() {
      let containerHeight = this.doms.container.clientHeight;
      let liheight = this.doms.ul.children[0].clientHeight;
      let index = this.findindex();
      let maxOffset = this.doms.ul.clientHeight - containerHeight;
      let offset = liheight * index + liheight / 2 - containerHeight / 2;
      if (offset < 0) {
        offset = 0;
      }
      if (offset > maxOffset) {
        offset = maxOffset;
      }
      let li = this.doms.ul.querySelector(".active");
      if (li) {
        li.classList.remove("active");
      }
      li = this.doms.ul.children[index];
      if (li) {
        li.classList.add("active");
      }
      console.log(offset);
      this.doms.ul.style.transform = `translateY(-${offset}px)`;
    },
    audioStart() {
      this.doms.audio.addEventListener("timeupdate", this.setOffsetAndliStyle);
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
* {
  margin: 0;
  padding: 0;
}
body {
  background-color: #000000;
  color: #666;
  text-align: center;
}
audio {
  width: 420px;
  margin: 30px 0;
}
.container {
  overflow: hidden;
  height: 420px;
  /* border: 2px solid #fff; */
}
.container ul {
  transition: 0.8s;
  list-style: none;
}
.container li {
  height: 30px;
  line-height: 30px;
  transition: 0.2s;
}
.container li.active {
  color: #fff;
  transform: scale(1.4);
}
</style>

这个demo是结合vue2做的,如果用纯js做的同学简单改一下变量就可以,里面的方法思路都是适用的~

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值