從0到1搭建一款Vue可配置視頻播放器組件
前言
話不多說,這篇文章主要講述如何從0到1搭建一款適用于Vue.js的自定義配置視頻播放器。我們平時在PC端網(wǎng)站上觀看視頻時,會看到有很多豐富樣式的視頻播放器,而我們自己寫的video標(biāo)簽樣式卻是那么丑。其實(shí)像那些網(wǎng)站都是基于原生video標(biāo)簽進(jìn)行開發(fā)的,只不過還得適當(dāng)加工一下,才會有我們所看到的漂亮的視頻播放器。
開發(fā)
在具體開發(fā)之前,我們需要明確我們需要做什么?
- 封裝一個可配置的視頻播放器;
 - 適用于Vue.js;
 - 應(yīng)用于PC端網(wǎng)站;
 - 視頻播放器常用的功能必須要有;
 - 發(fā)布到Npm;
 
好,明確了以上幾點(diǎn)之后,我們就開始敲代碼了。
一、搭建一個基礎(chǔ)的UI組件
這里的UI組件你可以理解成我們搭建一個靜態(tài)頁面,就是把視頻播放器簡單地搭建起來,有一個基礎(chǔ)的模型。
- <template>
 - <div
 - class="video-box"
 - >
 - <video
 - class="video-player"
 - ></video>
 - <div class="bottom-tool">
 - <div class="pv-bar">
 - <div class="pv-played"></div>
 - <div class="pv-dot"></div>
 - </div>
 - <div class="pv-controls">
 - <div class="pc-con-l">
 - <div class="play-btn">
 - <i class="iconfont icon-bofang"></i>
 - <i class="iconfont icon-zanting hide"></i>
 - </div>
 - <div class="pv-time">
 - <span class="pv-currentTime">00:00:00</span>
 - <span>/</span>
 - <span class="pv-duration">00:00:00</span>
 - </div>
 - </div>
 - <div class="pc-con-r">
 - <div class="pv-listen ml">
 - <div class="pv-yl">
 - <p class="pv-ol"></p>
 - <p class="pv-bg"></p>
 - </div>
 - <div class="pv-iconyl">
 - <i class="iconfont icon-yinliang"></i>
 - <i class="iconfont icon-jingyin hide"></i>
 - </div>
 - </div>
 - <div class="pv-speed ml">
 - <p class="pv-spnum">1x</p>
 - <ul class="selectList">
 - <li>0.5x</li>
 - <li>1x</li>
 - <li>1.25x</li>
 - <li>1.5x</li>
 - <li>2x</li>
 - </ul>
 - </div>
 - <div class="pv-screen ml">
 - <i class="iconfont icon-quanping"></i>
 - <i class="iconfont icon-huanyuan hide"></i>
 - </div>
 - <div class="pv-screens ml">
 - <i class="iconfont icon-shipinquanping"></i>
 - <i class="iconfont icon-tuichuquanping hide"></i>
 - </div>
 - </div>
 - </div>
 - </div>
 - </div>
 - </template>
 - <script>
 - export default {
 - name: "VamVideo"
 - };
 - </script>
 - <style scoped>
 - @import "./css/iconfont/iconfont.css";
 - @import "./css/index.css";
 - </style>
 
樣式文件我這里就不展示了,我會在文末給出源碼地址。

二、開發(fā)邏輯執(zhí)行文件
最最關(guān)鍵的部分莫過于邏輯文件了,我這里使用構(gòu)造函數(shù)的方式。
- // eslint-disable-next-line no-unused-vars
 - function VamVideo(vp, attrObj, styleObj) {
 - // 初始化
 - this.timer = null;
 - this.disX = 0;
 - this.disL = 0;
 - this.isPageFullScreen = false;
 - // 處理視頻屬性
 - for (const key in attrObj) {
 - if (Object.hasOwnProperty.call(attrObj, key) && key !== "controls") {
 - $(".video-player").setAttribute(key, attrObj[key]);
 - }
 - }
 - // 處理視頻樣式
 - for (const key in styleObj) {
 - if (Object.hasOwnProperty.call(styleObj, key)) {
 - $(".video-box").style[`${key}`] = styleObj[key];
 - key === "width"
 - ? (this.vbw = styleObj.width)
 - : (this.vbw = vp.offsetWidth);
 - key === "height"
 - ? (this.vbh = styleObj.height)
 - : (this.vbh = vp.offsetHeight);
 - }
 - }
 - // 封裝獲取元素節(jié)點(diǎn)
 - function $(el) {
 - return document.querySelector(el);
 - }
 - // 處理當(dāng)前時間
 - function nowTime() {
 - $(".pv-currentTime").innerHTML = changeTime($(".video-player").currentTime);
 - let scale = $(".video-player").currentTime / $(".video-player").duration;
 - let w = $(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth;
 - $(".pv-dot").style.left = scale * w + "px";
 - $(".pv-played").style.width = scale * w + "px";
 - }
 - // 處理時分秒
 - function changeTime(iNum) {
 - let iN = parseInt(iNum);
 - const iH = toZero(Math.floor(iN / 3600));
 - const iM = toZero(Math.floor((iN % 3600) / 60));
 - const iS = toZero(Math.floor(iN % 60));
 - return iH + ":" + iM + ":" + iS;
 - }
 - // 補(bǔ)0
 - function toZero(num) {
 - if (num <= 9) {
 - return "0" + num;
 - } else {
 - return "" + num;
 - }
 - }
 - // 元素顯示
 - this.showEl = function (el) {
 - $(el).style.display = "block";
 - };
 - // 元素隱藏
 - this.hideEl = function (el) {
 - $(el).style.display = "none";
 - };
 - // 動態(tài)設(shè)置視頻寬高
 - this.setVp = function (w, h) {
 - const _w = String(w).indexOf("px") != -1 ? w : w + "px";
 - const _h = String(h).indexOf("px") != -1 ? h : h + "px";
 - $(".video-player").style.width = _w;
 - $(".video-player").style.height = _h;
 - $(".video-box").style.width = _w;
 - $(".video-box").style.height = _h;
 - $(".pv-bar").style.width = _w;
 - };
 - // 底部控制欄(顯示/隱藏)
 - this.bottomTup = function () {
 - $(".bottom-tool").style.bottom = "0px";
 - };
 - this.bottomTdow = function () {
 - $(".bottom-tool").style.bottom = "-45px";
 - };
 - // 播放/暫停
 - this.usePlay = function () {
 - if ($(".video-player").paused) {
 - $(".video-player").play();
 - this.hideEl(".icon-bofang");
 - this.showEl(".icon-zanting");
 - nowTime();
 - this.timer = setInterval(nowTime, 1000);
 - } else {
 - $(".video-player").pause();
 - this.showEl(".icon-bofang");
 - this.hideEl(".icon-zanting");
 - clearInterval(this.timer);
 - }
 - };
 - this.isplay = function () {
 - this.usePlay();
 - };
 - // 總時長
 - this.useOnplay = function () {
 - $(".pv-duration").innerHTML = changeTime($(".video-player").duration);
 - };
 - // 播放結(jié)束
 - this.useEnd = function () {
 - this.showEl(".icon-bofang");
 - this.hideEl(".icon-zanting");
 - };
 - // 靜音
 - this.useVolume = function () {
 - if ($(".video-player").muted) {
 - $(".video-player").volume = 1;
 - this.hideEl(".icon-jingyin");
 - this.showEl(".icon-yinliang");
 - $(".video-player").muted = false;
 - } else {
 - $(".video-player").volume = 0;
 - this.showEl(".icon-jingyin");
 - this.hideEl(".icon-yinliang");
 - $(".video-player").muted = true;
 - }
 - };
 - // 頁面全屏
 - this.pageFullScreen = function () {
 - const w = document.documentElement.clientWidth || document.body.clientWidth;
 - const h =
 - document.documentElement.clientHeight || document.body.clientHeight;
 - this.isPageFullScreen = !this.isPageFullScreen;
 - if (this.isPageFullScreen) {
 - this.setVp(w, h);
 - this.hideEl(".icon-quanping");
 - this.showEl(".icon-huanyuan");
 - this.hideEl(".pv-screens");
 - } else {
 - this.setVp(this.vbw, this.vbh);
 - this.showEl(".icon-quanping");
 - this.hideEl(".icon-huanyuan");
 - this.showEl(".pv-screens");
 - }
 - };
 - // 窗口全屏
 - this.fullScreen = function () {
 - const el = $(".video-box");
 - const isFullscreen =
 - document.fullScreen ||
 - document.mozFullScreen ||
 - document.webkitIsFullScreen;
 - if (!isFullscreen) {
 - this.showEl(".icon-tuichuquanping");
 - this.hideEl(".icon-shipinquanping");
 - this.hideEl(".pv-screen");
 - (el.requestFullscreen && el.requestFullscreen()) ||
 - (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
 - (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
 - (el.msRequestFullscreen && el.msRequestFullscreen());
 - } else {
 - this.showEl(".icon-shipinquanping");
 - this.hideEl(".icon-tuichuquanping");
 - this.showEl(".pv-screen");
 - document.exitFullscreen
 - ? document.exitFullscreen()
 - : document.mozCancelFullScreen
 - ? document.mozCancelFullScreen()
 - : document.webkitExitFullscreen
 - ? document.webkitExitFullscreen()
 - : "";
 - }
 - };
 - // 播放進(jìn)度條
 - this.useTime = function (ev) {
 - let ev1 = ev || window.event;
 - this.disX = ev1.clientX - $(".pv-dot").offsetLeft;
 - document.onmousemove = (ev) => {
 - let ev2 = ev || window.event;
 - let L = ev2.clientX - this.disX;
 - if (L < 0) {
 - L = 0;
 - } else if (L > $(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth) {
 - L = $(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth;
 - }
 - $(".pv-dot").style.left = L + "px";
 - let scale = L / ($(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth);
 - $(".video-player").currentTime = scale * $(".video-player").duration;
 - nowTime();
 - };
 - document.onmouseup = function () {
 - document.onmousemove = null;
 - };
 - return false;
 - };
 - // 音量控制
 - this.useListen = function (ev) {
 - let ev1 = ev || window.event;
 - this.disL = ev1.clientX - $(".pv-ol").offsetLeft;
 - document.onmousemove = (ev) => {
 - let ev2 = ev || window.event;
 - let L = ev2.clientX - this.disL;
 - if (L < 0) {
 - L = 0;
 - } else if (L > $(".pv-yl").offsetWidth - $(".pv-ol").offsetWidth) {
 - L = $(".pv-yl").offsetWidth - $(".pv-ol").offsetWidth;
 - }
 - $(".pv-ol").style.left = L + "px";
 - let scale = L / ($(".pv-yl").offsetWidth - $(".pv-ol").offsetWidth);
 - $(".pv-bg").style.width = $(".pv-ol").offsetLeft + "px";
 - if ($(".pv-ol").offsetLeft !== 0) {
 - this.showEl(".icon-yinliang");
 - this.hideEl(".icon-jingyin");
 - } else {
 - this.showEl(".icon-jingyin");
 - this.hideEl(".icon-yinliang");
 - }
 - $(".video-player").volume = scale;
 - };
 - document.onmouseup = function () {
 - document.onmousemove = null;
 - };
 - return false;
 - };
 - // 播放速度
 - this.useSpnum = function (e) {
 - let ev = e || window.event;
 - $(".pv-spnum").innerText = ev.target.innerText;
 - const value = ev.target.innerText.replace("x", "");
 - $(".video-player").playbackRate = value;
 - };
 - }
 - // 導(dǎo)出
 - export default VamVideo;
 
三、整合組件邏輯
開發(fā)完UI組件以及邏輯組件了,那我們接下來就是將兩者結(jié)合起來。
- <template>
 - <div
 - class="video-box"
 - @mouseenter="vp.bottomTup()"
 - @mouseleave="vp.bottomTdow()"
 - >
 - <video
 - class="video-player"
 - @canplay="vp.useOnplay()"
 - @ended="vp.useEnd()"
 - @click="vp.isplay()"
 - ></video>
 - <div class="bottom-tool">
 - <div class="pv-bar">
 - <div class="pv-played"></div>
 - <div class="pv-dot" @mousedown="vp.useTime()"></div>
 - </div>
 - <div class="pv-controls">
 - <div class="pc-con-l">
 - <div class="play-btn" @click="vp.usePlay()">
 - <i class="iconfont icon-bofang"></i>
 - <i class="iconfont icon-zanting hide"></i>
 - </div>
 - <div class="pv-time">
 - <span class="pv-currentTime">00:00:00</span>
 - <span>/</span>
 - <span class="pv-duration">00:00:00</span>
 - </div>
 - </div>
 - <div class="pc-con-r">
 - <div class="pv-listen ml">
 - <div class="pv-yl">
 - <p class="pv-ol" @mousedown="vp.useListen()"></p>
 - <p class="pv-bg"></p>
 - </div>
 - <div class="pv-iconyl" @click="vp.useVolume()">
 - <i class="iconfont icon-yinliang"></i>
 - <i class="iconfont icon-jingyin hide"></i>
 - </div>
 - </div>
 - <div class="pv-speed ml">
 - <p class="pv-spnum">1x</p>
 - <ul class="selectList" @click="vp.useSpnum()">
 - <li>0.5x</li>
 - <li>1x</li>
 - <li>1.25x</li>
 - <li>1.5x</li>
 - <li>2x</li>
 - </ul>
 - </div>
 - <div class="pv-screen ml" @click="vp.pageFullScreen()">
 - <i class="iconfont icon-quanping"></i>
 - <i class="iconfont icon-huanyuan hide"></i>
 - </div>
 - <div class="pv-screens ml" @click="vp.fullScreen()">
 - <i class="iconfont icon-shipinquanping"></i>
 - <i class="iconfont icon-tuichuquanping hide"></i>
 - </div>
 - </div>
 - </div>
 - </div>
 - </div>
 - </template>
 - <script>
 - import VamVideo from "./vp.js";
 - export default {
 - name: "VamVideo",
 - data: () => ({
 - vp: null,
 - defaultStyle: {
 - width: "1200px",
 - height: "600px",
 - },
 - }),
 - props: {
 - properties: {
 - type: Object,
 - },
 - videoStyle: {
 - type: Object,
 - },
 - },
 - mounted() {
 - this.vp = new VamVideo(
 - document.querySelector(".video-box"),
 - this.properties,
 - Object.keys(this.videoStyle).length === 0
 - ? this.defaultStyle
 - : this.videoStyle
 - );
 - },
 - };
 - </script>
 - <style scoped>
 - @import "./css/iconfont/iconfont.css";
 - @import "./css/index.css";
 - </style>
 
首先我們引入了之前開發(fā)完成的邏輯文件vp.js,然后在mounted方法中對類VamVideo進(jìn)行實(shí)例化,賦給this.vp。傳給類的幾個參數(shù)分別是最外層節(jié)點(diǎn)、視頻屬性、視屏樣式。props屬性中的properties為視頻屬性,videoStyle為視頻樣式。
四、發(fā)布組件
完成了以上幾個步驟的開發(fā),我們需要將我們完成的組件發(fā)布到Npm上。
1. 初始化
創(chuàng)建一個空文件夾,我們可以取名叫v-vamvideo。在此文件夾下鍵入命令:
- npm init -y
 
因?yàn)槲覀冞€需要修改,所以直接創(chuàng)建package.json文件。
- {
 - "name": "vue-vam-video",
 - "version": "1.0.0",
 - "description": "Vue.js Custom video components",
 - "main": "index.js",
 - "author": "maomincoding",
 - "keywords": ["video"],
 - "license": "ISC",
 - "private": false
 - }
 
- name:組件名
 - author:Npm用戶名
 - main:入口文件
 - version:版本號,更新組件需要用到這個字段
 - description:描述
 - license的值按照以上即可
 - keywords為:搜索的關(guān)鍵詞
 - private設(shè)為false, 開源因此需要將這個字段改為false
 
2. 引入組件
將我們之前封裝好的組件復(fù)制到v-vamvide這個文件夾中,下圖就是我們之前封裝好的組件文件目錄。

3. 創(chuàng)建入口文件
我們要發(fā)布到Npm上需要一個入口文件,我們在v-vamvide根目錄下創(chuàng)建一個入口文件,我們這里叫做index.js。
- // 引入組件
 - import VamVideo from "./VamVideo/vamvideo.vue";
 - // 組件需要添加name屬性,代表注冊的組件名稱
 - VamVideo.install = (Vue) => Vue.component(VamVideo.name, VamVideo); //注冊組件
 - export default VamVideo;
 
4. 創(chuàng)建一個說明文檔
發(fā)布到Npm上,用戶需要知道這個組件干什么的?怎么用?我們在v-vamvide根目錄下創(chuàng)建一個說明文檔,取名為README.md
- # vue-vamvideo
 - > Vue.js Custom video components
 - ## Using documents
 - 1. Introducing components
 - 2. configuration parameter
 - - `properties`: Video properties.
 - - `videoStyle`: Video style.
 - These two parameters need to be set separately.
 - ***
 - <template>
 - <div id="app">
 - <vam-video :properties="videoOption.properties" :videoStyle="videoOption.videoStyle"></vam-video>
 - </div>
 - </template>
 - <script>
 - export default {
 - name: "Index",
 - data: () => ({
 - videoOption: {
 - properties: {
 - poster: require("./img/bg.png"),
 - src:
 - "https://mos-vod-drcn.dbankcdn.cn/P_VT/video_injection/A91343E9D/v3/9AB0A7921049102362779584128/MP4Mix_H.264_1920x1080_6000_HEAAC1_PVC_NoCut.mp4",
 - preload: "auto",
 - loop: "loop",
 - // autoplay:"autoplay",
 - // muted:true,
 - // controls:"controls"
 - },
 - videoStyle: {
 - // width: "1200px",
 - // height: "600px",
 - },
 - },
 - })
 - };
 - </script>
 - ***
 
我們離成功很近了,所以謝謝你可以閱讀到這。源碼地址:https://github.com/maomincoding/vue-vam-video
5. 發(fā)布
開始操作以下步驟之前,你需要把命令行切換到項目根目錄下(也就是這里的v-vamvide這個文件夾)。
1.查看登錄源是否是http://registry.npmjs.org
- npm config get registry
 
如果不是,切換登錄源。
- npm config set registry=http://registry.npmjs.org
 
2.登錄Npm
- npm login
 
相繼輸入用戶名、密碼、郵箱。回車出現(xiàn)Logged in as maomincoding on http://registry.npmjs.org,那么就登錄成功了。
3.上傳發(fā)布到Npm
- npm publish
 
五、安裝組件
既然我們已經(jīng)發(fā)布到Npm上,我們可以去Npm網(wǎng)站上看下效果。
https://www.npmjs.com/package/vue-vam-video

發(fā)布組件成功了,那么我們放在Vue工程上測試一下。
1.安裝組件
- npm i vue-vam-video
 
2.注冊組件
全局注冊
- import Vue from 'vue'
 - import App from './App.vue'
 - // 全局注冊
 - import VamVideo from "vue-vam-video";
 - Vue.use(VamVideo);
 - Vue.config.productionTip = false
 - new Vue({
 - render: h => h(App),
 - }).$mount('#app')
 
- <template>
 - <div id="app">
 - <vam-video :properties="videoOption.properties" :videoStyle="videoOption.videoStyle"></vam-video>
 - </div>
 - </template>
 - <script>
 - export default {
 - name: "App",
 - data: () => ({
 - videoOption: {
 - properties: {
 - poster: require("./assets/logo.png"),
 - src:
 - "https://mos-vod-drcn.dbankcdn.cn/P_VT/video_injection/A91343E9D/v3/9AB0A7921049102362779584128/MP4Mix_H.264_1920x1080_6000_HEAAC1_PVC_NoCut.mp4",
 - preload: "auto",
 - loop: "loop",
 - // autoplay:"autoplay",
 - // muted:true,
 - // controls:"controls"
 - },
 - videoStyle: {
 - // width: "1200px",
 - // height: "600px",
 - },
 - },
 - })
 - };
 - </script>
 
局部注冊
- <template>
 - <div id="app">
 - <vam-video :properties="videoOption.properties" :videoStyle="videoOption.videoStyle"></vam-video>
 - </div>
 - </template>
 - <script>
 - import VamVideo from 'vue-vam-video'
 - export default {
 - name: "App",
 - data: () => ({
 - videoOption: {
 - properties: {
 - poster: require("./assets/logo.png"),
 - src:
 - "https://mos-vod-drcn.dbankcdn.cn/P_VT/video_injection/A91343E9D/v3/9AB0A7921049102362779584128/MP4Mix_H.264_1920x1080_6000_HEAAC1_PVC_NoCut.mp4",
 - preload: "auto",
 - loop: "loop",
 - // autoplay:"autoplay",
 - // muted:true,
 - // controls:"controls"
 - },
 - videoStyle: {
 - // width: "1200px",
 - // height: "600px",
 - },
 - },
 - }),
 - components: {
 - VamVideo
 - },
 - };
 - </script>
 
3.效果 大功告成!


















 
 
 








 
 
 
 