Vue3實(shí)現(xiàn)簡(jiǎn)易音樂播放器組件
前言
用Vue3實(shí)現(xiàn)一個(gè)簡(jiǎn)易的音樂播放器組件
其效果圖如下所示:

實(shí)現(xiàn)這個(gè)組件需要提前做的準(zhǔn)備:
- 引入ElementUI
- 引入字節(jié)跳動(dòng)圖標(biāo)庫(kù)
- 一張唱見圖片
- 將要播放的音樂上傳到文件服務(wù)器上,并提供一個(gè)能在線訪問的鏈接【這里使用的是阿里云的OSS服務(wù)】
準(zhǔn)備
ElementUI
ElementUI的引入可以參照其官網(wǎng)的引入方式;
字節(jié)跳動(dòng)圖標(biāo)庫(kù)
組件的【上一首】【播放】【下一首】【音量】等圖標(biāo)都是來(lái)源自這個(gè)圖標(biāo)庫(kù),這是其安裝文檔
在main.js中,我是這樣引入的:
//引入字節(jié)跳動(dòng)圖標(biāo)庫(kù)
import {install} from '@icon-park/vue-next/es/all';
import '@icon-park/vue-next/styles/index.css';
......
//這種加載方式進(jìn)行加載的話,代表使用默認(rèn)的前綴進(jìn)行加載:icon
//也就是說(shuō)假如要使用一個(gè)主頁(yè)圖標(biāo),使用圖標(biāo)時(shí)標(biāo)簽該這么寫:?
//<icon-home theme="outline" size="24" fill="#FFFFFF" :strokeWidth="2"/>
//install(app,'prefix') 用這種方式進(jìn)行加載的話,可以自定義使用圖標(biāo)庫(kù)時(shí)的標(biāo)簽前綴
install(app)唱見圖片

音樂源
將要播放的音樂放到文件服務(wù)器上,我這里是使用阿里云的OSS服務(wù)進(jìn)行音樂文件的存儲(chǔ),然后在整個(gè)頁(yè)面加載時(shí)【也就是在onMounted生命周期函數(shù)中獲取這些數(shù)據(jù)源】。在后面的代碼中,這一步體現(xiàn)在:
//初始化歌曲源
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.png"
? ? ? ? ? ?alt="">
? ? </div>
? ? <!--進(jìn)度條-->
? ? <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請(qǐng)求,可以將這里替換成自己的請(qǐng)求邏輯
import requests from "@/api/ajax";
export default {
? name: "index",
? setup() {
? ? //是否正在播放
? ? const playState = ref(false);
? ? //現(xiàn)在的播放時(shí)間
? ? const playTime = ref(0.00);
? ? //歌曲的時(shí)間長(zhǎng)度
? ? const playDuration = ref(0.00);
? ? //進(jìn)度條長(zhǎng)度
? ? const sliderLength = ref(100);
? ? //歌曲URL
? ? const musicUrl = ref("");
? ? //播放器標(biāo)簽
? ? const musicAudio = ref(null);
? ? //實(shí)現(xiàn)音樂播放的標(biāo)簽
? ? const musicSource = ref(null);
? ? //是否靜音
? ? const voiceMute = ref(false);
? ? //音量大小
? ? const voicePower = ref(0.5);
? ? const musicState = reactive({
? ? ? musicArr: [],
? ? ? musicCount: 0
? ? })
? ? const musicCursor = ref(0);
? ? //頁(yè)面偏移量
? ? const pageOffset = ref(0)
? ? //是否達(dá)到閾值,達(dá)到閾值就顯示播放器,反之
? ? const offsetThreshold = ref(false)
? ? //激活播放器
? ? const operateMusicPlayer = () => {
? ? ? pageOffset.value = window.scrollY
? ? ? //當(dāng)頁(yè)面滾動(dòng)偏移達(dá)到800,激活用戶框
? ? ? if (pageOffset.value > 800) {
? ? ? ? offsetThreshold.value = true
? ? ? } else {
? ? ? ? //反之
? ? ? ? offsetThreshold.value = false
? ? ? }
? ? }
? ? //播放按鈕點(diǎn)擊回調(diào)
? ? const playButtonClick = () => {
? ? ? if (playState.value) {
? ? ? ? musicAudio.value.pause()
? ? ? } else {
? ? ? ? musicAudio.value.play()
? ? ? }
? ? ? //修改播放時(shí)間【設(shè)置這個(gè),當(dāng)一首歌正常播放結(jié)束之后,再次點(diǎn)擊播放按鈕,進(jìn)度條會(huì)得到重置】
? ? ? playTime.value = musicAudio.value.currentTime
? ? ? //重新設(shè)置播放狀態(tài)
? ? ? playState.value = !playState.value
? ? }
? ? //上一曲按鈕點(diǎn)擊回調(diào)
? ? const lastButtonClick = () => {
? ? ? musicCursor.value -= 1
? ? ? changeMusic()
? ? }
? ? //下一曲按鈕點(diǎn)擊回調(diào)
? ? const nextButtonClick = () => {
? ? ? musicCursor.value += 1
? ? ? changeMusic()
? ? }
? ? //歌曲進(jìn)度條文本提示
? ? const tooltipFormat = (val) => {
? ? ? let strTime = playTime.value
? ? ? let strMinute = parseInt(strTime / 60 + '')
? ? ? let strSecond = parseInt(strTime % 60 + '')
? ? ? return strMinute + ":" + strSecond
? ? }
? ? //當(dāng)歌曲能播放時(shí)【亦即在canplay鉤子函數(shù)中】,musicAudio.value.duration才不會(huì)是NaN,才能進(jìn)行歌曲長(zhǎng)度的設(shè)置
? ? const changeDuration = () => {
? ? ? if (playDuration.value != musicAudio.value.duration) {
? ? ? ? //修改進(jìn)度條的最大值
? ? ? ? sliderLength.value = musicAudio.value.duration
? ? ? ? //修改歌曲播放時(shí)間
? ? ? ? playDuration.value = musicAudio.value.duration
? ? ? }
? ? }
? ? //el-slider的鉤子函數(shù),拖動(dòng)進(jìn)度條時(shí)快進(jìn)歌曲,改變當(dāng)前播放進(jìn)度
? ? const changePlayTime = (val) => {
? ? ? musicAudio.value.currentTime = val
? ? }
? ? //音量按鈕點(diǎn)擊回調(diào)
? ? 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的鉤子函數(shù),用于調(diào)節(jié)音量
? ? const changeVoicePower = (val) => {
? ? ? musicAudio.value.volume = val
? ? ? voicePower.value = val
? ? ? if (val > 0) {
? ? ? ? voiceMute.value = false
? ? ? } else {
? ? ? ? voiceMute.value = true
? ? ? }
? ? }
? ? //播放狀態(tài)下,進(jìn)度條里的數(shù)值每秒遞增。而Audio因?yàn)樵诓シ艩顟B(tài)下,currentTime會(huì)自己遞增,所以不用處理
? ? const updatePlayTimePerSecond = () => {
? ? ? if (playState.value) {
? ? ? ? playTime.value += 1
? ? ? ? if (playTime.value >= playDuration.value) {
? ? ? ? ? //代表當(dāng)前歌曲已經(jīng)播放完畢,進(jìn)行切歌
? ? ? ? ? musicCursor.value++
? ? ? ? ? changeMusic()
? ? ? ? }
? ? ? }
? ? }
? ? //切歌
? ? const changeMusic = () => {
? ? ? //切歌【這里的music_url是后端返回給前端的json字符串中,用于存儲(chǔ)歌曲在線鏈接的屬性名是:music_url,所以要實(shí)現(xiàn)自己的請(qǐng)求邏輯,將這里的music_url改為自己的即可】
? ? ? musicSource.value.src = musicState.musicArr[musicCursor.value % musicState.musicCount].music_url
? ? ? // 當(dāng)刷新了url之后,需要執(zhí)行l(wèi)oad方法才能播放這個(gè)音樂
? ? ? musicAudio.value.load()
? ? ? playTime.value = musicAudio.value.currentTime
? ? ? sliderLength.value = musicAudio.value.duration
? ? ? musicAudio.value.play()
? ? ? playState.value = true
? ? }
? ? //初始化歌曲源【將這里替換成自己的請(qǐng)求邏輯】
? ? const initMusicArr = () => {
? ? ? requests.get("/Music/QueryAllMusic").then(function (res) {
? ? ? ? musicState.musicArr = res
? ? ? ? musicState.musicCount = res.length
? ? ? })
? ? }
? ? onMounted(() => {
? ? ? initMusicArr()
? ? ? //播放狀態(tài)下,使播放進(jìn)度自增1,以與Audio內(nèi)置的currentTime相匹配
? ? ? setInterval(updatePlayTimePerSecond, 1000)
? ? ? //添加滾動(dòng)事件
? ? ? 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%;
? /*設(shè)置圖片不可點(diǎn)擊*/
? 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>以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue3-treeselect數(shù)據(jù)綁定失敗的解決方案
這篇文章主要介紹了vue3-treeselect數(shù)據(jù)綁定失敗的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
vue+elementUI組件遞歸實(shí)現(xiàn)可折疊動(dòng)態(tài)渲染多級(jí)側(cè)邊欄導(dǎo)航
這篇文章主要介紹了vue+elementUI組件遞歸實(shí)現(xiàn)可折疊動(dòng)態(tài)渲染多級(jí)側(cè)邊欄導(dǎo)航,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Vue中mintui的field實(shí)現(xiàn)blur和focus事件的方法
今天小編就為大家分享一篇Vue中mintui的field實(shí)現(xiàn)blur和focus事件的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
淺析vue如何實(shí)現(xiàn)手機(jī)橫屏功能
在項(xiàng)目開發(fā)中有時(shí)候需求需要手動(dòng)實(shí)現(xiàn)橫屏功能,所以這篇文章主要為大家詳細(xì)介紹了如何使用Vue實(shí)現(xiàn)手機(jī)橫屏功能,需要的小伙伴可以參考一下2024-03-03

