前言
用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>