歌词同步播放滚动,拖动,点击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>

发布了117 篇原创文章 · 获赞 146 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/printf_hello/article/details/105448010