前言
话不多说,这篇文章主要讲述如何从0到1搭建一款适用于Vue.js的自定义配置视频播放器。我们平时在PC端网站上观看视频时,会看到有很多丰富样式的视频播放器,而我们自己写的video标签样式却是那么丑。其实像那些网站都是基于原生video标签进行开发的,只不过还得适当加工一下,才会有我们所看到的漂亮的视频播放器。
开发
在具体开发之前,我们需要明确我们需要做什么?
- 封装一个可配置的视频播放器;
- 适用于Vue.js;
- 应用于PC端网站;
- 视频播放器常用的功能必须要有;
- 发布到Npm;
好,明确了以上几点之后,我们就开始敲代码了。
一、搭建一个基础的UI组件
这里的UI组件你可以理解成我们搭建一个静态页面,就是把视频播放器简单地搭建起来,有一个基础的模型。
<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>
样式文件我这里就不展示了,我会在文末给出源码地址。
二、开发逻辑执行文件
最最关键的部分莫过于逻辑文件了,我这里使用构造函数的方式。
// 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); } } // 封装获取元素节点 function $(el) { return document.querySelector(el); } // 处理当前时间 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; } // 补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"; }; // 动态设置视频宽高 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); }; // 播放结束 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() : ""; } }; // 播放进度条 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; }; } // 导出 export default VamVideo;
三、整合组件逻辑
开发完UI组件以及逻辑组件了,那我们接下来就是将两者结合起来。
<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>
首先我们引入了之前开发完成的逻辑文件vp.js
,然后在mounted方法中对类VamVideo
进行实例化,赋给this.vp
。传给类的几个参数分别是最外层节点、视频属性、视屏样式。props属性中的properties
为视频属性,videoStyle
为视频样式。
四、发布组件
完成了以上几个步骤的开发,我们需要将我们完成的组件发布到Npm上。
1. 初始化
创建一个空文件夹,我们可以取名叫v-vamvideo。在此文件夹下键入命令:
npm init -y
因为我们还需要修改,所以直接创建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为
:搜索的关键词private
设为false
, 开源因此需要将这个字段改为false
2. 引入组件
将我们之前封装好的组件复制到v-vamvide这个文件夹中,下图就是我们之前封装好的组件文件目录。
3. 创建入口文件
我们要发布到Npm上需要一个入口文件,我们在v-vamvide根目录下创建一个入口文件,我们这里叫做index.js
。
// 引入组件 import VamVideo from "./VamVideo/vamvideo.vue"; // 组件需要添加name属性,代表注册的组件名称 VamVideo.install = (Vue) => Vue.component(VamVideo.name, VamVideo); //注册组件 export default VamVideo;
4. 创建一个说明文档
发布到Npm上,用户需要知道这个组件干什么的?怎么用?我们在v-vamvide根目录下创建一个说明文档,取名为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. 发布
开始操作以下步骤之前,你需要把命令行切换到项目根目录下(也就是这里的v-vamvide这个文件夹)。
- 查看登录源是否是
http://registry.npmjs.org
npm config get registry
如果不是,切换登录源。
npm config set registry=http://registry.npmjs.org
- 登录Npm
npm login
相继输入用户名、密码、邮箱。回车出现Logged in as maomincoding on http://registry.npmjs.org
,那么就登录成功了。
- 上传发布到Npm
npm publish
五、安装组件
既然我们已经发布到Npm上,我们可以去Npm网站上看下效果。
https://www.npmjs.com/package/vue-vam-video
发布组件成功了,那么我们放在Vue工程上测试一下。
- 安装组件
npm i vue-vam-video
- 注册组件
全局注册
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>
- 效果
大功告成!
结语
谢谢阅读,如果觉得有用,不要忘记一键三连哦~