效果
能播放暂停,这个进度条是借助了 ant design 的Slider 滑动输入条,自己也实现了一个 横向滑动进度条,点击或者拖动都可以改变进度。样式可自行修改。
一、实现思路
写过原生音乐播放器的大概都能知道需要用到audio的那些属性,主要是在界面上将一个audio 隐藏然后通过js来控制音乐的一些操作
template部分
<audio controls ref="audio" style="display: none">
<source src="../assets/music.mp3"/>
</audio>
<div class="container">
<div class="control">
<div>
<img :src="bf" v-if="dataVal.isListen" @click="changePause"/> <img :src="zt" v-else @click="changePause"/>
</div>
<div style="height: 200px;display: flex;flex-direction: column;align-items: center">
<div>{{ dataVal.voice }}%</div>
<a-slider vertical v-model:value="dataVal.voice" :tip-formatter="null" @change="voiceChange"/>
<img :src="jingyin_icon" v-if="dataVal.voice === 0" @click="changeVoiceBtn"/>
<img :src="yinliang_icon" v-else @click="changeVoiceBtn"/>
</div>
</div>
<a-slider v-model:value="percentage" :tip-formatter="null" @change="progressChange"/>
<div>{{ (dataVal.total * percentage / 100).toFixed(2) }}s/{{ dataVal.total }}s</div>
</div>
二、横向进度条部分
封装成了一个组件,可变参数如下
const props = defineProps({
// 背景色
bgColor: {
type: String,
default: '#f0f0f0'
},
// 前景色
activeColor: {
type: String,
default: '#4caf50'
},
//当前进度
percentage: {
type: Number,
default: 0
}
})
三、横向进度条组件完整代码
<template>
<div class="progress-bar" @click="clickProgress" ref="progressBar" :style="{background:props.bgColor}">
<div class="progress" :style="{ width: progressWidth,background:props.activeColor }"></div>
<div class="progress-handle" @mousedown="startDrag" :style="{ background:props.activeColor }">{{ progress }}</div>
</div>
</template>
<script setup>
import {ref, computed, watch, getCurrentInstance} from 'vue';
const {proxy} = getCurrentInstance()
const props = defineProps({
// 背景色
bgColor: {
type: String,
default: '#f0f0f0'
},
// 前景色
activeColor: {
type: String,
default: '#4caf50'
},
//当前进度
percentage: {
type: Number,
default: 0
}
})
const progress = ref(0);
const progressBar = ref(null);
let dragging = false;
let dragStartX = 0;
watch(
() => [progress, props.percentage],
([progress, percentage]) => {
if (percentage) {
// progress.value = percentage.toFixed(2)
}
},
{deep: true}
)
const updateProgress = (clientX) => {
const progressBarRect = progressBar.value.getBoundingClientRect();
const progressWidthPercentage = ((clientX - progressBarRect.left) / progressBarRect.width) * 100;
progress.value = Math.max(0, Math.min(progressWidthPercentage, 100)).toFixed(2);
proxy.$emit('valueChange', progress.value)
};
const startDrag = (event) => {
dragging = true;
dragStartX = event.clientX;
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', endDrag);
};
const handleDrag = (event) => {
if (dragging) {
updateProgress(event.clientX);
}
};
const endDrag = () => {
dragging = false;
document.removeEventListener('mousemove', handleDrag);
document.removeEventListener('mouseup', endDrag);
};
const clickProgress = (event) => {
updateProgress(event.clientX);
};
const progressWidth = computed(() => {
return progress.value + '%';
});
</script>
<style scoped>
.progress-bar {
width: 99%;
height: 20px;
border-radius: 10px;
display: flex;
justify-content: start;
position: relative;
cursor: pointer;
}
.progress {
height: 100%;
border-radius: 10px;
transition: width 0.2s;
}
.progress-handle {
width: 40px;
height: 40px;
border-radius: 50%;
margin-top: -10px;
margin-left: -10px;
line-height: 40px;
color: #fff;
text-align: center;
}
</style>
四、自定义audio完整代码
<script setup>
import bf from '../assets/bf.png'
import zt from '../assets/zt.png'
import jingyin_icon from '../assets/jingyin_icon.png'
import yinliang_icon from '../assets/yinliang_icon.png'
import Progress from './progress.vue'
import {reactive, ref, onMounted, getCurrentInstance, onActivated, watch} from 'vue'
const dataVal = reactive({
isListen: false,
positionY: 0,
voice: 100,
total: 0.00,
})
const audio = ref()
const percentage = ref(0)
//播放还是暂停
let timer = null
const changePause = () => {
if (dataVal.isListen) {
audio.value.pause(); //暂停
} else {
audio.value.play(); //播放
if (timer) {
clearTimeout(timer)
} else {
setInterval(() => {
percentage.value = (audio.value.currentTime / audio.value.duration * 100).toFixed(2)
}, 100)
}
}
dataVal.isListen = !dataVal.isListen
dataVal.total = audio.value.duration.toFixed(2) //总时间
}
const progressChange = (val) => {
dataVal.total = audio.value.duration.toFixed(2) //总时间
audio.value.currentTime = audio.value.duration * val / 100
audio.value.play(); //播放.
dataVal.isListen = true
}
const voiceChange = (val) => {
audio.value.volume = val / 100
}
const changeVoiceBtn = () => {
dataVal.voice = dataVal.voice > 0 ? 0 : 100
audio.value.volume = dataVal.voice / 100
}
</script>
<template>
<audio controls ref="audio" style="display: none">
<source src="../assets/music.mp3"/>
</audio>
<div class="container">
<div class="control">
<div>
<img :src="bf" v-if="dataVal.isListen" @click="changePause"/> <img :src="zt" v-else @click="changePause"/>
</div>
<div style="height: 200px;display: flex;flex-direction: column;align-items: center">
<div>{{ dataVal.voice }}%</div>
<a-slider vertical v-model:value="dataVal.voice" :tip-formatter="null" @change="voiceChange"/>
<img :src="jingyin_icon" v-if="dataVal.voice === 0" @click="changeVoiceBtn"/>
<img :src="yinliang_icon" v-else @click="changeVoiceBtn"/>
</div>
</div>
<a-slider v-model:value="percentage" :tip-formatter="null" @change="progressChange"/>
<div>{{ (dataVal.total * percentage / 100).toFixed(2) }}s/{{ dataVal.total }}s</div>
</div>
<!--<Progress style="margin-top: 100px" @valueChange="progressChange" :percentage="percentage" activeColor="#3F50F2" bgColor="#77737d"/>-->
</template>
<style scoped>
.container {
background: pink;
width: 80%;
height: 400px;
margin: 0 auto;
padding: 30px;
}
.control {
display: flex;
justify-content: space-between;
align-items: flex-end;
height: 80%;
}
</style>