基于Vue2实现歌曲播放和歌词滚动效果
作者:愉快的小跳蛙
这篇文章主要介绍了如何基于Vue2实现歌曲播放和歌词滚动效果,文中通过代码示例和图文讲解的非常详细,对大家的学习或工作有一定的帮助,感兴趣的小伙伴可以自己动手试一下
需求:需要实现歌词滚动效果。
思路:通过js+css的transform属性完成。
难点:主要就是需要知道正在播放的歌词是那句,然后对正在播放的歌词进行变色和放大,最难的就是让高亮歌词随着歌曲播放滚动。
1.先看效果图
2.处理歌词格式(项目中有后端兄弟实现转换的可以省略)
// 处理歌词格式 parseLrc(musicLrc) { const lines = musicLrc?.split(`\n`); let lrcList = lines.map((line) => { let [time, words] = line?.split("]") ?? [null, null]; return { time: this.parseTime(time.substring(1)) || null, words: words || null, }; }); let lrcListRes = lrcList.filter((v) => { return v.name !== null && v.words !== null; }); // this.computingTime(); return lrcListRes; }, parseTime(t) { const part = t?.split(":"); return Number(part[0] * 60) + Number(part[1]); },
歌词格式一般都是数组对象,对象的key各位可以自己根据需要命名,主要是思路。思路最重要!思路最重要!思路最重要!
我这里的数据格式如下图
3.利用audio的timeupdate的事件来进行计算歌词是否高亮以及偏移量
// 添加audio事件 autoDown() { const that = this; var audio = document.getElementById("myAudio"); audio.addEventListener("ended", function () { that.switchingBtn("down"); }); audio.addEventListener("timeupdate", function () { that.computingTime(); }); }, // 计算播放时间对应的下标 computingTime() { let arr = this.selectedFiles[this.playIndex].lrcList || []; let currentTime = document.getElementById("myAudio")?.currentTime || 0; let index = arr.findIndex((e) => currentTime < e.time) - 1; this.currentIndex = index >= 0 ? index : arr.length - 1; }, // 计算偏移量(保证高亮的歌词在中间) computingOffset(index) { // 外部大盒子高度 let musicLrcBoxHeight = this.$refs.musicLrc?.clientHeight; // 歌词总高度 let musicLrcHeight = this.$refs.musicLrc_bady?.clientHeight; // 每个li高度 let musicLrcLiHeight = this.$refs.musicLrc_item?.clientHeight || 22; // 歌词偏移高度 let offsetHenght = index * musicLrcLiHeight + musicLrcLiHeight / 2 - musicLrcBoxHeight / 2; // 最大偏移高度 let offsetMax = musicLrcHeight - musicLrcBoxHeight + 10; if (offsetHenght < 0) { offsetHenght = 0; } // if (offsetHenght > offsetMax) { // offsetHenght = offsetMax; // } this.$refs.musicLrc_bady.style.transform = `translateY(-${offsetHenght}px)`; },
注意:这里的computingTime和computingOffset两个事件是核心代码!!!
4.整个demo源码
<template> <div class="h100 dis_sb bs"> <div class="music bg-fff bs"> <div class="p10 bs mb10" style="height: 40px"> <el-button type="text" size="small" @click="triggerFileInput"> 选择歌曲 </el-button> <span class="f12 ml10">请先选择本地音乐!!!</span> <input ref="audioInput" style="display: none; height: 10px" type="file" @change="handleFileSelect" multiple accept="audio/*" /> </div> <div class="bs p10" v-if="selectedFiles?.length > 0" style="height: 60px"> <audio id="myAudio" class="audio" controls :src="fileUrl || selectedFiles[0]?.url" autoplay /> <div class="tac bs" style="line-height: 20px"> <el-button type="text" size="small" @click="switchingBtn('up')"> 上一曲 </el-button> <el-button type="text" size="small" @click="togglePlay"> {{ playing ? "暂停" : "播放" }} </el-button> <el-button type="text" size="small" @click="switchingBtn('down')"> 下一曲 </el-button> </div> </div> <div v-if="selectedFiles?.length > 0" class="p10 music_body"> <ul class="main bs mt20"> <li :class="[index == playIndex ? 'main_item_action li' : 'li']" v-for="(file, index) in selectedFiles" :key="index" > <p @click="choose(file, index)">{{ file.name }}</p> </li> </ul> </div> </div> <div class="bs h100 p10 musicLrc" ref="musicLrc"> <ul class="musicLrc_bady tac f14" ref="musicLrc_bady"> <li v-for="(v, i) in selectedFiles[playIndex]?.lrcList" :key="i" ref="musicLrc_item" > <p :class="[i == currentIndex ? 'musicLrc_action ' : '']"> {{ v.words }} </p> </li> <div v-if="selectedFiles[playIndex]?.lrcList?.length == 0" class="tac bs" style="margin: auto; padding-top: 30px; color: #ccc" > 暂无歌词 </div> </ul> </div> </div> </template> <script> import musicList from "./musicList.js"; export default { data() { return { selectedFiles: [], lrcList: [], fileUrl: null, playing: false, isPlay: false, playIndex: 0, currentTime: 0, currentIndex: null, }; }, created() { musicList.forEach((e) => { if (e.lrc) { e.lrcList = this.parseLrc(e.lrc); } else { e.lrcList = []; } }); this.selectedFiles = JSON.parse(JSON.stringify(musicList)); }, watch: { selectedFiles: { handler(newVal, oldVal) { if (newVal?.length > 4) { this.$nextTick(() => { this.fileUrl = newVal[0]?.url; this.playing = true; this.playIndex = 0; }); } }, deep: true, }, $route: { handler(newVal, oldVal) { if (newVal?.path !== "/music") { this.$nextTick(() => { this.playing = false; }); } }, deep: true, }, currentIndex: { handler(newVal, oldVal) { if (newVal > 0) { setTimeout(() => { this.computingOffset(newVal); }, 150); } else { this.computingOffset(0); } }, deep: true, }, }, mounted() { this.$nextTick(() => { this.autoDown(); }); }, methods: { // 添加本地音乐 handleFileSelect(event) { const files = event.target.files; this.selectedFiles = JSON.parse(JSON.stringify(musicList)); this.fileUrl = null; for (let i = 0; i < files?.length; i++) { const file = files[i]; const reader = new FileReader(); reader.onload = (e) => { const fileObj = { name: file.name, url: e.target.result, // lrc: null, lrcList: [], }; this.selectedFiles.push(fileObj); }; reader.readAsDataURL(file); } }, triggerFileInput() { this.$refs.audioInput.click(); }, // 选择播放歌曲 choose(v, i) { const that = this; that.playing = true; that.fileUrl = that.selectedFiles[i]?.url; that.playIndex = i; }, // 判断播放状态 togglePlay() { const that = this; var audio = document.getElementById("myAudio"); if (audio.paused) { audio.play(); that.playing = true; } else { audio.pause(); that.playing = false; } }, // 添加audio事件 autoDown() { const that = this; var audio = document.getElementById("myAudio"); audio.addEventListener("ended", function () { that.switchingBtn("down"); }); audio.addEventListener("timeupdate", function () { that.computingTime(); }); }, // 播放歌曲 playAudio() { var audio = document.getElementById("myAudio"); audio.play(); this.currentIndex = 0; }, // 切换歌曲 switchingBtn(v) { const that = this; that.playing = true; const length = this.selectedFiles?.length || 0; if (v === "down") { this.playIndex = (this.playIndex + 1) % length; } else { this.playIndex = (this.playIndex - 1 + length) % length; } that.fileUrl = that.selectedFiles[that.playIndex]?.url; setTimeout(() => { that.playAudio(); }, 150); }, // 处理歌词格式 parseLrc(musicLrc) { const lines = musicLrc?.split(`\n`); let lrcList = lines.map((line) => { let [time, words] = line?.split("]") ?? [null, null]; return { time: this.parseTime(time.substring(1)) || null, words: words || null, }; }); let lrcListRes = lrcList.filter((v) => { return v.name !== null && v.words !== null; }); // this.computingTime(); return lrcListRes; }, parseTime(t) { const part = t?.split(":"); return Number(part[0] * 60) + Number(part[1]); }, // 计算播放时间对应的下标 computingTime() { let arr = this.selectedFiles[this.playIndex].lrcList || []; let currentTime = document.getElementById("myAudio")?.currentTime || 0; let index = arr.findIndex((e) => currentTime < e.time) - 1; this.currentIndex = index >= 0 ? index : arr.length - 1; }, // 计算偏移量(保证高亮的歌词在中间) computingOffset(index) { // 外部大盒子高度 let musicLrcBoxHeight = this.$refs.musicLrc?.clientHeight; // 歌词总高度 let musicLrcHeight = this.$refs.musicLrc_bady?.clientHeight; // 每个li高度 let musicLrcLiHeight = this.$refs.musicLrc_item?.clientHeight || 22; // 歌词偏移高度 let offsetHenght = index * musicLrcLiHeight + musicLrcLiHeight / 2 - musicLrcBoxHeight / 2; // 最大偏移高度 let offsetMax = musicLrcHeight - musicLrcBoxHeight + 10; if (offsetHenght < 0) { offsetHenght = 0; } // if (offsetHenght > offsetMax) { // offsetHenght = offsetMax; // } this.$refs.musicLrc_bady.style.transform = `translateY(-${offsetHenght}px)`; }, }, }; </script> <style scoped> .music { width: calc(50% - 5px); height: 100%; border-radius: 10px; } #myAudio { width: 100%; height: 30px; } .music_body { height: calc(100% - 126px); overflow-x: hidden; overflow-y: auto; } .main { .li { border: 1px solid #eee; border-radius: 5px; padding: 0 10px; margin-bottom: 10px; box-sizing: border-box; line-height: 30px; font-size: 12px; cursor: grab; color: #666; } .main_item_action { border: 1px solid #409eff; color: #409eff; } } .musicLrc { width: calc(50% - 5px); height: 100%; background-color: rgb(0, 0, 0); border-radius: 10px; color: #ccc; line-height: 22px; /* overflow: hidden; */ transform: translateY(); overflow-x: hidden; overflow-y: auto; } .musicLrc_bady { li { transition: 0.8s; } } .musicLrc_action { transform: scale(1.5); color: #409eff; } </style>
以上就是基于Vue2实现歌曲播放和歌词滚动效果的详细内容,更多关于Vue2歌曲播放和歌词滚动的资料请关注脚本之家其它相关文章!