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

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

前言


话不多说,这篇文章主要讲述如何从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>


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


微信截图_20220506131746.png


二、开发逻辑执行文件


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


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


微信截图_20220506131807.png


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


微信截图_20220506131831.png


五、安装组件


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


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


微信截图_20220506131856.png


发布组件成功了,那么我们放在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')


<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>


  1. 效果


大功告成!


微信截图_20220506131942.png


结语


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



相关文章
|
27天前
|
JavaScript
在 Vue 中处理组件选项与 Mixin 选项冲突的详细解决方案
【10月更文挑战第18天】通过以上的分析和探讨,相信你对在 Vue 中使用 Mixin 时遇到组件选项与 Mixin 选项冲突的解决方法有了更深入的理解。在实际开发中,要根据具体情况灵活选择合适的解决方案,以确保代码的质量和可维护性。
80 7
|
9天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
9天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
25天前
|
缓存 JavaScript UED
Vue 的动态组件与 keep-alive
【10月更文挑战第19天】总的来说,动态组件和 `keep-alive` 是 Vue.js 中非常实用的特性,它们为我们提供了更灵活和高效的组件管理方式,使我们能够更好地构建复杂的应用界面。深入理解和掌握它们,以便在实际开发中能够充分发挥它们的优势,提升我们的开发效率和应用性能。
44 18
|
20天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
28天前
|
JavaScript 安全 测试技术
vue封装组件发布到Npm
【10月更文挑战第17天】
|
25天前
|
前端开发 UED
vue3知识点:Suspense组件
vue3知识点:Suspense组件
30 4
|
24天前
|
JavaScript 前端开发 测试技术
组件化开发:创建可重用的Vue组件
【10月更文挑战第21天】组件化开发:创建可重用的Vue组件
24 1
|
25天前
|
JavaScript 前端开发 Java
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
32 2
|
25天前
|
Java
vue3知识点:Teleport组件
vue3知识点:Teleport组件
25 1

相关实验场景

更多

推荐镜像

更多