歌詞同步播放滾動,拖動,點擊dome

bandicam-2020-04-11-10-08-54-759.gif
調用接口: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>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章