調用接口: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>