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.png"
           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>

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: