vue實(shí)現(xiàn)鼠標(biāo)滑動(dòng)預(yù)覽視頻封面組件示例詳解
組件效果
http://www.dbjr.com.cn/Special/926.htm
組件設(shè)計(jì)
我們首先應(yīng)該要對組件進(jìn)行一個(gè)簡單的設(shè)計(jì)。
主要的邏輯如上圖??????,可以拆分成這么幾個(gè)步驟:
1、視頻截取關(guān)鍵幀
我們可以先將視頻各個(gè)時(shí)間的關(guān)鍵幀截圖保存,具體截取幀數(shù)可以使用傳入?yún)?shù)控制。
2、鼠標(biāo)移入封面時(shí)顯示對應(yīng)關(guān)鍵幀
在鼠標(biāo)移入的時(shí)候我們應(yīng)該要計(jì)算當(dāng)前鼠標(biāo)位置和視頻寬度的比例關(guān)系,然后從視頻幀列表中獲取到對應(yīng)的圖片作為當(dāng)前的視頻封面圖片。
3、視頻和封面的狀態(tài)切換
這里我們是將用兩個(gè)元素分別作為視頻和封面,所以我們狀態(tài)切換的時(shí)候需要控制兩個(gè)元素的顯示和隱藏。
- 點(diǎn)擊封面
顯示并播放視頻,隱藏封面。
- 暫停播放
顯示封面,隱藏視頻。
功能實(shí)現(xiàn)
分析完組件的關(guān)鍵步驟之后我們便可以開始動(dòng)手來實(shí)現(xiàn)相應(yīng)的功能了。
1、視頻截取關(guān)鍵幀圖片列表
1.1 截取指定幀
視頻關(guān)鍵幀的截取我們可以使用canvas來實(shí)現(xiàn),具體實(shí)現(xiàn)方法如下:
/** * @param {element} video * @param {number} currentTime * @return {void} */ cutCover(video, currentTime) { video.currentTime = currentTime; const canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); canvas.width = parseInt(this.width); canvas.height = parseInt(this.height); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const img = canvas.toDataURL("image/png"); return img; },
通過該函數(shù)我們可以獲取指定時(shí)間的視頻圖片幀。
- 傳入?yún)?shù)
/** * @param {element} video * @param {number} currentTime */
video為需要截取視頻的dom元素,currentTime為要截取圖片幀的時(shí)間點(diǎn)。
- 返回參數(shù)
/** * @return {Base64} img */
返回參數(shù)為截取的指定幀的Base64格式的圖片。
1.2 截取stepNums張關(guān)鍵幀圖片
stepNums為我們傳入的組件參數(shù),及需要截取的封面關(guān)鍵幀圖片數(shù)量,數(shù)量越多,預(yù)覽的效果越連貫,可以根據(jù)視頻長度來調(diào)整截取的張數(shù)。
init(){ const videoContentShow = document.getElementById( this.uid + "-video" ); videoContentShow.style.height = this.height; videoContentShow.style.width = this.width; const videoContent = videoContentShow.cloneNode(); videoContent.addEventListener("canplay", () => { if (this.currentTime < this.duration) this.cut(videoContent); else this.progressValue = 0; }); } cut(video) { const duration = video.duration; this.duration = duration; this.currentTime += duration / this.stepNums; const img = this.cutCover(video, this.currentTime); this.imgList.push(img); if (this.imgList.length == 2) { this.coverSrc = img; const coverImg = document.getElementById( this.uid + "-coverImg" ); coverImg.setAttribute("src", img); } }
具體代碼如上,首先我們應(yīng)該先要獲取到視頻的dom元素,但是要注意:我們不在原始視頻元素上進(jìn)行截取操作,我們這里使用了cloneNode()來克隆一個(gè)dom元素進(jìn)行操作。因?yàn)樵谶M(jìn)行截取的時(shí)候我們需要對視頻的currentTime屬性進(jìn)行一個(gè)修改,也就是改變視頻的播放進(jìn)度,如果在原視頻上截取的話,在未截取完成前播放視頻會(huì)導(dǎo)致視頻播放進(jìn)度混亂,所以這里我們在克隆元素對象上進(jìn)行操作。
我們總共需要截取stepNums張圖片,所以每次截取的時(shí)間間隔應(yīng)該為:duration / this.stepNums,即視頻總時(shí)間長度/截取圖片張數(shù),循環(huán)截取即可。
2、鼠標(biāo)移入封面時(shí)顯示對應(yīng)關(guān)鍵幀
鼠標(biāo)移入封面的時(shí)候我們需要對封面圖片進(jìn)行切換。
2.1 鼠標(biāo)移動(dòng)事件監(jiān)聽
<img :id="uid + '-coverImg'" :src="coverSrc" class="j-coverImg" @mousemove="imgHover" @mouseleave="hoverOut" @click="coverClick" />
這里我們使用vue中的mousemove和mouseleave對鼠標(biāo)事件進(jìn)行監(jiān)聽。
imgHover(e) { const coverImg = document.getElementById(this.uid + "-coverImg"); const w = coverImg.offsetWidth / this.stepNums; const x = e.offsetX - coverImg.offsetLeft; const index = Math.min( Math.max(Math.ceil(x / w), 1), this.stepNums ); if (this.imgList.length < index) return; this.progressValue = index; coverImg.setAttribute( "src", this.imgList[Math.min(this.imgList.length - 1, index)] ); },
鼠標(biāo)移入的時(shí)候我們需要根據(jù)鼠標(biāo)的坐標(biāo)位置來計(jì)算展示的幀數(shù)下標(biāo),具體計(jì)算如下:
- 每張圖片展示的區(qū)間大小
const w = coverImg.offsetWidth / this.stepNums;
每個(gè)區(qū)間的大小我們只需要將封面的寬度除于圖片幀列表的數(shù)量即可得到每張圖片展示的區(qū)間大小。
- 當(dāng)前鼠標(biāo)所在區(qū)間
const x = e.offsetX - coverImg.offsetLeft; const index = Math.min( Math.max(Math.ceil(x / w), 1), this.stepNums );
首先我們應(yīng)該要計(jì)算當(dāng)前鼠標(biāo)在封面里的相對位置,這里我們只需要其橫坐標(biāo)x即可,然后將坐標(biāo)除于區(qū)間大小,我們即可得到當(dāng)前坐標(biāo)所對應(yīng)的區(qū)間下標(biāo)。這里的最大值應(yīng)該進(jìn)行限制為1和stepNums。
2.2 鼠標(biāo)移出事件監(jiān)聽
鼠標(biāo)移出的時(shí)候我們需要將封面恢復(fù)成當(dāng)前視頻的封面。
hoverOut(e) { const coverImg = document.getElementById(this.uid + "-coverImg"); const step = this.duration / this.stepNums; const index = Math.ceil(this.pauseTime / step); this.progressValue = index; coverImg.setAttribute("src", this.pauseCover || this.coverSrc); },
3、視頻和封面的狀態(tài)切換
封面和視頻的顯示隱藏需要根據(jù)播放狀態(tài)來進(jìn)行對應(yīng)的切換。
3.1 播放視頻
點(diǎn)擊封面的時(shí)候播放視頻,需要隱藏封面及相關(guān)的進(jìn)度條并顯示視頻
doHide(hide = false) { const videoContent = document.getElementById(this.uid + "-video"); videoContent.style.display = hide ? "block" : "none"; videoContent.currentTime = this.pauseTime; hide ? videoContent.play() : videoContent.pause(); const img = document.getElementById(this.uid + "-coverImg"); img.style.display = hide ? "none" : "block"; const progress = document.getElementById(this.uid + "-progress"); progress.style.display = hide ? "none" : "block"; const progress1 = document.getElementById(this.uid + "-progress1"); progress1.style.display = hide ? "none" : "block"; }, coverClick() { this.doHide(true); },
3.2 視頻暫停
視頻暫停時(shí)我們需要隱藏視頻,截取當(dāng)前幀作為封面并顯示封面及相關(guān)的進(jìn)度條。
videoContentShow.addEventListener("pause", e => { this.pauseTime = videoContentShow.currentTime; this.pauseCover = this.cutCover( videoContentShow, videoContentShow.currentTime ); coverImg.setAttribute("src", this.pauseCover); const step = this.duration / this.stepNums; const index = Math.ceil(this.pauseTime / step); this.progressValue = index; setTimeout(() => { if (videoContentShow.paused) this.doHide(); }, 200); });
這里我使用了一個(gè)setTimeout來進(jìn)行一個(gè)延時(shí)控制,大家知道為什么嗎?因?yàn)橐曨l有兩種操作會(huì)觸發(fā)視頻的pause事件:
- 點(diǎn)擊暫停按鈕
- 拉動(dòng)進(jìn)度條
這里拉動(dòng)進(jìn)度條的時(shí)候會(huì)觸發(fā)視頻的pause事件并且馬上繼續(xù)播放,所以我們應(yīng)該要過濾掉這一情況。
組件使用
<template> <div class="content"> <div class="video-list"> <j-video-cover class="video" :videoUrl="videoUrl" stepNums="40" ></j-video-cover> </div> </div> </template> <script> export default { data() { return { videoUrl: require("../../assets/video/202112250058.mp4"), } } } </script>
組件庫引用
這里我將這個(gè)組件打包進(jìn)了自己的一個(gè)組件庫,并將其發(fā)布到了npm上,有需要的同學(xué)也可以直接引入該組件進(jìn)行使用。
引入組件代碼
<j-code-height-light :code = "code" :keyWords = "keyWords" :color = "color"> </j-code-height-light> <!-- 注釋 --> <div class = 'body'> <div class = 'title'>標(biāo)題</div> <div class = 'main'> <span >內(nèi)容</span> </div> </div> /** * 組件參數(shù)配置如下 */ props: { code: { type: String, default: '' }, keyWords:{ type:Array, default:[ { value:'關(guān)鍵字1', color:'顏色1' }, { value:'關(guān)鍵字2', color:'顏色2' } ] }, color:{ type: Object, default: { keyWord:'orange',//js關(guān)鍵字 varWord:'purple',//js變量 tagWord:'#F9273F',//html標(biāo)簽 strWord:'green',//字符串變量值 attrWord:'green',//html屬性 attrValue:'yellow',//html屬性值 methodkeyWord:'#74759b',//js方法 functionkeyWord:'#2c9678',//自定義函數(shù) note:'grey'//注釋 } } }, methods:{ test(){ console.log('test'); }, testP(p1,p2){ console.log(p1,p2); } }
引入后即可直接使用。
源碼地址
組件庫已開源,想要查看完整源碼的可以到 gitee 查看,自己也整理了相關(guān)的文檔對其進(jìn)行了簡單介紹,具體如下:
組件文檔 http://shouce.jb51.net/vue/single-file-components.html
以上就是vue實(shí)現(xiàn)鼠標(biāo)滑動(dòng)預(yù)覽視頻封面組件示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue鼠標(biāo)滑動(dòng)視頻封面預(yù)覽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue+koa2搭建mock數(shù)據(jù)環(huán)境的詳細(xì)教程
這篇文章主要介紹了vue+koa2搭建mock數(shù)據(jù)環(huán)境的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05通過Element ui往頁面上加一個(gè)分頁導(dǎo)航條的方法
這篇文章主要介紹了通過Element ui往頁面上加一個(gè)分頁導(dǎo)航條的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05前端vue?a鏈接下載文件失敗的問題(未發(fā)現(xiàn)文件)
這篇文章主要介紹了前端vue?a鏈接下載文件失敗的問題(未發(fā)現(xiàn)文件),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09詳解本地Vue項(xiàng)目請求本地Node.js服務(wù)器的配置方法
本文只針對自己需要本地模擬接口于是搭建一個(gè)本地node服務(wù)器供自己測試使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03webpack項(xiàng)目中使用vite加速的兼容模式詳解
這篇文章主要為大家介紹了webpack項(xiàng)目中使用vite加速的兼容模式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Vue引用第三方datepicker插件無法監(jiān)聽datepicker輸入框的值的解決
這篇文章主要介紹了Vue引用第三方datepicker插件無法監(jiān)聽datepicker輸入框的值的解決,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01