从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

Jacquelyn38
• 阅读 1492

前言

话不多说,这篇文章主要讲述如何从0到1搭建一款适用于Vue.js的自定义配置视频播放器。我们平时在PC端网站上观看视频时,会看到有很多丰富样式的视频播放器,而我们自己写的video标签样式却是那么丑。其实像那些网站都是基于原生video标签进行开发的,只不过还得适当加工一下,才会有我们所看到的漂亮的视频播放器。

开发

在具体开发之前,我们需要明确我们需要做什么?

  1. 封装一个可配置的视频播放器;

  2. 适用于Vue.js;

  3. 应用于PC端网站;

  4. 视频播放器常用的功能必须要有;

  5. 发布到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>  
`

样式文件我这里就不展示了,我会在文末给出源码地址。

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

二、开发逻辑执行文件

最最关键的部分莫过于逻辑文件了,我这里使用构造函数的方式。

``// 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这个文件夹中,下图就是我们之前封装好的组件文件目录。

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

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这个文件夹)。

  1. 查看登录源是否是http://registry.npmjs.org
`npm config get registry  
`

如果不是,切换登录源。

`npm config set registry=http://registry.npmjs.org  
`
  1. 登录Npm
`npm login  
`

相继输入用户名、密码、邮箱。回车出现Logged in as maomincoding on http://registry.npmjs.org,那么就登录成功了。

  1. 上传发布到Npm
`npm publish  
`

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

五、安装组件

既然我们已经发布到Npm上,我们可以去Npm网站上看下效果。

`https://www.npmjs.com/package/vue-vam-video  
`

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

发布组件成功了,那么我们放在Vue工程上测试一下。

  1. 安装组件
`npm i vue-vam-video  
`
  1. 注册组件

全局注册

`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')  

`

`

`


**局部注册**

`

`

```

  1. 效果 大功告成!

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

结语

谢谢阅读,如果觉得有用,不要忘记一键三连哦~

  • 欢迎关注我的公众号前端历劫之路

  • 回复关键词电子书,即可获取12本前端热门电子书。

  • 回复关键词红宝书第4版,即可获取最新《JavaScript高级程序设计》(第四版)电子书。

  • 关注公众号后,点击下方菜单即可加我微信,我拉拢了很多IT大佬,创建了一个技术交流、文章分享群,期待你的加入。

  • 作者:Vam的金豆之路

  • 微信公众号:前端历劫之路

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

- END -

从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

本文转转自微信公众号前端历劫之路原创https://mp.weixin.qq.com/s/vrHGiurc0gIJXG7n9oUDGQ,如有侵权,请联系删除。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这