Vue | 原生audio样式不好看,自己写一个简易的音乐播放控件,实现播放暂停可拖动
最近遇到一个需求,需要写一个简单的音频控件:往控件传入音频url实现播放、暂停、允许拖动、显示时间等功能,由于原生audio标签样式不太好看,所以决定自己写一个。效果如下:思路把这个需求分成几个部分:左边的播放暂停按钮、右边上面的文字显示、下面的进度条。可以把audio作为原始控件(但不在页面中显示),用自己写的控件来控制audio标签的行为动作。1.暂停、播放按钮控制audio的play()与p
最近遇到一个需求,需要写一个简单的音频控件:往控件传入音频url实现播放、暂停、允许拖动、显示时间等功能,由于原生audio
标签样式不太好看,所以决定自己写一个。效果如下:
思路
把这个需求分成几个部分:左边的播放暂停按钮、右边上面的文字显示、下面的进度条。
可以把audio
作为原始控件(但不在页面中显示),用自己写的控件来控制audio
标签的行为动作。
1.暂停、播放按钮控制audio的play()
与pause()
。
2.进度条,这里可以用range
来实现,修改range
的样式来达到上图的效果,通过onInput
和onChange
来监控range
拖动的变化,进而控制audio的变化(播放到指定位置)。
3.文件名字样以及右边的时间显示可以通过audio
标签的duration
和currentTime
属性来控制。
实现
vue项目中使用了
fontawesome图表库
,可以根据实际自己替换。
html
html结构分为左边的图标跟右边的文字跟range
控件,基本结构也比较简单。
html中必须有一个
audio
标签,将这个标签隐藏即可,然后用过自己写的控件来控制audio
。
<audio style="display: none" controls></audio>
CSS
这里主要是对range
控件的样式设置,通过input[type="range"]
控制进度条的样式,通过input[type='range']::-webkit-slider-thumb
控制滑块的样式。
input[type='range'] {
outline: none;
-webkit-appearance: none; /*清除系统默认样式*/
width: 100% !important;
background: -webkit-linear-gradient(#10a9ff, #10a9ff) no-repeat, #dddddd; /*背景颜色,俩个颜色分别对应上下*/
background-size: 0% 100%; /*设置左右宽度比例,这里可以设置为拖动条属性*/
height: 2px; /*横条的高度,细的真的比较好看嗯*/
}
/*拖动块的样式*/
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none; /*清除系统默认样式*/
height: 10px; /*拖动块高度*/
width: 3px; /*拖动块宽度*/
background: #10a9ff; /*拖动块背景*/
}
逻辑
大体思路是:由两个icon和range控制audio,由audio控制文字的显示。
1.播放暂停图标控制audio标签播放。
audio
元素有pause()
和play()
控制音频的暂停和播放。因此,可以对icon绑定事件控制audio
的播放暂停。
<span class="icon" v-if="isPlay == false" @click="play">
<i class="fa fa-play play-icon" aria-hidden="true"></i>
</span>
<span class="icon" v-if="isPlay == true" @click="pause">
<i class="fa fa-pause pause-icon" aria-hidden="true"></i>
</span>
// 控制音乐播放
play() {
const audio = this.$refs.audio;
audio.play();
this.isPlay = true;
},
// 控制音乐暂停
pause() {
const audio = this.$refs.audio;
audio.pause();
this.isPlay = false;
}
isPlay
用来控制显示哪一个icon。
2.rang控制audio元素的时间进度(range拖动的时候)。
rang
实际上是一个input
标签,因此有onChange
和onInput
两个回调事件监控range相关参数的变化。audio
元素有一个currentTime
属性可以设置audio
标签进度(即到达指定时间),可以通过监控range
的变化,实时更改audio
的currentTime
来实现拖动改变音频进度。
<input type="range" ref="range" @input="onChange" @change="onChange" min="0" max="360" value="0">
注:这里将range分成了360份,后面的计算均和360相关,当然可以根据自己的需要设置。
// range--拖动进度条得到的回调
onChange() {
let value = this.$refs.range.value;
// 控制进度条样式
const persentage = ((value / 360) * 100).toFixed(1) + '%';
this.$refs.range.style.backgroundSize = `${persentage} 100%`; // 拖动的时候,需要跟着改变进度条样式
// 控制音频播放
const timeToGo = (value / 360) * this.totalTime;
const audio = this.$refs.audio;
audio.currentTime = timeToGo;
}
3.audio播放的时候,控制range也跟着动。
这个发生在range控件不被人为拖动时,需要跟着audio变化(包括样式和滑块的位置)。
audio
标签有方法ontimeupdate
来监控audio
变化,每次audio
的进度变化的时候,就会执行回调。因此,可以通过绑定一个事件来实现range
跟着audio
变化。其中,range
滑块的位置可以通过设置range
的属性值value
来改变,value
的值在min
与max
之间。
<audio style="display: none" :src="audioURL" ref="audio" controls @timeupdate="update"></audio>
// audio--进度变化的时候的回调--改变文字
update() {
const audio = this.$refs.audio;
const currentTime = audio.currentTime; // 当前播放时间
this.currentTime = currentTime;
// 改变进度条的值
const range = this.$refs.range;
range.value = ((this.currentTime / this.totalTime) * 360).toFixed(1);
// 进度条的值改变的时候,颜色也跟着变化
const persentage = ((this.currentTime / this.totalTime) * 100).toFixed(1) + '%';
this.$refs.range.style.backgroundSize = `${persentage} 100%`;
}
4.文件名的获取。
由于需求是传入音频url的音频播放控件,因此,可以对传入的url进行分析。
一般URL的格式是https://xxx/xxx/xxx/xxxx.mp3
,可以基于/
对URL分解得到数组,获取最后一个元素即为文件名。
// 获取文件名称
getFilename(url) {
const arr = url.split('/');
return arr[arr.length - 1];
},
5.当前时间与总时间的显示。
audio
标签有duration
和currentTime
属性来获取当前音频播放进度以及总时间(单位是秒)。获取到当前时间以及播放总时间后,进行格式处理之后就可以绑定到相关的位置上。
<div class="words flex-between">
<div class="name">{{ audioName==null ? "未知": audioName}}</div>
<div class="time">{{ formatCurrentTime }} / {{ formatTotalTime }}</div>
</div>
formatCurrentTime
和formatTotalTime
是由currentTime
和totalTime
通过计算属性返回的,下面只分析currentTime
和totalTime
的获取。
// audio--进度变化的时候的回调--改变文字
update() {
const audio = this.$refs.audio
const currentTime = audio.currentTime // 当前播放时间
this.currentTime = currentTime
},
总时间在音频加载完毕后就进行记录,通过绑定audio
的onCanplay
回调来实现。
<audio style="display: none" :src="audioURL" ref="audio" controls @timeupdate="update" @canplay="loadingFinish"></audio>
loadingFinish() {
const totalTime = this.$refs.audio.duration
this.totalTime = totalTime
}
6.prop设置。
传入一个音频URL,用于显示文件名以及作为audio
的音源链接。
最后
当然,样式可以更"花里胡哨" 。主要是了解了audio
标签的onUpdatetime()
回调、onCanplay()
回调;range
的value
属性、onChange()
回调、onInput()
回调。功能也可以进一步扩展,后面再添加吧~
完整代码
<template>
<div class="container">
<div class="audio-container">
<div class="left">
<span class="icon" v-if="isPlay == false" @click="play">
<i class="fa fa-play play-icon" aria-hidden="true"></i>
</span>
<span class="icon" v-if="isPlay == true" @click="pause">
<i class="fa fa-pause pause-icon" aria-hidden="true"></i>
</span>
</div>
<div class="right">
<div class="words flex-between">
<div class="name">{{ audioName==null ? "未知": audioName}}</div>
<div class="time">{{ formatCurrentTime }} / {{ formatTotalTime }}</div>
</div>
<div class="duration">
<input type="range" ref="range" @input="onChange" @change="onChange" min="0" max="360" value="0">
</div>
</div>
</div>
<audio style="display: none" :src="audioURL" ref="audio" controls @timeupdate="update" @canplay="loadingFinish"></audio>
</div>
</template>
<script>
export default {
name: 'AudioPlayer',
components: {},
props: {
audioURL: {
type: String,
default: '../未知',
},
},
data() {
return {
isPlay: false, // 控制icon切换
totalTime: 0, // 播放总时间--秒
currentTime: 0, // 当前播放时间--秒
}
},
computed: {
formatTotalTime() {
return this.formatTime(this.totalTime)
},
formatCurrentTime() {
return this.formatTime(this.currentTime)
},
// 音频名称
audioName() {
return this.getFilename(this.audioURL)
},
},
mounted() {
this.$refs.audio.src = this.audioURL
// 将range位置归0--如果不使用这个的话,设置了value之后没有用= =郁闷
setTimeout(() => {
this.$refs.range.value = 0
}, 1)
},
methods: {
// 控制音乐播放
play() {
const audio = this.$refs.audio
audio.play()
this.isPlay = true
},
// 控制音乐暂停
pause() {
const audio = this.$refs.audio
audio.pause()
this.isPlay = false
},
// 音乐缓存完毕,获取时间
loadingFinish() {
const totalTime = this.$refs.audio.duration
this.totalTime = totalTime
},
// range--拖动进度条得到的回调
onChange() {
let value = this.$refs.range.value
const persentage = ((value / 360) * 100).toFixed(1) + '%'
this.$refs.range.style.backgroundSize = `${persentage} 100%`
// 控制音频播放
const timeToGo = (value / 360) * this.totalTime
const audio = this.$refs.audio
audio.currentTime = timeToGo
},
// audio--进度变化的时候的回调--改变文字
update() {
const audio = this.$refs.audio
const currentTime = audio.currentTime // 当前播放时间
this.currentTime = currentTime
// 改变进度条的值
const range = this.$refs.range
range.value = ((this.currentTime / this.totalTime) * 360).toFixed(1)
// 进度条的值改变的时候,颜色也跟着变化
const persentage = ((this.currentTime / this.totalTime) * 100).toFixed(1) + '%'
this.$refs.range.style.backgroundSize = `${persentage} 100%`
},
//辅助函数,将秒变成分秒的形式--用在计算属性中
formatTime(value) {
let second = 0
let minute = 0
minute = parseInt(value / 60)
second = parseInt(value % 60)
// 补0
minute = minute < 10 ? '0' + minute : minute
second = second < 10 ? '0' + second : second
return minute + ':' + second
},
// 通过url获取filename
getFilename(url) {
const arr = url.split('/')
return arr[arr.length - 1]
},
},
}
</script>
<style scoped>
.audio-container {
padding: 8px 16px;
width: 100%;
background: #f5f6f8;
border-radius: 2px;
display: flex;
}
.left {
margin-right: 16px;
}
// 播放暂停按钮
.icon {
display: inline-block;
width: 28px;
height: 28px;
border: 2px solid #10a9ff;
border-radius: 50%;
text-align: center;
font-size: 16px;
line-height: 28px;
color: $base-color-blue;
}
.icon:hover {
cursor: pointer;
}
.play-icon {
position: relative;
left: 2px;
}
.flex-between {
display: flex;
justify-content: space-between;
align-content: center;
}
.right {
flex: 1;
}
.words {
margin-bottom: -1px;
}
.name {
font-size: 14px;
color: #333333;
line-height: 14px;
}
.time {
font-size: 14px;
color: #666666;
line-height: 14px;
}
// 控件
input[type='range'] {
outline: none;
-webkit-appearance: none; /*清除系统默认样式*/
width: 100% !important;
background: -webkit-linear-gradient(#10a9ff, #10a9ff) no-repeat, #dddddd; /*背景颜色,俩个颜色分别对应上下*/
background-size: 0% 100%; /*设置左右宽度比例,这里可以设置为拖动条属性*/
height: 2px; /*横条的高度,细的真的比较好看嗯*/
}
/*拖动块的样式*/
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none; /*清除系统默认样式*/
height: 10px; /*拖动块高度*/
width: 3px; /*拖动块宽度*/
background: #10a9ff; /*拖动块背景*/
}
</style>
更多推荐
所有评论(0)