调用接口:https://api.imjad.cn/cloudmusic/
获取所需信息:
let song = this.url + '?type=song&id='+id+'';
let lyric = this.url + '?type=lyric&id='+id+'';
let detail = this.url + '?type=detail&id='+id+'';
let comments = this.url + '?type=comments&id='+id+'';
获取API接口数据后
需要展示给用户看到的有
歌曲名 - 歌手名
歌曲照片
歌曲专辑
歌曲时长
评论
将获取到一首歌的所需信息放到一个对象中,把由所有歌曲信息组成的对象添加到一个数组中,v-for该数组以渲染界面
歌词滚动,滑动,点击需求
滚随时间滚动,滚动距离为当前播放歌词所在行的高度,以防止有些歌曲歌词宽度较高(一句话较长),使当前播放歌词不能出现屏幕中间部分,进而影响后面歌词展示
手动上下滑动查看下面和上面的歌词,当滑到超出当前播放歌词的位置,在下次播放歌词的时候‘回滚’回歌词播放位置始终显示在屏幕中间部,当滑动时,鼠标不抬起,则播放下一句歌词时不‘回滚’,鼠标抬起后,再次播放下面歌词再自动‘回滚’回去
点击不是当前模仿歌词或是当前模仿歌词,立即播放到该位置 通过给每个生成的歌词的标签添加data-index自定义属性,属性值为歌词在数组中对应的索引,点击歌词获取歌词在数组中的位置,找到当前歌词对应的播放时间,将currentTime设置成该时间以实现
下面是全部代码,主要是为了功能实现效果,有些代码有优化的空间,没做具体完善,直接粘贴应该就可以用的
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.js"></script>
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.js"></script>
<style type="text/css">
* {
padding: 0;
margin: 0;
}
#app {
width: 100%;
height: 100%;
display: flex;
flex-flow: column nowrap;
position: relative;
overflow: hidden;
}
.card {
border: 1px solid #000000;
width: 20%;
margin: 10px;
box-sizing: border-box;
border-radius: 10px;
padding: 5px;
overflow: hidden;
}
.cover_url {
width: 6.25rem;
height: 6.25rem;
}
audio {
visibility: hidden;
}
.lyric {
align-self: center;
position: absolute;
width: 700px;
height: 600px;
margin: 60px;
background-color: #ccc;
overflow: hidden;
}
.con {
position: relative;
width: 6.25rem;
height: 6.25rem;
}
.play {
width: 2.2875rem;
height: 2.25rem;
position: absolute;
background: url(icon_list_menu.png) no-repeat -40px 0;
left: 60px;
bottom: 5px;
}
.played {
background: url(icon_list_menu.png) no-repeat -40px -200px !important;
}
.inner{
width: 16.5rem;
height:37.45rem;
margin: 0 auto;
padding: 300px 0 0 0 ;
user-select: none;
cursor: pointer;
/* border: 1px solid #000000; */
transform: translateY(0);
}
.word{
text-align: center;
margin: 10px;
}
.green{
color: #00b800;
font-weight: bold;
}
</style>
</head>
<body>
<div id="app">
<div v-for="(list,index) in info" :key="index" class="card">
{{list.name}}<br>
{{list.singer}}<br>
<span class="con">
<img class="cover_url" :src="list.cover_url" alt="no">
<i class="play" :class="[currentIndex == index ? 'played' : '']" @click="play(list.music_url,index,list.lyric)"></i>
</span><br>
{{list.alia}}<br>
{{list.al_name}}
</div>
<div class="lyric">
<div class="inner" ref="inner" @mousedown="drag"></div>
</div>
<audio ref="audio" :src="music_url" @timeupdate="timeupdate" controls autoplay></audio>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
url: 'https://api.imjad.cn/cloudmusic/',
lists: [],
ids: ['1361195373', '28012031', '569213220'],
info: [],
music_url: '',
currentIndex: -1,
time:[],
word:[],
index:0,
i:0,
mark:0,
lateY:0,
defaultY:0,
moveY:0,
data_index:0
}
},
mounted() {
this.getLists()
this.$refs.inner.style.transform = 'translateY(0)'
},
methods: {
seek(w){
console.log(w)
},
drag(e){
let tarY = e.pageY
this.lateY = (this.$refs.inner.style.transform).match(/\.*\d+/g)[0]
this.$refs.inner.onmousemove = (e) => {
this.moveY = -(tarY - e.pageY) - this.lateY
this.$refs.inner.style.transform = 'translateY('+this.moveY+'px)'
}
document.onmouseup = () => {
this.$refs.inner.onmousemove = null;
this.$refs.inner.onmousedown = null;
}
},
timeupdate(){
let curr = this.$refs.audio.currentTime * 1000 || 0;
this.index = this.time.findIndex( t => curr - t <= 0 );
this.$refs.inner.childNodes.forEach(x =>x.className = 'word');
this.index = this.index<=0 ? 1 :this.index;
this.$refs.inner.childNodes[this.index - 1].className = 'green word';
//每次歌词‘回滚’加一个小动画,自己主动拖动时没有过渡
this.$refs.inner.style.transition = '';
if(this.mark !== this.index){
//每次歌词滚动距离为当前行高(每行歌词宽度不近相同)+ 当前translateY
let currH = this.$refs.inner.childNodes[this.index-1].offsetHeight;
this.$refs.inner.style.transform = 'translateY(-'+ (currH + this.defaultY) +'px)';
this.$refs.inner.style.transition = 'all 1s ease';
//当前translateY (放在后面,不受歌词滚动期间手动调整translateY影响,能回来,放前面划到哪就是那了)
this.defaultY = Number((this.$refs.inner.style.transform).match(/\.*\d+/g)[0])
this.i++
}
this.mark = this.index;
},
play(url, index, lyric) {
this.word = []
this.time = []
this.currentIndex = index
this.$refs.audio.src = url
this.okLyric(lyric)
this.$refs.inner.innerHTML = `${this.word.map((w,i) => `<p data-index=${i} class="word">${w}</p>`).join('')}`;
//点击新的歌曲,translateY 置为0
this.$refs.inner.style.transform = 'translateY(0px)'
this.i = 0;
this.mark = 0;
this.defaultY = 0;
//歌词点击事件
this.$refs.inner.childNodes.forEach(x => {
x.onclick = () => {
let id_w = x.getAttribute('data-index');
this.$refs.audio.currentTime = this.time[id_w] / 1000;//换算成秒
this.defaultY = this.moveY
}
})
},
getLists() {
this.ids.forEach(x => {
this.getInfo(x)
})
},
okLyric(lyric){
let one = lyric.split('\n')
one.forEach((lyr,index) => {
let three = lyr.split(']')
if(three[1] == '' || three[1] == undefined){
return true
}
this.word.push(three[1])//歌词获取完毕
//获取每句歌词对应的时间
let two = '' || three[0].match(/\[(\d+:\d+.\d+)/)[1]
let m = parseInt(two.split(':')[0]) * 60 * 1000//分钟
let s = parseInt(two.split(':')[1].split('.')[0] * 1000)//秒
let ss = parseInt(two.split(':')[1].split('.')[1])//毫秒
let T = m + s +ss
this.time.push(T)//歌词对应时间转换获取完毕
})
},
getInfo(id) {
let song = this.url + '?type=song&id=' + id + '';
let lyric = this.url + '?type=lyric&id=' + id + '';
let detail = this.url + '?type=detail&id=' + id + '';
let comments = this.url + '?type=comments&id=' + id + '';
let s = axios.get(song)
let l = axios.get(lyric)
let d = axios.get(detail)
let c = axios.get(comments)
Promise.all([s, l, d, c])
.then(res => {
let list = res.map((el, index) => {
return el.data
})
this.lists.push(list)
if (this.ids.length == this.lists.length) {
this.info = this.lists.map(list => {
let obj = {};
obj.name = list[2].songs[0].name
obj.singer = list[2].songs[0].ar[0].name
obj.music_url = list[0].data[0].url
obj.lyric = list[1].lrc.lyric
obj.cover_url = list[2].songs[0].al.picUrl
obj.alia = list[2].songs[0].alia[0] || ""
obj.al_name = list[2].songs[0].al.name
return obj
})
}
})
.catch(err => console.log(err))
}
}
})
</script>
</body>
</html>