vue歌曲進(jìn)度條示例代碼
注意這個(gè)不是vue-cli創(chuàng)建的項(xiàng)目 是一個(gè)引用vue.js寫的html文件 ,直接粘到一個(gè)html文件就能用了,我的音樂鏈接隔一段時(shí)間會(huì)失效,需要自己準(zhǔn)備音樂
有拖動(dòng)和點(diǎn)擊切換播放進(jìn)度的功能
demo圖片

代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<audio ref="audioRef" autoplay @canplay='canplay' @timeupdate='update'></audio>
<button @click="play">播放</button>
<button @click="pause">暫停</button>
<div class="progress-wrapper">
<span class="time time-l">{{formatTime(currentTime)}}</span>
<div class="progress-bar-wrapper">
<cpn :progress=progress
@progress-changing="onProgressChanging"
@progress-changed='progressChanged'>
</cpn>
</div>
<span class="time time-l">{{formatTime(duration)}}</span>
</div>
</div>
<!-- 子組件 -->
<template id="myCpn">
<div class="progress-bar">
<!-- 后面黑色的一條 -->
<div class="bar-inner" @click="clickProgress">
<!-- 已經(jīng)播放的區(qū)域 -->
<div class="progress" :style='progressStyle' ref="progress">
</div>
<!-- btn -->
<div class="progress-btn-wrapper" :style='btnStyle' @touchstart.preventDefault='onTouchStart'
@touchmove.preventDefault='onTouchMove' @touchend.preventDefault='onTouchEnd' >
<div class="progress-btn"></div>
</div>
</div>
</div>
</template>
<script src="../../js/vue.js"></script>
<script>
audioEl = null
const progressBtnWidth = 16
// 子組件
const cpn = {
template: "#myCpn",
props: {
progress: {
type: Number,
default: 0
}
},
data() {
return {
offset: 0
}
},
mounted() {
},
created() {
this.touch = {}
},
computed: {
progressStyle() {
return `width: ${this.offset}px`
},
btnStyle() {
// console.log('fds');
return `transform: translate3d(${this.offset}px,0,0)`
},
},
watch: {
progress(newProgress) {
// 進(jìn)度條寬度
const barWidth = this.$el.clientWidth - progressBtnWidth
this.offset = barWidth * newProgress
}
},
methods: {
onTouchStart(e) {
// console.log(e);
this.touch.x1 = e.changedTouches[0].clientX
// 黃色進(jìn)度條初始寬度
this.touch.beginWidth = this.$refs.progress.clientWidth
console.log(this.touch);
},
onTouchMove(e) {
// console.log(e);
// x偏移量
const delta = e.changedTouches[0].clientX - this.touch.x1
// 之前的width+這次拖動(dòng)增加的偏移量=應(yīng)有的黃條長度
const tempWidth = this.touch.beginWidth + delta
// 再拿到barWidth
const barWidth = this.$el.clientWidth - progressBtnWidth
// 黃條長度/barwidth = progress 現(xiàn)在應(yīng)該有的進(jìn)度
const progress = tempWidth / barWidth
this.offset = barWidth * progress
this.$emit('progress-changing', progress)
// console.log("tempWidth", tempWidth);
// console.log("barWidth", barWidth);
// console.log("progress", progress);
},
onTouchEnd(e) {
// console.log(e);
const barWidth = this.$el.clientWidth - progressBtnWidth
const progress = this.$refs.progress.clientWidth / barWidth
this.$emit('progress-changed', progress)
},
// 點(diǎn)擊進(jìn)度條
clickProgress(e){
// console.log("fds");
console.log('getBoundingClientRect', this.$el.getBoundingClientRect());
const rect = this.$el.getBoundingClientRect()
// 黃條應(yīng)有的寬度
const offsetWidth = e.pageX - rect.x
const barWidth = this.$el.clientWidth - progressBtnWidth
const progress = offsetWidth/barWidth
this.$emit('progress-changed', progress)
console.log(offsetWidth)
}
},
}
const app = new Vue({
el: "#app",
data: {
content: 'fdasdf',
src: 'https://music.163.com/song/media/outer/url?id=1463165983.mp3',
currentTime: 0,
duration: 0,
isplay: false,
progressChanging : false
},
components: {
cpn
},
mounted() {
this.$nextTick(() => {
audioEl = this.$refs.audioRef
audioEl.src = this.src
// 默認(rèn)暫停
audioEl.pause()
})
},
computed: {
progress() {
return this.currentTime / this.duration
console.log("progress", this.currentTime / this.duration);
},
},
methods: {
play() {
audioEl.play()
this.isplay = true
},
pause() {
audioEl.pause()
this.isplay = false
// console.log();
},
canplay(e) {
// console.log(123456);
console.log(e);
this.duration = e.target.duration
},
update(e) {
if(!this.progressChanging){
this.currentTime = e.target.currentTime
}
},
onProgressChanging(e) {
// console.log("onProgressChanging", e);
this.progressChanging = true
// 實(shí)時(shí)修改currentTime值
this.currentTime = this.duration * e
},
progressChanged(e){
// console.log(e);
this.progressChanging = false
audioEl.currentTime = this.currentTime= this.duration * e
if(!this.isplay){
console.log("------");
audioEl.play()
}
},
formatTime(interval) {
// interval 向下取整
interval = interval | 0
// 不足兩位的話就向前填充一個(gè)0
let minute = ((interval / 60 | 0) + '')
let second = ((interval % 60 | 0) + '')
let len = minute.length
for (; len < 2; len++) {
minute = '0' + minute
}
len = second.length
for (; len < 2; len++) {
second = '0' + second
}
return `${minute}:${second}`
},
},
})
</script>
</body>
<style>
#app {
width: 100%;
}
.progress-wrapper {
display: flex;
width: 80%;
padding: 10px 0;
align-items: center;
margin: 0 auto;
}
.time {
width: 40px;
flex: 0 0 40px;
font-size: 8px;
margin: 0 auto;
padding: 0 8px;
}
.time-l {
text-align: left;
}
.time-l {
text-align: right;
}
.progress-bar-wrapper {
flex: 1;
}
/* 子組件樣式 */
.progress-bar {
height: 30px;
}
.bar-inner {
position: relative;
top: 11px;
height: 8px;
background-color: rgba(87, 82, 82, 0.062);
border-radius: 5px;
}
.progress {
position: absolute;
height: 100%;
background-color: rgb(238, 238, 136);
}
.progress-btn-wrapper {
position: absolute;
left: -8px;
top: -11px;
width: 30px;
height: 30px;
}
.progress-btn {
position: relative;
top: 7px;
left: 7px;
box-sizing: border-box;
width: 16px;
height: 16px;
border: 3px solid rgb(189, 189, 218);
border-radius: 50%;
background: rgb(123, 192, 212);
}
</style>
</html>
解說
https://developer.mozilla.org/zh-CN/docs/Web/API/TouchEvent
中間的進(jìn)度條是一個(gè)進(jìn)度條組件,一個(gè)黑色的背景是進(jìn)度的總長度,左側(cè)黃色的條是當(dāng)前播放的進(jìn)度,中間的滑塊是可以左右拖動(dòng)的,可以手動(dòng)改變進(jìn)度條,在播放的過程中,進(jìn)度條是會(huì)變長的,并且滑塊是向右偏移的,可以左右拖動(dòng)滑塊,拖動(dòng)也是改變了播放進(jìn)度,并且左側(cè)的時(shí)間是會(huì)發(fā)生變化的
來實(shí)現(xiàn)播放過程中,進(jìn)度條也會(huì)隨之播放 組件的狀態(tài)靠什么決定呢 可以靠進(jìn)度來決定,組件的任何狀態(tài)都可以根據(jù)進(jìn)度來決定,父組件傳入一個(gè)數(shù)字類型的progress
btn的位置,以及progress黃條的寬度都是根據(jù)progress計(jì)算而來的,寬度可以用一個(gè)數(shù)據(jù)offset來表示(定義個(gè)data),之后要監(jiān)聽progess,
https://cn.vuejs.org/v2/api/#vm-el
知識 獲取根 DOM 元素
watch: {
progress(newProgress) {
// 進(jìn)度條寬度
const barWidth = this.$el.clientWidth - progressBtnWidth
// 偏移量
this.offset = barWidth * newProgress
}
}
知識 當(dāng)然可以用computed,但是要注意用computed獲取el的寬度一開始肯定是獲取不到的,computed一開始上來就計(jì)算一次,在模板被渲染的時(shí)候就會(huì)訪問offset,然后就會(huì)計(jì)算一次el寬度,這時(shí)候組件還沒有mounted,是獲取不到的;watch的話,progress變化的時(shí)候其實(shí)已經(jīng)渲染了,所以clientWidth就可以拿到,另外,因?yàn)橹筮€要處理一些邏輯,更偏向邏輯的編寫,所以應(yīng)該用watch去實(shí)現(xiàn)
有了offset之后要去映射dom,給黃色進(jìn)度條和btn設(shè)置一個(gè)動(dòng)態(tài)的style,

他們兩個(gè)的style都是根據(jù)offset計(jì)算而來的,
computed: {
progressStyle(){
return `width: ${this.offset}px`
},
btnStyle() {
return `transform: translate3d(${this.offset}px,0,0)`
}
},
現(xiàn)在來根據(jù)offset來計(jì)算出它的樣式是怎么樣的 我們接受progress這個(gè)屬性,當(dāng)外部的progress變了之后,就根據(jù)progress計(jì)算出它的offset,有了偏移量,樣式就能發(fā)生變化,
疑問 flex 0 0 40px 與width 兩者效果是類似的,但是在某些場合下,flex布局會(huì)出現(xiàn)擠壓或塌陷的現(xiàn)象,導(dǎo)致寬度被擠壓,所以設(shè)定width可以保證我們的寬度不變化
這里是監(jiān)聽canplay事件

父組件計(jì)算屬性 播放進(jìn)度:已播放時(shí)間/總時(shí)間 總時(shí)間已經(jīng)拿到了,播放時(shí)間可以用一個(gè)事件:timeupdate來監(jiān)聽

現(xiàn)在的效果
可以看出來這是秒數(shù),需要格式化時(shí)間,定義一個(gè)工具函數(shù)
插播 函數(shù)柯里化 https://www.jianshu.com/p/2975c25e4d71 IIFE:自我執(zhí)行函數(shù) 柯里化
還有位運(yùn)算一些東西 https://www.jianshu.com/p/a3202bc3f7a4


一個(gè)疑問 xxx.yyy|0 為什么等于xxx 為什么這里或運(yùn)算符能有取整的作用呢
知識padstart方法
formatTime函數(shù)
formatTime(interval) {
// interval 向下取整
interval = interval | 0
// 不足兩位的話就向前填充一個(gè)0
const minute = ((interval / 60 | 0) + '').padstart(2, '0')
const second = ((interval % 60 | 0) + '').padstart(2, '0')
return `${minute}:${second}`
}
但是并不能用 它識別不了這個(gè)padstart方法
所以只能自己寫了
formatTime(interval) {
// interval 向下取整
interval = interval | 0
// 不足兩位的話就向前填充一個(gè)0
let minute = ((interval / 60 | 0) + '')
let second = ((interval % 60 | 0) + '')
let len = minute.length
for( ;len<2;len++){
minute='0'+minute
}
len = second.length
for( ;len<2;len++){
second='0'+second
}
return `${minute}:${second}`
}
接下來寫進(jìn)度條的交互邏輯
支持拖動(dòng)和點(diǎn)擊
在移動(dòng)端常見的就是ontouchstart ontouchmove ontouchend
https://developer.mozilla.org/zh-CN/docs/Web/API/TouchEvent
知識 prevent修飾符
給滑塊添加三個(gè)事件
methods: {
onTouchStart(e) {
console.log(e);
},
onTouchMove(e) {
console.log(e);
},
onTouchEnd(e) {
console.log(e);
}
},
需要獲取兩個(gè)信息,一個(gè)是要知道它點(diǎn)擊的位置,也就是說要知道他的橫坐標(biāo)是什么。以及左側(cè)進(jìn)度條的寬度(offset)
[screenX clientX pageX概念
因?yàn)闄M坐標(biāo)的位置在touchmove的時(shí)候也需要獲取,所以可以把數(shù)據(jù)綁定到一個(gè)可以被共享的對象上,可以在created鉤子函數(shù)中定義一個(gè)對象,
created() {
this.touch = {}
},
給黃條一個(gè)ref 之后
onTouchStart(e) {
// console.log(e);
this.touch.x1=e.changedTouches[0].clientX
// 黃色進(jìn)度條初始寬度
this.touch.beginWidth = this.$refs.progress.clientWidth
console.log(this.touch);
},
onTouchStart(e) {
// console.log(e);
this.touch.x1=e.changedTouches[0].clientX
// 黃色進(jìn)度條初始寬度
this.touch.beginWidth = this.$refs.progress.clientWidth
console.log(this.touch);
},
onTouchMove(e) {
// console.log(e);
// x偏移量
const delta = e.changedTouches[0].clientX-this.touch.x1
// 之前的width+這次拖動(dòng)增加的偏移量=應(yīng)有的黃條長度
const tempWidth = this.touch.beginWidth + delta
// 再拿到barWidth
const barWidth = this.$el.clientWidth - progressBtnWidth
// 黃條長度/barwidth = progress 現(xiàn)在應(yīng)該有的進(jìn)度
const progress = tempWidth/barWidth
this.offset = barWidth * progress
// console.log("tempWidth", tempWidth);
// console.log("barWidth", barWidth);
// console.log("progress", progress);
},
來整理一下,最終目的是要拿到offset,offset是由progress和barWidth共同決定的,這里progress怎么算呢需要拿到當(dāng)前黃條應(yīng)該的寬度除總寬度,黃條應(yīng)該的寬度就是一開始的寬度+這次滑動(dòng)的x距離,然后barWidth的獲取是簡單的,之后就可以算出來了
會(huì)不會(huì)覺得多此一舉呢 直接原來的黃條寬度+這次滑動(dòng)的長度不就可以了嗎 為什么還要算progress呢,因?yàn)橐屚獠恐?,歌曲的進(jìn)度發(fā)生了改變,要讓他們對應(yīng)上才可以,最終是要修改audio的,這個(gè)是用父組件做的,現(xiàn)在只是實(shí)現(xiàn)了拖動(dòng),所以需要派發(fā)事件,這里派發(fā)兩個(gè)自定義事件,一個(gè)progress-changing事件,表示手指還在拖動(dòng)的過程中,還沒有離開,當(dāng)手指離開的時(shí)候還要派發(fā)一個(gè)progress-change 把新的progress傳出去
實(shí)時(shí)修改currentTime的值

這是拖動(dòng)的時(shí)候修改currentTIme,修改音樂的時(shí)間是在手松開的時(shí)候,

但是我們暫停的時(shí)候發(fā)現(xiàn)是可以拖動(dòng)的,但是播放的時(shí)候拖動(dòng)發(fā)現(xiàn)是有問題的,
優(yōu)化:在change的時(shí)候,如果是暫停的效果就讓他播放,這時(shí)候就要定義一個(gè)isplay在點(diǎn)擊播放暫停的時(shí)候翻轉(zhuǎn)

現(xiàn)在來改bug,在播放的時(shí)候,拖動(dòng)進(jìn)度會(huì)出問題,為什么呢,監(jiān)聽progressChanging,我們修改了currentTime,這個(gè)currentTime一旦發(fā)生了改變,progress會(huì)根據(jù)currentTime做一個(gè)新的計(jì)算,然后傳給子組件,子組件他就會(huì)進(jìn)入到這個(gè)邏輯

offset就會(huì)重新做一次計(jì)算,
最后這里會(huì)覆蓋

應(yīng)該在update的時(shí)候需要做一些控制,在changing的過程加一個(gè)標(biāo)志位,

就是說在update函數(shù)中,如果changing在拖動(dòng)的過程中,不要去修改currentTime,在changing的過程中,就認(rèn)為是進(jìn)度條改變,他修改進(jìn)度條的優(yōu)先級高,自身播放導(dǎo)致的currentTime改變優(yōu)先級比較低,
這樣就ok了
除了拖動(dòng),我們還希望點(diǎn)擊它跳轉(zhuǎn)到對應(yīng)位置,
知識webapi --getBoundingClientRect 方法返回元素的大小及其相對于視口的位置(獲取短的那一條)。

用pagex獲取長的那一條
clickProgress(e){
// console.log("fds");
console.log('getBoundingClientRect', this.$el.getBoundingClientRect());
const rect = this.$el.getBoundingClientRect()
// 黃條應(yīng)有的寬度
const offsetWidth = e.pageX - rect.x
const barWidth = this.$el.clientWidth - progressBtnWidth
const progress = offsetWidth/barWidth
this.$emit('progress-changed', progress)
console.log(offsetWidth)
}
到此這篇關(guān)于vue歌曲進(jìn)度條demo的文章就介紹到這了,更多相關(guān)vue歌曲進(jìn)度條內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用 Vue cli 3.0 構(gòu)建自定義組件庫的方法
本文旨在給大家提供一種構(gòu)建一個(gè)完整 UI 庫腳手架的思路。通過實(shí)例代碼給大家講解了使用 Vue cli 3.0 構(gòu)建自定義組件庫的方法,感興趣的朋友跟隨小編一起看看吧2019-04-04
vue+element實(shí)現(xiàn)輸入密碼鎖屏
這篇文章主要為大家詳細(xì)介紹了vue+element實(shí)現(xiàn)輸入密碼鎖屏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Vue中使用v-print打印出現(xiàn)空白頁問題及解決
這篇文章主要介紹了Vue中使用v-print打印出現(xiàn)空白頁問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
vue router動(dòng)態(tài)路由下讓每個(gè)子路由都是獨(dú)立組件的解決方案
這篇文章主要介紹了vue router動(dòng)態(tài)路由下讓每個(gè)子路由都是獨(dú)立組件的解決方案,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-04-04
el-form-item?prop屬性動(dòng)態(tài)綁定不生效問題及解決
這篇文章主要介紹了el-form-item?prop屬性動(dòng)態(tài)綁定不生效問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
vue-router中的hash和history兩種模式的區(qū)別
大家都知道vue-router有兩種模式,hash模式和history模式,這里來談?wù)剉ue-router中的hash和history兩種模式的區(qū)別。感興趣的朋友一起看看吧2018-07-07

