最近看了一篇关于音频可视化的博文,然后偶然联想到歌词滚动这个效果,虽然网上介绍歌词滚动 的文章已经很多了,但是并不是每一个人实现的思路都是一致的,所以在这给大家分享一下我自己的一些想法,和具体是如何实现这个功能的 。
思路:获取歌曲所匹配的LRC歌词数据 网易云音乐 LRC 滚动歌词,获取音频实时播放时间,匹配LRC歌词所对应时间的文字,通过js控制为当前歌词文本添加动画,以及修改动画执行时间即可实现。
一、处理歌词数据
原始歌词数据实际上不需要我们生成,实际上是一串带有时间和歌词的字符串,首先需要转换成数组对象方便使用。
使用网易云LRC滚动歌词就可以获取到自己想要的歌词,具体获取方法可以看上面链接
先声明formatLrc函数,用来处理原始各吃数据,通过split截取字符串,去掉换行符,或得新数组,循环新数组,按照']'截取,得到新数组格式 类似于['[01:42.000','没有你'] 很明显多了个 '['符号,使用substring()方法截取掉每一个 '['即可,保存所有处理后的数据,就是我们想要的数据格式啦,具体代码如下:
formatLrc() {
var strLrc = this.LRC.split("\n");//按换行截取字符串,去掉换行得到一个数组strLrc
let arr=[]//声明数组
for (var i = 0; i < strLrc.length; i++) {//循环strLrc
var str = strLrc[i];
var parts = str.split("]");//按照']'截取strLrc的每一项,得到新数组parts
var timeStr = parts[0].substring(1);//获取时间字符串
var obj = {
time: timeStr,
words: parts[1],
};
arr.push(obj)//每循环一次把obj添加到arr数组末尾
}
return arr;
},
通过以上处理得到一个新的数据 ,数据具体格式如下:
二、获取当前播放时间
<audio :src="music" @timeupdate="audioTime" controls></audio>
音频播放控件有个事件 @timeupdate事件可以实时反馈当前播放进度信息,封装audioTime函数,具体代码如下:
audioTime(e) {
var time = e.target.currentTime; //当前播放器时间
for (var i = 0; i < this.lrcData.length; i++) {
if (time < this.lrcData[i].time) {
//循环歌词数组,当播放器当前时间第一次小于歌词时间时当前数组下标减一即为当前时间数组所对应歌词下标
this.dataWords = this.lrcData[i - 1].words;
//保存当前歌词动画执行事件
this.lrcTime = this.lrcData[i].time - this.lrcData[i - 1].time;
return i - 1;
}
}
},
获取到当前播放时间之后,和歌词数组里的时间相互匹配,当播放器当前时间第一次小于歌词时间时当前数组下标减一即为当前时间数组所对应歌词下标。
具体页面布局我这里写的相对比较简单,主要只有一个播放控件
<template>
<div>
<audio :src="music" @timeupdate="audioTime" controls></audio>
<div
:class="className"
:style="{ 'animation-duration': `${lrcTime}000ms` }" >
{{ dataWords }}
</div>
</div>
</template>
css定义动画,实现歌词颜色从左到右缓慢变化。
@keyframes scan {
0% {
background-size: 0 100%;
}
100% {
background-size: 100% 100%;
}
}
.text {
background: #7e7e7e -webkit-linear-gradient(left, #76ca16, #0fa046) no-repeat 0
0;
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
.load {
background-size: 100% 100%;
animation: scan linear;
}
由于每一句歌词演唱时间不同,如果每一句歌词动画执行时间都相同的话,页面会很生硬,显得比较尴尬。我这里用了个笨办法,用下一句歌词的时间减去当前歌词的时间,得到的就是当前歌词演唱所需的时间,把歌词动画时间修改为当前获取的花时间,即可实现歌词和演唱时间同步的效果,具体代码如下:
audioTime(e) {
var time = e.target.currentTime; //当前播放器时间
for (var i = 0; i < this.lrcData.length; i++) {
if (time < this.lrcData[i].time) {
//循环歌词数组,当播放器当前时间第一次小于歌词时间时当前数组下标减一即为当前时间数组所对应歌词下标
this.lrcTime = this.lrcData[i].time - this.lrcData[i - 1].time;
this.dataWords = this.lrcData[i - 1].words;
return i - 1;
}
}
},
综上所述,即可得到一个简单的歌词和歌曲同步效果的歌词滚动!
以下是全部代码:
<template>
<div class="hello">
<audio :src="music" @timeupdate="audioTime" controls></audio>
<div
:class="className"
:style="{ 'animation-duration': `${lrcTime}000ms` }" >
{{ dataWords }}
</div>
</div>
</template>
<script>
import music from "../assets/music.mp3";
export default {
data() {
return {
className: "text load",
lrcTime: "",
music,
currenttime: "",
LRC: `[00:00.000] 作词 : 周耀辉/李焯雄
[00:01.000] 作曲 : 林健华
[00:02.000] 编曲 : 林健华
[00:15.000]忽然之间
[00:18.000]天昏地暗
[00:21.000]世界可以忽然什么都没有
[00:28.000]我想起了你
[00:32.000]再想到自己
[00:35.000]我为什么总在非常脆弱的时候
[00:40.000]怀念你
[00:43.000]''
[00:44.000]我明白太放不开你的爱
[00:49.000]太熟悉你的关怀分不开
[00:54.000]想你算是安慰还是悲哀
[00:58.000]而现在就算时针都停摆
[01:03.000]就算生命像尘埃分不开
[01:08.000]我们也许反而更相信爱
[01:24.000]''
[01:25.000]如果这天地
[01:29.000]最终会消失
[01:32.000]不想一路走来珍惜的回忆
[01:38.000]没有你
[01:40.000]''
[01:41.000]我明白太放不开你的爱
[01:46.000]太熟悉你的关怀分不开
[01:51.000]想你算是安慰还是悲哀
[01:55.000]而现在就算时针都停摆
[02:00.000]就算生命像尘埃分不开
[02:05.000]我们也许反而更相信爱
[02:34.000]''
[02:35.000]我明白太放不开你的爱
[02:40.000]太熟悉你的关怀分不开
[02:45.000]想你算是安慰还是悲哀
[02:49.000]而现在就算时针都停摆
[02:54.000]就算生命像尘埃分不开
[03:00.000]我们也许反而更相信爱`,
lrcData: "",
dataWords: "",
};
},
name: "HelloWorld",
methods: {
//歌词数据转化为数组
formatLrc() {
var strLrc = this.LRC.split("\n");
let arr = [];
for (var i = 0; i < strLrc.length; i++) {
var str = strLrc[i];
var parts = str.split("]");
var timeStr = parts[0].substring(1);
var obj = {
time: this.formatTime(timeStr),
words: parts[1],
};
arr.push(obj);
}
this.lrcData = arr;
},
//时间转换(秒)
formatTime(time) {
var parts = time.split(":"); //[03:00.000]==>[03,00.00]
return +parts[0] * 60 + +parts[1]; //计算秒
},
audioTime(e) {
var time = e.target.currentTime; //当前播放器时间
for (var i = 0; i < this.lrcData.length; i++) {
if (time < this.lrcData[i].time) {
//循环歌词数组,当播放器当前时间第一次小于歌词时间时当前数组下标减一即为当前时间数组所对应歌词下标
this.lrcTime = this.lrcData[i].time - this.lrcData[i - 1].time;
this.dataWords = this.lrcData[i - 1].words;
return i - 1;
}
}
},
},
watch: {
dataWords() {
this.className = "text";
setTimeout(() => {
this.className = "text load";
}, 50);
},
},
mounted() {
this.formatLrc();
},
};
</script>
<style scoped>
@keyframes scan {
0% {
background-size: 0 100%;
}
100% {
background-size: 100% 100%;
}
}
.text {
background: #7e7e7e -webkit-linear-gradient(left, #76ca16, #0fa046) no-repeat 0
0;
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
.load {
background-size: 100% 100%;
animation: scan linear;
}
</style>
代码写得很简单,大致思路以及代码都在这里了,大家可以参考,还有很多细节需要优化,例如动画切换,以及每次动画执行时间执行速度等,都可以继续优化。这里仅供大家参考!!还望各位大佬多多指教,留言交流。