歌曲滚动效果的实现
话不多说,先看效果图:
歌词随着时间进行变化,那这种效果我们应该怎么去做呢?实现的思路又是怎样的呢?
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,这样我们就知道当前唱到了第几局句歌词
我们获取到了第几句歌词之后,我们就对歌词进行样式渲染了,就实现我们想要的滚动效果了
在实现这个之间,我们需要思考一个问题,滚动的效果是怎么实现的呢?
 {
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做的同学简单改一下变量就可以,里面的方法思路都是适用的~