vue3+ts+vant制作音乐播放器(进度条拖拽、倍速切换、上一曲、下一曲)完整版

2023-03-14,,

1、进度条的用的是vant的Progress组件,比手写进度条方便很多,有自带的事件

2、H5页面兼容pc

效果展示

上代码

一、template模块

<template lang="pug">
.audioPlay
main
.audioBox
.imgBox
van-image.songImg(
width="4.6rem",
height="4.6rem",
fit="cover",
style="border-radius: 5px; overflow: hidden",
:src="require('@/assets/images/media/song.png')"
)
.titBox 文稿
.titText {{ title }}
.audioControl
audio(
:src="audioSrc",
@canplay="getDuration",
@timeupdate="updateTime",
v-show="false",
controls,
ref="audio"
)
van-slider.audioSlider(
v-model="sliderValue",
@update:model-value="sliderOnChange",
active-color="#D8BE98",
inactive-color="#E0E0E0",
:disabled="isSlide > 0 ? false : true"
)
template(#button)
.custom-button {{ currentDuration }}/{{ duration }}
ul.handleUl
li.handleLi(@click="handleBack")
van-image.handleImg(
width="0.44rem",
height="0.44rem",
fit="cover",
:src="require('@/assets/images/media/houtui.png')"
)
li.handleLi(@click="prevPlay(isPlayNum)")
van-image.handleImg(
width="0.44rem",
height="0.44rem",
fit="cover",
:src="isPlayNum > 1 ? require('@/assets/images/media/prevL.png') : require('@/assets/images/media/prev.png')"
)
li.handleLi(@click="handlePauseOrPlay")
van-image.handleImg(
width="0.98rem",
height="0.98rem",
fit="cover",
:src="paused ? require('@/assets/images/media/play.png') : require('@/assets/images/media/stop.png')"
)
li.handleLi(@click="nextPlay(isPlayNum)")
van-image.handleImg(
width="0.44rem",
height="0.44rem",
fit="cover",
:src="isPlayNum < catalogArray.length ? require('@/assets/images/media/nextL.png') : require('@/assets/images/media/next.png')"
)
li.handleLi(@click="handleForward")
van-image.handleImg(
width="0.44rem",
height="0.44rem",
fit="cover",
:src="require('@/assets/images/media/qianjin.png')"
)
ul.funUl
li.funLi(@click="sheetShowCli('mulu', '选集')")
van-image.funImg(
width="0.54rem",
height="0.54rem",
fit="cover",
:src="require('@/assets/images/media/mulu.png')"
)
p.funtext 目录
li.funLi(@click="sheetShowCli('beisu', '倍速')")
van-image.funImg(
width="0.54rem",
height="0.54rem",
fit="cover",
:src="require('@/assets/images/media/beisu.png')"
)
p.funtext 倍速
li.funLi(
@click="sheetShowCli('pinglun', '评论')"
)
van-image.funImg(
width="0.54rem",
height="0.54rem",
fit="cover",
:src="require('@/assets/images/media/pinglun.png')"
)
p.funtext 评论
li.funLi
van-image.funImg(
width="0.54rem",
height="0.54rem",
fit="cover",
:src="require('@/assets/images/media/shoucang.png')"
)
p.funtext 收藏
li.funLi
van-image.funImg(
width="0.54rem",
height="0.54rem",
fit="cover",
:src="require('@/assets/images/media/dianzan.png')"
)
p.funtext 点赞
van-action-sheet.audioSheet(v-model:show="sheetShow", :title="sheetTit")
.sheetCon
.multipW(v-if="sheetConActive == 'beisu'")
.multipInfo(
v-for="(item, index) in multipleArray",
:key="index",
@click="multipSelect(item.num, index)"
)
.multipText(:class="item.isSelected ? 'isSelect' : ''") {{ item.text }}
van-icon.multipIcon(
:name="require('@/assets/images/media/isSelected.png')",
size="0.7rem",
v-if="item.isSelected"
)
.catalogW(v-if="sheetConActive == 'mulu'")
.catalogInfo(
v-for="(item, index) in catalogArray",
:key="index",
@click="catalogSelect(index)"
)
.catalogCon(:class="item.isPlay ? 'isPlay' : ''")
.con_box
.con_boxTit {{ index + 1 }}.{{ item.title }}
.con_boxDesc {{ item.desc }}
.con_boxIcon
van-icon.timeIcon(name="clock-o", size="0.2rem")
span.timeSpan {{ item.audioDurat }}
van-image.con_Img(
width="0.6rem",
height="0.6rem",
fit="cover",
:src="item.isPlay ? require('@/assets/images/media/playing.gif') : require('@/assets/images/media/playMl.png')"
)
.reviewW(v-if="sheetConActive == 'pinglun'")
.reviewInfo 评论内容
</template>

二、ts部分

<script lang="ts">
import {
defineComponent,
onBeforeMount,
reactive,
ref,
toRefs,
} from "vue"; interface control {
audioUrl: string;
play: boolean;
} export default defineComponent({
name: "audioPlay",
setup() {
const audioControl: control = reactive({ audioUrl: "", play: false });
onBeforeMount(() => {
audioInfo.audioSrc = (catalogArray as any).value[0].audioSrc;
audioInfo.title = (catalogArray as any).value[0].title;
audioInfo.duration = (catalogArray as any).value[0].audioDurat;
setTimeout(() => {
audioInfo.isSlide = audio.value.duration;
console.log("audio.value.duration22", typeof audio.value.duration);
// alert(audio.value.duration);
}, 100);
}); //暂停播放
const handlePlayer = (): void => {
audioControl.play = !audioControl.play;
audioControl.play
? (audio.value as any).play()
: (audio.value as any).pause();
}; const audioInfo = reactive({
audioSrc: "",
backSecond: 15, //后退秒数
forwardSecond: 15, //前进秒数
duration: "00:00", //音频总时长
currentDuration: "00:00", //音频当前播放时长
title: "",
paused: true,
isPlayNum: 1, //上下集用到-正在播放的第几集
isSlide: 0, //判断滑块是否可以滑动
});
const audio = ref();
const sliderValue = ref();
//后退
const handleBack = (): void => {
if (audio.value.currentTime > audioInfo.backSecond) {
audio.value.currentTime =
audio.value.currentTime - audioInfo.backSecond;
}
};
//前进
const handleForward = (): void => {
if (
audio.value.duration - audio.value.currentTime >
audioInfo.forwardSecond
) {
audio.value.currentTime =
audio.value.currentTime + audioInfo.forwardSecond;
}
};
//暂停或播放
const handlePauseOrPlay = (): void => {
console.log("audio.value.duration22", typeof audio.value.duration);
setTimeout(() => {
audio.value.paused ? audio.value.play() : audio.value.pause();
audioInfo.paused = !audioInfo.paused;
}, 200);
};
//视频在可以播放时触发
const getDuration = (): void => {
setTimeout(() => {
(audioInfo.duration as any) = timeFormat(audio.value.duration);
}, 200);
};
//将单位为秒的的时间转换成mm:ss的形式
const timeFormat = (number: Number) => {
let minute = parseInt((<number>number / 60) as any);
let second = parseInt((<number>number % 60) as any);
(minute as any) = minute >= 10 ? minute : "0" + minute;
(second as any) = second >= 10 ? second : "0" + second;
return minute + ":" + second;
};
//进度条发生变化时触发
const updateTime = (): void => {
audioInfo.currentDuration = timeFormat(audio.value.currentTime);
sliderValue.value = (
(audio.value.currentTime * 100) /
audio.value.duration
).toFixed(3);
audioInfo.isSlide = audio.value.duration;
// 播放完毕按钮变回
if (audioInfo.currentDuration == audioInfo.duration) {
audioInfo.paused = true;
}
};
//滑动进度条
const sliderOnChange = (value: any): void => {
console.log("value", timeFormat((audio.value.duration * value) / 100));
// 设置播放时间
audioInfo.currentDuration = timeFormat(
(audio.value.duration * value) / 100
);
audio.value.currentTime = parseInt(
((audio.value.duration * value) / 100) as any
);
};
// 点击右侧功能
const sheetShow = ref(false);
const sheetTit = ref("");
const sheetConActive = ref("");
const multipleArray = ref([
{ num: 0.75, text: "0.75X", isSelected: false },
{ num: 1, text: "1.0X(正常倍速)", isSelected: true },
{ num: 1.25, text: "1.25X", isSelected: false },
{ num: 1.5, text: "1.5X", isSelected: false },
{ num: 2, text: "2X", isSelected: false },
]);
const catalogArray = ref([
{
title: "音频播放器一曲",
desc: "第一站《大宪章》纪念碑1/4",
audioSrc: require("@/assets/images/media/audio/music1.mp3"),
audioDurat: "04:07",
isPlay: true,
},
{
title: "测试二未过时的未过时仍未未过时的未过时过时的未过时的未过",
desc: "第一站测试二未过时的未过时仍未未过时的未过时过时的未过时的",
audioSrc: require("@/assets/images/media/audio/music2.mp3"),
audioDurat: "02:06",
isPlay: false,
},
{
title: "测试三过时未过时的未过时",
desc: "第一站测试三未过时的未过时仍未未过时的未过时过时的未过时的/4",
audioSrc: require("@/assets/images/media/audio/music3.mp3"),
audioDurat: "04:56",
isPlay: false,
},
]); // 下方功能唤起面板
const sheetShowCli = (val: any, tit: any): void => {
sheetConActive.value = val;
sheetTit.value = tit;
sheetShow.value = true;
};
// 倍速选择
const multipSelect = (num: Number, index: number): void => {
audio.value.playbackRate = num;
multipleArray.value.forEach((item: any) => {
item.isSelected = false;
});
multipleArray.value[index].isSelected = true;
sheetShow.value = false;
};
// 选集功能
const catalogSelect = (index: number): void => {
catalogArray.value.forEach((item: any) => {
item.isPlay = false;
});
sliderValue.value = 0;
audio.value.currentTime = 0;
audioInfo.paused = true;
audioInfo.audioSrc = (catalogArray as any).value[index].audioSrc;
audioInfo.title = (catalogArray as any).value[index].title;
audioInfo.currentDuration = "00:00";
audioInfo.duration = (catalogArray as any).value[index].audioDurat;
catalogArray.value[index].isPlay = true;
sheetShow.value = false;
audioInfo.isPlayNum = index + 1;
handlePauseOrPlay();
};
// 上一集
const prevPlay = (num: any): void => {
if (num > 1) {
catalogArray.value.forEach((item: any) => {
item.isPlay = false;
});
sliderValue.value = 0;
audio.value.currentTime = 0;
audioInfo.paused = true;
audioInfo.audioSrc = (catalogArray as any).value[num - 2].audioSrc;
audioInfo.title = (catalogArray as any).value[num - 2].title;
audioInfo.currentDuration = "00:00";
audioInfo.duration = (catalogArray as any).value[num - 2].audioDurat;
catalogArray.value[num - 2].isPlay = true;
audioInfo.isPlayNum = num - 1;
sheetShow.value = false;
handlePauseOrPlay();
}
};
// 下一集
const nextPlay = (num: any): void => {
if (num < catalogArray.value.length) {
catalogArray.value.forEach((item: any) => {
item.isPlay = false;
});
sliderValue.value = 0;
audio.value.currentTime = 0;
audioInfo.paused = true;
audioInfo.audioSrc = (catalogArray as any).value[num].audioSrc;
audioInfo.title = (catalogArray as any).value[num].title;
audioInfo.currentDuration = "00:00";
audioInfo.duration = (catalogArray as any).value[num].audioDurat;
catalogArray.value[num].isPlay = true;
audioInfo.isPlayNum = num + 1;
sheetShow.value = false;
handlePauseOrPlay();
}
};
return {
...toRefs(audioControl),
handlePlayer, ...toRefs(audioInfo),
handleBack,
handleForward,
handlePauseOrPlay,
getDuration,
updateTime,
audio,
sliderValue,
sliderOnChange,
sheetShow,
multipleArray,
sheetShowCli,
sheetTit,
sheetConActive,
multipSelect,
catalogArray,
catalogSelect,
prevPlay,
nextPlay,
};
},
});
</script>

三、样式部分

<style lang="scss">
.audioSlider {
.van-slider__bar {
z-index: 1111;
}
&.van-slider--disabled {
opacity: 1;
}
}
.isPlay {
.con_Img {
.van-image__img {
width: 0.4rem;
height: 0.4rem;
}
}
}
</style>
<style lang="scss" scoped>
.audioPlay {
height: 100vh;
display: flex;
flex-direction: column;
.content {
@include Padding(0.2rem, 0px);
@include Position(relative, 0, -0.9rem);
width: 100%;
}
main {
flex: 1;
overflow: auto;
.audioBox {
.imgBox {
width: 100%;
text-align: center;
margin-top: 1.1rem;
}
}
.titBox {
width: 1.8rem;
height: 0.6rem;
border-radius: 0.3rem;
border: 1px solid #d8be98;
text-align: center;
margin: 0.6rem auto 0;
font-size: 0.32rem;
line-height: 0.6rem;
color: #d8be98;
box-sizing: border-box;
}
.titText {
width: 80%;
margin: 0.3rem auto 0;
@include textEllipsis();
font-size: 0.36rem;
line-height: 0.4rem;
color: #333;
text-align: center;
}
.audioControl {
position: relative;
&::before {
position: absolute;
top: 0;
left: 0;
width: 50%;
content: "";
height: 2px;
background: #d8be98;
}
&::after {
position: absolute;
top: 0;
right: 0;
width: 50%;
content: "";
height: 2px;
background: #e0e0e0;
}
width: 90%;
position: absolute;
bottom: 0.6rem;
left: 50%;
transform: translateX(-50%);
.audioSlider {
width: 80%;
margin: 0 auto; .custom-button {
background: #d8be98;
padding: 0 0.14rem;
height: 0.36rem;
line-height: 0.4rem;
border-radius: 0.18rem;
font-size: 0.24rem;
color: #000;
transform: scale(0.9);
}
}
.handleUl {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0.1rem;
box-sizing: border-box;
margin-top: 0.4rem;
.handleLi {
line-height: 0;
}
}
}
.funUl {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.7rem;
.funLi {
line-height: 0; .funtext {
color: #999999;
font-size: 0.24rem;
line-height: 0.4rem;
}
}
}
}
.audioSheet {
.sheetCon {
border-top: 1px solid #dfdfdf;
.multipW {
padding: 0 0.32rem 1rem;
.multipInfo {
border-bottom: 1px solid #dfdfdf;
height: 0.86rem;
display: flex;
justify-content: space-between;
align-items: center;
.multipText {
color: #666666;
font-size: 0.32rem;
&.isSelect {
color: #caaa7c;
}
.multipIcon {
color: #caaa7c;
}
}
}
}
.catalogW {
padding: 0 0.32rem 0.6rem;
.catalogInfo {
border-bottom: 1px solid #e6e6e6;
.catalogCon {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.24rem 0.2rem 0.24rem 0;
box-sizing: border-box;
.con_box {
width: 80%;
.con_boxTit {
width: 100%;
@include textEllipsis();
font-size: 0.32rem;
color: #000000;
}
.con_boxDesc {
font-size: 0.28rem;
color: #999999;
@include textEllipsis();
margin-top: 0.1rem;
}
.con_boxIcon {
color: #cccccc;
font-size: 0.24rem;
margin-top: 0.2rem;
.timeIcon {
margin-right: 0.1rem;
}
}
}
&.isPlay {
.con_box {
.con_boxTit {
color: #caaa7c;
}
.con_boxDesc {
color: #e0ccb1;
}
.con_boxIcon {
color: #e0ccb1;
}
}
}
.con_Img {
background: #f7f1e8;
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
.reviewW {
padding: 0 0.32rem 0.6rem;
.reviewInfo {
border-bottom: 1px solid #e6e6e6;
padding: 0.4rem 0;
.reviewCon {
display: flex;
justify-content: space-between;
.con_box {
width: calc(100% - 0.78rem);
.con_boxTit {
font-size: 0.28rem;
color: #666666;
}
.con_boxTime {
color: #b3b3b3;
font-size: 0.24rem;
line-height: 0.4rem;
}
.con_boxText {
font-size: 0.32rem;
color: #333333;
line-height: 0.48rem;
}
.con_boxReply {
background: #f8f8f8;
width: 100%;
padding: 0.1rem 0.2rem 0.2rem;
box-sizing: border-box;
margin-top: 0.2rem;
.replyList {
display: flex;
font-size: 0.28rem;
line-height: 0.36rem;
margin-top: 0.1rem;
.replyName {
color: #666;
white-space: nowrap;
margin-right: 0.1rem;
}
.replyText {
color: #333;
}
}
}
}
}
}
}
}
}
@media only screen and (min-width: 750px) {
.audioPlay {
@include boxSize(750px, 100vh);
margin: 0 auto;
}
}
</style>

做个笔记

vue3+ts+vant制作音乐播放器(进度条拖拽、倍速切换、上一曲、下一曲)完整版的相关教程结束。

《vue3+ts+vant制作音乐播放器(进度条拖拽、倍速切换、上一曲、下一曲)完整版.doc》

下载本文的Word格式文档,以方便收藏与打印。