最近在修补个人博客,增加了一个播放音乐的模块,所以在这里记录一下播放歌词实时滚动效果的实现,顺便总结一下,加深一下对各个知识点的理解观点。
GitHub地址
演示地址
该功能尚未完全开发。 当前部分可以通过导航栏进入音乐模块,点击下方歌手图片进入。
先给大家看一下治疗效果图。
这里主要有两个组件:歌曲信息组件和歌曲控制组件。 歌曲控制组件还没有建立。 这里主要记录一下歌曲信息组件的实现。
歌曲信息组件
这部分主要是歌曲列表和当前歌曲信息
1.歌单实现
在此之前,网易云音乐socket的使用请参考网易云音乐API文档
主意:
获取歌曲数据,处理歌曲数据并渲染到视图
(1)实例创建后,即创建的钩子函数期间,我们可以访问data、compute、methods上的技术和数据,然后可以向socket发送请求。
created() {
//通过getSong()获取对应歌曲信息
this.getSong(this.$route.query.name); //在这之前,我是通过路由进入的该组件,所以会传递一个name歌手名字作为参数
};
(2)接下来编译getSong()html5 同步歌词,通过axios获取数据。 我们可以先复制下来看看。
getSong(name) {
let url =
"https://api.imjad.cn/cloudmusic/?type=search&search_type=1&s=" +
name +
"&offset=1&limit=10"; //这里以第一页的十条数据为例
this.$axios
.get(url)
.then(res => console.log(res.data))
.catch();
}
(3) 打开控制台。 很明显,我们得到的并不是我们最终想要的结果,所以我们需要从大量的数据中提取出我们想要的信息。
通常一个歌单所需的数据无外乎如下:
我们可以随意点击其中一个来查找。 至于怎么找到……我们就一一搜索吧。 比如上面有一个name属性,很可能就是歌曲的标题。 socket文档中歌手对应的类型是artist,那么我们就去找这个属性。 如果没有的话,很大概率是短语的缩写,于是我们点击了ar属性,找到了我们想要的。
这一步我们也能感受到参数语义在编码过程中的重要性。
(4)最后,找到我们想要的数据后,我们使用formatSongs()来组织它。
//提取歌曲信息,arr为接口返回的初始数据
formatSongs(arr) {
let n = arr.length;
for (let i = 0; i < n; i++) {
let obj = {}; //我们将每首歌的信息以对象的方式存储
obj.id = arr[i].id;
obj.song = arr[i].name;
obj.singer = arr[i].ar[0].name;
obj.dt = this.formatDt(arr[i].dt); //由于返回的歌曲时长单位是ms,我们还要将其转换成00:00的格式
this.songList.push(obj); //最后将这个歌曲对象存储到组件的songList歌曲列表属性上,最后用个v-for就可以在视图上呈现出来了
}
}
//格式化播放时长
formatDt(time) {
let dt = time / 1000;
let m = parseInt(dt / 60);
let s = parseInt(dt % 60); //这里用Math.ceil取整会更严谨些
m >= 10 ? m : (m = "0" + m);
s >= 10 ? s : (s = "0" + s);
return m + ":" + s;
}
歌曲列表的实现比较简单,不包含标签样式。
2. 获取当前播放歌曲的信息
主意:
获取歌曲信息(主要是歌词)并处理歌曲数据(主要处理歌词格式),实现歌词滚动效果
(1)获取歌词信息
直接上传代码
//获取歌词信息,id为歌曲id
getLyric(id) {
let url = "https://api.imjad.cn/cloudmusic/?type=lyric&id=" + id; //获取歌词信息要设置type=lyric,详情看接口文档
this.$axios
.get(url)
.then(res => ((this.lyric = []), this.formatLyric(res.data.lrc.lyric)))
.catch();//同样的,我们先要在接口返回的数据中提取出我们需要的那部分,并用一个formatLyric方法来对它进行格式化
}
(2)在对歌词进行低级处理之前,我们先随机播放一首歌曲,看看原来的歌词文本是什么样子的。
它看起来还不错,有一定的图案,甚至还帮我们改了线。
我们需要做的就是将时间和文本部分分开......一切都在掌控之中html5 同步歌词,直到我发现这一点......
好的! 原来还有这些写法。 不同时间重复的歌词可以写在一起。 确实,这样的写法简单多了,但是我们也要处理更多的事情……
好吧,我们直接上代码
(3)处理歌词文本
//传入初始歌词文本text
formatLyric(text) {
let arr = text.split("n"); //原歌词文本已经换好行了方便很多,我们直接通过换行符“n”进行切割
let row = arr.length; //获取歌词行数
for (let i = 0; i < row; i++) {
let temp_row = arr[i]; //现在每一行格式大概就是这样"[00:04.302][02:10.00]hello world";
let temp_arr = temp_row.split("]");//我们可以通过“]”对时间和文本进行分离
let text = temp_arr.pop(); //把歌词文本从数组中剔除出来,获取到歌词文本了!
//再对剩下的歌词时间进行处理
temp_arr.forEach(element => {
let obj = {};
let time_arr = element.substr(1, element.length - 1).split(":");//先把多余的“[”去掉,再分离出分、秒
let s = parseInt(time_arr[0]) * 60 + Math.ceil(time_arr[1]); //把时间转换成与currentTime相同的类型,方便待会实现滚动效果
obj.time = s;
obj.text = text;
this.lyric.push(obj); //每一行歌词对象存到组件的lyric歌词属性里
});
}
this.lyric.sort(this.sortRule); //由于不同时间的相同歌词我们给排到一起了,所以这里要以时间顺序重新排列一下
this.$store.commit("setLyric", this.lyric); //把歌词提交到store里,为了重新进入该组件时还能再次渲染
},
sortRule(a, b) { //设置一下排序规则
return a.time - b.time;
}
(4)低格式完成后,我们还通过v-for将歌词渲染到视图中。 接下来我们要做的就是让歌词随着播放的进行而向上滚动。
主要思路如下:
我们先来看看歌词元素的放置。 主要分为三个部分:蓝色框是放置歌词的位置。 我们设置一个位置:相对; 不用担心。 白色的盒子就是我们需要连接的部分。 通过Setabsolute绝对定位。 当歌曲进度更新时,我们可以改变它的top值来达到向下滚动的效果。 不过,只要我们加上overflow:hidden; 到绿色框,我们可以隐藏多余的部分,最终得到之前的效果。 图片功效。
我们使用watch来监听歌曲进度的变化,也就是当前的播放时间。
watch: {
lyricCurrent() {
this.lyric.forEach((element, index) => {
if (this.lyricCurrent == element.time) {
this.lyricMove.top = -index * 2.5 + 6 + "rem";
this.currentRow = index; //通过比较我们歌词属性里的时间与当前播放时间,来定位到该歌词
}
});
}
}
<el-row type="flex" justify="center" class="lyric-contain">
<el-col :span="23" class="song-lyric" :style="lyricMove">
<el-row
v-for="(item,index) in lyric"
:key="index"
:style="{'font-size': (index==currentRow ? '1.3rem':'.9rem')}"
class="lyric-row"
>{{item.text}}</el-row>
</el-col>
</el-row>
写在最后:
它还涉及到多个组件,以及状态管理等,所以我没有贴出全部代码。 更多的是提供一个实现思路。 有些地方可能表达不准确,欢迎大家批评指正。