前言
话不多说,这篇文章主要讲述如何从0到1搭建一款适用于Vue.js的自定义配置视频播放器。我们平时在PC端网站上观看视频时,会看到有很多丰富样式的视频播放器,而我们自己写的video标签样式却是那么丑。其实像那些网站都是基于原生video标签进行开发的,只不过还得适当加工一下,才会有我们所看到的漂亮的视频播放器。
开发
在具体开发之前,我们需要明确我们需要做什么?
- 封装一个可配置的视频播放器;
- 适用于Vue.js;
- 应用于PC端网站;
- 视频播放器常用的功能必须要有;
- 发布到Npm;
好,明确了以上几点之后,我们就开始敲代码了。
一、搭建一个基础的UI组件
这里的UI组件你可以理解成我们搭建一个静态页面,就是把视频播放器简单地搭建起来,有一个基础的模型。
-
-
- class="video-box"
- >
-
- class="video-player"
- >
- "bottom-tool">
- "pv-bar">
- "pv-played">
- "pv-dot">
-
- "pv-controls">
- "pc-con-l">
- "play-btn">
- "iconfont icon-bofang">
- "iconfont icon-zanting hide">
-
- "pv-time">
- "pv-currentTime">00:00:00
- /
- "pv-duration">00:00:00
-
-
- "pc-con-r">
- "pv-listen ml">
- "pv-yl">
-
"pv-ol"
> -
"pv-bg"
> -
- "pv-iconyl">
- "iconfont icon-yinliang">
- "iconfont icon-jingyin hide">
-
-
- "pv-speed ml">
-
"pv-spnum"
>1x -
"selectList"
> -
- 0.5x
-
- 1x
-
- 1.25x
-
- 1.5x
-
- 2x
-
-
- "pv-screen ml">
- "iconfont icon-quanping">
- "iconfont icon-huanyuan hide">
-
- "pv-screens ml">
- "iconfont icon-shipinquanping">
- "iconfont icon-tuichuquanping hide">
-
-
-
-
-
-
-
-
-
-
样式文件我这里就不展示了,我会在文末给出源码地址。
二、开发逻辑执行文件
最最关键的部分莫过于逻辑文件了,我这里使用构造函数的方式。
- // 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组件以及逻辑组件了,那我们接下来就是将两者结合起来。
-
-
- class="video-box"
- @mouseenter="vp.bottomTup()"
- @mouseleave="vp.bottomTdow()"
- >
-
- class="video-player"
- @canplay="vp.useOnplay()"
- @ended="vp.useEnd()"
- @click="vp.isplay()"
- >
- "bottom-tool">
- "pv-bar">
- "pv-played">
- "pv-dot" @mousedown="vp.useTime()">
-
- "pv-controls">
- "pc-con-l">
- "play-btn" @click="vp.usePlay()">
- "iconfont icon-bofang">
- "iconfont icon-zanting hide">
-
- "pv-time">
- "pv-currentTime">00:00:00
- /
- "pv-duration">00:00:00
-
-
- "pc-con-r">
- "pv-listen ml">
- "pv-yl">
-
"pv-ol"
@mousedown="vp.useListen()"> -
"pv-bg"
> -
- "pv-iconyl" @click="vp.useVolume()">
- "iconfont icon-yinliang">
- "iconfont icon-jingyin hide">
-
-
- "pv-speed ml">
-
"pv-spnum"
>1x -
"selectList"
@click="vp.useSpnum()"> -
- 0.5x
-
- 1x
-
- 1.25x
-
- 1.5x
-
- 2x
-
-
- "pv-screen ml" @click="vp.pageFullScreen()">
- "iconfont icon-quanping">
- "iconfont icon-huanyuan hide">
-
- "pv-screens ml" @click="vp.fullScreen()">
- "iconfont icon-shipinquanping">
- "iconfont icon-tuichuquanping hide">
-
-
-
-
-
-
-
-
-
-
首先我们引入了之前开发完成的逻辑文件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.
- ***
-
- "app">
-
"videoOption.properties" :videoStyle="videoOption.videoStyle"> -
-
-
-
- ***
我们离成功很近了,所以谢谢你可以阅读到这。源码地址: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')
-
- "app">
-
"videoOption.properties" :videoStyle="videoOption.videoStyle"> -
-
-
-
局部注册
-
- "app">
-
"videoOption.properties" :videoStyle="videoOption.videoStyle"> -
-
-
-
效果 大功告成!