Vue3实现简易音乐播放器组件

前言

用Vue3实现一个简易的音乐播放器组件

其效果图如下所示:

实现这个组件需要提前做的准备:

引入ElementUI

引入字节跳动图标库

一张唱见图片

将要播放的音乐上传到文件服务器上,并提供一个能在线访问的链接【这里使用的是阿里云的OSS服务】

准备

ElementUI

ElementUI的引入可以参照其官网的引入方式;

字节跳动图标库

组件的【上一首】【播放】【下一首】【音量】等图标都是来源自这个图标库,这是其安装文档

在main.js中,我是这样引入的:

//引入字节跳动图标库 import {install} from '@icon-park/vue-next/es/all'; import '@icon-park/vue-next/styles/index.css'; ...... //这种加载方式进行加载的话,代表使用默认的前缀进行加载:icon //也就是说假如要使用一个主页图标,使用图标时标签该这么写:  //<icon-home theme="outline" size="24" fill="#FFFFFF" :strokeWidth="2"/> //install(app,'prefix') 用这种方式进行加载的话,可以自定义使用图标库时的标签前缀 install(app)

唱见图片

音乐源

将要播放的音乐放到文件服务器上,我这里是使用阿里云的OSS服务进行音乐文件的存储,然后在整个页面加载时【也就是在onMounted生命周期函数中获取这些数据源】。在后面的代码中,这一步体现在:

//初始化歌曲源 const initMusicArr = () => {         requests.get("/Music/QueryAllMusic").then(function (res) {             musicState.musicArr = res             musicState.musicCount = res.length         })     }     onMounted(() => {         initMusicArr()             ......     }) 完整代码 <template>   <!--音乐播放器-->   <div class="music-container" :class="{'music-active-switch': offsetThreshold}">     <div class="music-disk">       <!--唱片图片-->       <img class="music-disk-picture" :class="{'music-disk-playing-style': playState}" src="./images/R-C.webp"            alt="">     </div>     <!--进度条-->     <div class="music-slider">       <el-slider           v-model="playTime"           :format-tooltip="tooltipFormat"           size="small"           :max="sliderLength"           @change="changePlayTime"/>     </div>     <!--按钮组-->     <div class="button-group">       <!--上一曲 按钮-->       <button class="play-button" @click="lastButtonClick">         <icon-go-start theme="outline" size="23" fill="#939393" :strokeWidth="3" strokeLinejoin="miter"                        strokeLinecap="butt"/>       </button>       <!--播放 按钮-->       <button class="play-button" @click="playButtonClick">         <icon-play-one v-if="!playState" theme="outline" size="23" fill="#939393" :strokeWidth="3"                        strokeLinejoin="miter" strokeLinecap="butt"/>         <icon-pause v-if="playState" theme="outline" size="23" fill="#939393" :strokeWidth="3"                     strokeLinejoin="miter" strokeLinecap="butt"/>       </button>       <!--下一曲 按钮-->       <button class="play-button" @click="nextButtonClick">         <icon-go-end theme="outline" size="23" fill="#939393" :strokeWidth="3" strokeLinejoin="miter"                      strokeLinecap="butt"/>       </button>       <!--音量按钮-->       <div class="voice-container">         <button class="voice-button" @click="voiceButtonClick">           <icon-volume-notice v-if="!voiceMute" theme="outline" size="23" fill="#939393" :strokeWidth="3"                               strokeLinejoin="miter" strokeLinecap="butt"/>           <icon-volume-mute v-if="voiceMute" theme="outline" size="23" fill="#939393" :strokeWidth="3"                             strokeLinejoin="miter" strokeLinecap="butt"/>         </button>         <div class="voice-slider">           <el-slider               v-model="voicePower"               :max="1"               :step="0.1"               size="small"               @change="changeVoicePower"/>         </div>       </div>     </div>     <audio         ref="musicAudio"         class="audio-component"         controls         preload="auto"         @canplay="changeDuration">       <source ref="musicSource" type="audio/mpeg"/>     </audio>   </div> </template> <script> import {computed, onMounted, onUnmounted, reactive, ref, watch} from "vue"; //这里是自己封装的axios请求,可以将这里替换成自己的请求逻辑 import requests from "@/api/ajax"; export default {   name: "index",   setup() {     //是否正在播放     const playState = ref(false);     //现在的播放时间     const playTime = ref(0.00);     //歌曲的时间长度     const playDuration = ref(0.00);     //进度条长度     const sliderLength = ref(100);     //歌曲URL     const musicUrl = ref("");     //播放器标签     const musicAudio = ref(null);     //实现音乐播放的标签     const musicSource = ref(null);     //是否静音     const voiceMute = ref(false);     //音量大小     const voicePower = ref(0.5);     const musicState = reactive({       musicArr: [],       musicCount: 0     })     const musicCursor = ref(0);     //页面偏移量     const pageOffset = ref(0)     //是否达到阈值,达到阈值就显示播放器,反之     const offsetThreshold = ref(false)     //激活播放器     const operateMusicPlayer = () => {       pageOffset.value = window.scrollY       //当页面滚动偏移达到800,激活用户框       if (pageOffset.value > 800) {         offsetThreshold.value = true       } else {         //反之         offsetThreshold.value = false       }     }     //播放按钮点击回调     const playButtonClick = () => {       if (playState.value) {         musicAudio.value.pause()       } else {         musicAudio.value.play()       }       //修改播放时间【设置这个,当一首歌正常播放结束之后,再次点击播放按钮,进度条会得到重置】       playTime.value = musicAudio.value.currentTime       //重新设置播放状态       playState.value = !playState.value     }     //上一曲按钮点击回调     const lastButtonClick = () => {       musicCursor.value -= 1       changeMusic()     }     //下一曲按钮点击回调     const nextButtonClick = () => {       musicCursor.value += 1       changeMusic()     }     //歌曲进度条文本提示     const tooltipFormat = (val) => {       let strTime = playTime.value       let strMinute = parseInt(strTime / 60 + '')       let strSecond = parseInt(strTime % 60 + '')       return strMinute + ":" + strSecond     }     //当歌曲能播放时【亦即在canplay钩子函数中】,musicAudio.value.duration才不会是NaN,才能进行歌曲长度的设置     const changeDuration = () => {       if (playDuration.value != musicAudio.value.duration) {         //修改进度条的最大值         sliderLength.value = musicAudio.value.duration         //修改歌曲播放时间         playDuration.value = musicAudio.value.duration       }     }     //el-slider的钩子函数,拖动进度条时快进歌曲,改变当前播放进度     const changePlayTime = (val) => {       musicAudio.value.currentTime = val     }     //音量按钮点击回调     const voiceButtonClick = () => {       voiceMute.value = !voiceMute.value       if (!voiceMute.value) {         voicePower.value = 1         musicAudio.value.volume = 1       } else {         voicePower.value = 0         musicAudio.value.volume = 0       }     }     //el-slider的钩子函数,用于调节音量     const changeVoicePower = (val) => {       musicAudio.value.volume = val       voicePower.value = val       if (val > 0) {         voiceMute.value = false       } else {         voiceMute.value = true       }     }     //播放状态下,进度条里的数值每秒递增。而Audio因为在播放状态下,currentTime会自己递增,所以不用处理     const updatePlayTimePerSecond = () => {       if (playState.value) {         playTime.value += 1         if (playTime.value >= playDuration.value) {           //代表当前歌曲已经播放完毕,进行切歌           musicCursor.value++           changeMusic()         }       }     }     //切歌     const changeMusic = () => {       //切歌【这里的music_url是后端返回给前端的json字符串中,用于存储歌曲在线链接的属性名是:music_url,所以要实现自己的请求逻辑,将这里的music_url改为自己的即可】       musicSource.value.src = musicState.musicArr[musicCursor.value % musicState.musicCount].music_url       // 当刷新了url之后,需要执行load方法才能播放这个音乐       musicAudio.value.load()       playTime.value = musicAudio.value.currentTime       sliderLength.value = musicAudio.value.duration       musicAudio.value.play()       playState.value = true     }     //初始化歌曲源【将这里替换成自己的请求逻辑】     const initMusicArr = () => {       requests.get("/Music/QueryAllMusic").then(function (res) {         musicState.musicArr = res         musicState.musicCount = res.length       })     }     onMounted(() => {       initMusicArr()       //播放状态下,使播放进度自增1,以与Audio内置的currentTime相匹配       setInterval(updatePlayTimePerSecond, 1000)       //添加滚动事件       window.addEventListener("scroll", operateMusicPlayer)     })     onUnmounted(() => {       window.removeEventListener("scroll", operateMusicPlayer)     })     return {       musicAudio,       musicSource,       playState,       playTime,       playDuration,       sliderLength,       musicUrl,       voiceMute,       voicePower,       musicState,       musicCursor,       pageOffset,       offsetThreshold,       playButtonClick,       lastButtonClick,       nextButtonClick,       voiceButtonClick,       tooltipFormat,       changeMusic,       changeDuration,       changePlayTime,       changeVoicePower,       updatePlayTimePerSecond,       initMusicArr     }   }, } </script> <style scoped> .music-container {   position: fixed;   justify-content: center;   width: 280px;   height: 110px;   background-color: white;   border-radius: 15px;   bottom: 15px;   left: 10px;   opacity: 0;   transition: 0.5s; } .music-disk {   position: absolute;   width: 90px;   height: 90px;   left: 15px;   top: 10px;   border-radius: 50%; } .music-disk-picture {   width: 90px;   height: 90px;   border-radius: 50%;   /*设置图片不可点击*/   pointer-events: none; } .music-disk-playing-style {   animation: music-disk-rotate 5s linear infinite; } @keyframes music-disk-rotate {   0% {     transform: rotate(0deg);   }   100% {     transform: rotate(360deg);   } } .button-group {   position: absolute;   width: 330px;   height: 38px;   left: 90px;   bottom: 13px;   margin-left: 10px; } .button-group > button {   margin-left: 10px; } .play-button {   float: left;   width: 31px;   height: 31px;   padding: 4px;   /*margin: 0px;*/   border: 0px;   border-radius: 50%;   margin: 7px 0px 0px 0px; } .voice-button {   float: left;   width: 31px;   height: 31px;   padding: 0px;   /*margin: 0px;*/   border: 0px;   border-radius: 50%;   margin: 7px 0px 0px 0px;   background-color: transparent; } .music-slider {   position: absolute;   top: 20px;   left: 120px;   width: 50%; } .voice-container {   float: left;   margin-left: 12px;   width: 31px;   height: 38px;   overflow: hidden !important;   transition: 0.5s; } .voice-container:hover {   width: 160px; } .voice-slider {   position: relative;   top: 2px;   right: -30px;   width: 90px;   height: 35px;   background-color: white;   border-radius: 10px;   padding: 0px 15px 0px 15px;   transition: 0.2s; } .audio-component {   width: 300px;   height: 200px;   top: 100px;   display: none; } .music-active-switch{   opacity: 1; } </style>

推荐阅读