工做中用到video標籤作視頻播放器,一開始用 video.js 插件代替,若是隻用這個插件進行簡單的播放視頻,是否是有點浪費,並且這個插件用 webpack 打包後 vendor.js 會很大,因此本文實現一個基於HTML5標籤video的自定義視頻播放器。其中實現了播放暫停、進度拖拽、音量控制及全屏等功能css
dom元素和css樣式編寫;vue
簡單的播放和暫停;node
時間進度顯示;webpack
控制欄視頻進度條點擊和拖拽;git
聲音進度條控制;github
全屏和退出全屏基礎實現;web
控制欄隱藏或顯示;bash
video自帶屬性和方法整理:dom
屬性或者方法 | 解釋 |
---|---|
currentTime | 當前視頻播放的時間,單位是s |
duration | 當前視頻播放總時長,單位是s |
volume | 聲音,最小值爲0,最大值爲1 |
paused | 暫停狀態 |
ended | 結束狀態 |
play() | 播放視頻 |
pause() | 暫停視頻播放 |
loadedmetadata() | 視頻加載獲取數據,這裏獲取duration |
timeupdate() | 視頻變化事件,這裏獲取實時的currentTime |
ended() | 視頻播放結束事件 |
volumechange() | 視頻聲音事件 |
本實例採用vue腳手架,具體dom元素實現以下,並添加相關注釋:ide
<div class="custom-video_container">
<!--video-->
<video
class="custom-video_video"
ref="custom-video"
>
<source type="video/mp4">
<p>設備不支持</p>
</video>
<!--播放或者暫停按鈕-->
<span class="custom-video_play custom-video_play-pause iconfont icon-zanting"></span>
<!-- 控制區域背景 -->
<div class="custom-video_control">
<!-- 視頻進度條 -->
<div class="custom-video_control-bg">
<div
class="custom-video_control-bg-outside"
ref="custom-video_control-bg-outside"
>
<span
class="custom-video_control-bg-inside"
ref="custom-video_control-bg-inside"
></span>
<span
class="custom-video_control-bg-inside-point"
ref="custom-video_control-bg-inside-point"
></span>
</div>
</div>
<!-- 聲音 -->
<div class="custom-video_control-voice">
<span class="custom-video_control-voice-play iconfont icon-shengyin"></span>
<div class="custom-video_control-voice-bg">
<div class="custom-video_control-voice-bg-outside">
<span class="custom-video_control-voice-bg-inside"></span>
<span class="custom-video_control-voice-bg-point"></span>
</div>
</div>
</div>
<!-- 時間 -->
<div class="custom-video_control-time">
<span>"00:00"</span>/<span>"00:00"</span>
</div>
<!-- 全屏縮放 -->
<span class="custom-video_control-full iconfont icon-quanping"></span>
</div>
</div>
複製代碼
與之對應css和完整的代碼會附加在文章的結尾。後面相關邏輯會依據上面的dom結構進行事件綁定和拓展。
播放或者暫停有兩種場景,第一種經過點擊「播放」或者「暫停」按鈕控制播放或者暫停;第二種是點擊視頻區域控制播放或者暫停。
第一種實現經過點擊「播放」按鈕,此按鈕的樣式變成「暫停」,修改上面註釋區域播放或者暫停按鈕元素
<!--播放或者暫停按鈕-->
<span
v-if="videoState.play"
class="custom-video_play custom-video_play-pause iconfont icon-zanting"
@click="pause('btn')"
>
</span>
<span
v-else
class="custom-video_play custom-video_play-play iconfont icon-bofang"
@click="play('btn')"
>
</span>
複製代碼
而且對應的js以下:
data() {
return {
videoState: {
play: false, //播放狀態
playState: false, // 記錄播放狀態
},
videoDom: null, // video
}
},
mounted() {
// 初始化相關元數據
this.videoDom = this.$refs["custom-video"]
},
methods: {
play(flag) { // 播放按鈕事件
if(flag) this.videoState.playState = true
this.videoState.play = true
this.videoDom.play()
},
pause(flag) { // 暫停按鈕事件
if(flag) this.videoState.playState = false
this.videoDom.pause()
this.videoState.play = false
},
}
複製代碼
點擊「播放」按鈕調用play()方法,videoState.play值變化引發按鈕樣式變成「暫停」,並調用video標籤自帶的播放方法,反之就是「暫停」。
第二種點擊屏幕播放或者暫停是經過監聽video click事件,代碼以下:
mounted() {
// 初始化相關元數據
this.videoDom = this.$refs["custom-video"]
this.initMedaData()
},
methods: {
initMedaData() { // 初始化video相關事件
this.videoDom.addEventListener("click", () => { // 點擊視頻區域能夠進行播放或者暫停
if(this.videoDom.paused || this.videoDom.ended) {
if(this.videoDom.ended) {
// 若是視頻結束,currentTime初始化爲0
this.videoDom.currentTime = 0
}
this.play('btn') //調用下面play的方法
} else {
this.pause('btn') //調用下面pause的方法
}
})
},
play(flag) { // 播放按鈕事件
if(flag) this.videoState.playState = true
this.videoState.play = true
this.videoDom.play()
},
pause(flag) { // 暫停按鈕事件
if(flag) this.videoState.playState = false
this.videoDom.pause()
this.videoState.play = false
},
}
複製代碼
圖片右下角「00:00 / 00:29」,它是當前的播放時間比上視頻總時長,video自帶屬性currentTime和duration,這兩個字段獲取的值單位都是s,因此要進行格式轉換,先修改對應的dom結構:
<!-- 時間 -->
<div class="custom-video_control-time">
<span>{{currentTime ? currentTime : "00:00"}}</span>
/
<span>{{duration ? duration : "00:00"}}</span>
</div>
複製代碼
相關js代碼以下:
data() {
return {
duration: 0, // 視頻總時長
currentTime: 0, // 視頻當前播放時長
}
},
mounted() {
// 初始化相關元數據
this.videoDom = this.$refs["custom-video"]
this.initMedaData()
},
methods: {
initMedaData() { // 初始化video相關事件
this.videoDom.addEventListener('loadedmetadata', () => { // 獲取視頻總時長
this.duration = this.timeTranslate(this.videoDom.duration)
})
},
this.videoDom.addEventListener("timeupdate", () => { // 監聽視頻播放過程當中的時間
this.currentTime = this.timeTranslate(this.videoDom.currentTime)
}),
timeTranslate(t) { // 時間轉化
let m = Math.floor(t / 60)
m < 10 && (m = '0' + m)
return m + ":" + (t % 60 / 100 ).toFixed(2).slice(-2)
},
}
複製代碼
data() {
return {
videoDom: null, // video
videoProOut: null, // 視頻總進度條
videoPro: null, // 視頻進度條
videoPoi: null, // 視頻進度點
}
},
mounted() {
// 初始化相關元數據
this.videoDom = this.$refs["custom-video"]
this.videoProOut = this.$refs['custom-video_control-bg-outside']
this.videoPro = this.$refs['custom-video_control-bg-inside']
this.videoPoi = this.$refs['custom-video_control-bg-inside-point']
this.initMedaData()
},
methods: {
initMedaData() { // 初始化video相關事件
this.videoDom.addEventListener('loadedmetadata', () => { // 獲取視頻總時長
this.duration = this.timeTranslate(this.videoDom.duration)
})
},
this.videoDom.addEventListener("timeupdate", () => { // 監聽視頻播放過程當中的時間
const percentage = 100 * this.videoDom.currentTime / this.videoDom.duration
// 頁面渲染進度
this.videoPro.style.width = percentage + '%'
this.videoPoi.style.left = percentage - 1 + '%'
})
}
複製代碼
<!-- 進度條 -->
<div
class="custom-video_control-bg"
@mousedown="handlePrograssDown"
@mousemove="handlePrograssMove"
@mouseup="handlePrograssUp"
>
<!--此處省略視頻進度條dom結構-->
</div>
複製代碼
拖拽或者點擊進度條首先計算進度條的起點水平距離,須要計算該點的偏移量,封裝偏移量getOffset方法以下(ps:貌似是zepto源碼片斷),
getOffset(node, offset) { // 獲取當前屏幕下進度條的左偏移量和上偏移量
if(!offset) {
offset = {}
offset.left = 0
offset.top = 0
}
if(node === document.body || node === null) {
return offset
}
offset.top += node.offsetTop
offset.left += node.offsetLeft
return this.getOffset(node.offsetParent, offset)
},
複製代碼
點擊邏輯以下:
handlePrograssDown(ev) { // 監聽點擊進度條事件,方便獲取初始點擊的位置
this.videoState.downState = true //按下鼠標標誌
this.pause() // 視頻暫時中止
this.videoState.distance = ev.clientX - this.videoState.leftInit //記錄點擊的離起點的距離
這裏的leftInit就是經過getOffset方法獲取的進度條起點偏移量
},
複製代碼
鬆開鼠標,經過記錄的距離算出當前的currentTime,而後今後點進行視頻播放或者暫停,邏輯以下:
handlePrograssUp() { //鬆開鼠標,播放當前進度條視頻
this.videoState.downState = false
// 計算點擊此處的currentTime
this.videoDom.currentTime = this.videoState.distance / this.processWidth * this.videoDom.duration
// 頁面回顯的currentTime數據
this.currentTime = this.timeTranslate(this.videoDom.currentTime)
// 這個是判斷當前視頻是在播放狀態進行點擊仍是在暫停狀態進行點擊的
if(this.videoState.playState) {
this.play()
}
},
複製代碼
上面的this.videoState.playState這個狀態是經過按鈕或者視頻區域點擊進行斷定的,具體在上面play(flag)和pause(flag)方法中。
handlePrograssMove(ev) { // 監聽移動進度條事件,同步播放相關事件
if(!this.videoState.downState) return //若是沒有經過鼠標點擊起點,則直接不進行下面計算
let disX = ev.clientX - this.videoState.leftInit
// 進行邊界判斷
if(disX > this.processWidth) {
disX = this.processWidth
}
if(disX < 0) {
disX = 0
}
this.videoState.distance = disX
// 計算當前的currentTime
this.videoDom.currentTime = this.videoState.distance / this.processWidth * this.videoDom.duration
},
複製代碼
播放的進度條重點是經過點擊或者拖動的位置計算當前的視頻的時間點,將此值賦予video標籤,這裏就是
this.videoDom.currentTime = 表達式
這樣timeupdate方法會被觸發:
this.videoDom.addEventListener("timeupdate", () => { // 監聽視頻播放過程當中的時間
const percentage = 100 * this.videoDom.currentTime / this.videoDom.duration
// inside進度條長度
this.videoPro.style.width = percentage + '%'
// point移動變化
this.videoPoi.style.left = percentage - 1 + '%'
this.currentTime = this.timeTranslate(this.videoDom.currentTime)
})
複製代碼
聲音與視頻的進度條是相似的,只不過聲音的進度條是計算豎直方向的,聲音相關屬性volume,值的範圍 0 ~ 1,監聽聲音的方法是volumechange。聲音的樣式圖片樣式以下:
// 監聽聲音的方法,經過此方法進行進度條渲染
this.videoDom.addEventListener("volumechange", () => {
const percentage = this.videoDom.volume * 100
this.voicePro.style.height = percentage + '%'
this.voicePoi.style.bottom = percentage + '%'
})
// 聲音控制的三個方法
handleVolPrograssDown(ev) { // 監聽聲音點擊事件
this.voiceState.topInit = this.getOffset(this.voiceProOut).top
this.volProcessHeight = this.voiceProOut.clientHeight
this.voiceState.downState = true //按下鼠標標誌
this.voiceState.distance = ev.clientY - this.voiceState.topInit
},
handleVolPrograssMove(ev) { // 監聽聲音進度條移動事件
if(!this.voiceState.downState) return
let disY = this.voiceState.topInit + this.volProcessHeight - ev.clientY
if(disY > this.volProcessHeight - 2) {
disY = this.volProcessHeight - 2
}
if(disY < 0) {
disY = 0
}
this.voiceState.distance = disY
this.videoDom.volume = this.voiceState.distance / this.volProcessHeight
this.videoOption.volume = Math.round(this.videoDom.volume * 100)
},
handleVolPrograssUp() { // 監聽聲音鼠標離開事件
this.voiceState.downState = false //按下鼠標標誌
this.videoDom.volume = this.voiceState.distance / this.volProcessHeight
this.videoOption.volume = Math.round(this.videoDom.volume * 100)
},
複製代碼
全屏方法,這裏面進行了兼容處理:
fullScreen() {
let ele = document.documentElement
if (ele .requestFullscreen) {
ele .requestFullscreen()
} else if (ele .mozRequestFullScreen) {
ele .mozRequestFullScreen()
} else if (ele .webkitRequestFullScreen) {
ele .webkitRequestFullScreen()
}
// 對應的video標籤大小100%
this.$refs['custom-video_container'].style.width = "100%"
this.$refs['custom-video_container'].style.height = "100%"
},
複製代碼
退出全屏方法:
exitFullscreen() {
let de = document
if (de.exitFullscreen) {
de.exitFullscreen();
} else if (de.mozCancelFullScreen) {
de.mozCancelFullScreen();
} else if (de.webkitCancelFullScreen) {
de.webkitCancelFullScreen();
}
// 返回初始化值
this.$refs['custom-video_container'].style.width = "500px"
this.$refs['custom-video_container'].style.height = "300px"
}
複製代碼
注意:這裏進度條和退出全屏事件都沒有對鍵盤按健進行處理,只是單純的鼠標點擊事件
控制欄在暫停的時候顯示,在播放的時候,只要鼠標在視頻播放器中,也會顯示,離開後幾秒後消失,這裏用到vue過渡動畫:
<div
class="custom-video_container"
ref="custom-video_container"
@mouseover="handleControls($event, 'start')"
@mouseleave="handleControls($event, 'end')"
>
<!--省略-->
<transition>
name="fade"
>
<div
class="custom-video_control"
v-show="!videoState.hideControl || !videoState.play"
>
<!--控制欄dom元素-->
</div>
</transition>
</div>
複製代碼
對應的css文件
/* 控制欄隱藏動畫 */
.fade-enter-active {
transition: all .3s ease;
}
.fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.fade-enter, .fade-leave-to {
transform: translateY(50px);
opacity: 0;
}
複製代碼
控制欄的消失或者展現是經過綁定最外層dom元素的mouseover、mouseleave事件進行邏輯控制, 這裏用mouseleave,而不是用mouseout,若是使用mouseout事件,在通過控制欄時會出現閃爍。具體事件源碼以下:
handleControls(ev, flag) { // 監聽離開或者進入視頻區域隱藏或者展現控制欄
switch (flag) {
case 'start':
this.videoState.hideControl = false
break;
case 'end':
this.videoState.hideControl = true
break;
default:
break;
}
},
複製代碼
經過控制this.videoState.hideControl狀態顯示隱藏活隱藏控制欄。
源碼地址:vue-player