ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Vue3 组件示例工程(三) —— 视频组件

2021-07-08 19:00:02  阅读:397  来源: 互联网

标签:视频 const 示例 js video 全屏 Vue3 组件 videojs


目录

1.视频组件 video-base 编写

2.视频组件 video-base 使用


1.视频组件 video-base 编写

  • 效果展示【缩略图】:
  • 效果展示【点击后的全屏效果】:


  • 组件名称:TVideoBase

  • 为了播放各种格式的视频(.m3u8、.mp4...),此组件调用了 video.js 库
  • 安装 video.js:npm install --save video.js
  • 在组件中,应该导入这个库:
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
  • 可供接受的参数(props):
/**
 * t-video-base
 * @desc 视频
 * @param {string} [themeStyle] - 主题风格
 * @param {Object} [data] - 数据
 * @param {Object} [cStyle] - 自定义样式
 * @example
 * <t-video-base></t-video-base>
 */

  props: {
    // 数据
    data: {
      type: Object,
      default: () => ({}),
    },
    // 自定义样式
    cStyle: {
      type: Object,
      default: () => ({
        wrapper: {},
      }),
    },
  },

  • 组件模板:
<template>
  <div class="t-video-base">

     <!-- 封面容器 + 播放按钮 -->
    <div class="video"
      :style="{ ...cStyle.wrapper, background:`url(${data.coverSrc}) 0% 0% / 100% 100% no-repeat`}"
      @click="openViewer"
    >
      <!-- 播放按钮 -->
      <div class="video-play">
        <i class="el-icon-caret-right" />
      </div>
    </div>

    <!-- 全屏状态 - 容器 -->
    <div v-if="showViewer" 
         id="t-video-viewer"
         class="video-viewer"
    >
      <div class="video-viewer__box">

        <!-- 全屏状态 - 顶部标题 -->
        <div class="video-viewer__name">
          {{ data.videoName }}
        </div>

        <!-- 全屏状态 - 关闭按钮 -->
        <div
          class="video-viewer__close"
          @click="closeViewer"
        >
          <i class="el-icon-close" />
        </div>

        <!-- 全屏状态 - 视频播放标签 -->
        <video
          ref="realtimeVideo"
          class="video-js video-viewer__video"
          :src="data.videoSrc"
          controls="controls"
          autoplay
        >
          浏览器不支持视频标签
        </video>

      </div>
    </div>

  </div>
</template>
  • 该模板包含了两个部分: 缩略图模块 + 全屏模块
  • 缩略图模块:
  1. 通过动态绑定样式,设置用户传入的背景图,作为缩略封面
  2. :style="{ ...cStyle.wrapper, background:`url(${data.coverSrc}) 0% 0% / 100% 100% no-repeat`}"
  3. 通过给播放按钮绑定 click事件,实现全屏模块的展示 @click="openViewer"
  • 全屏模块:
  1. 首先通过 v-if="showViewer"  确定 全屏模块是否展示,此全屏模块容器A应该设置 id,id="t-video-viewer"
  2. 接着在上述容器A中,再设置一个容器B,用于包裹 视频全屏状态下的:标题,关闭按钮,video标签
  3. 全屏的标题,根据用户传入的内容进行设置
  4. 全屏的关闭按钮绑定 click事件,实现全屏模块的隐藏 @click="closeViewer"
  5. 全屏的视频标签,根据用户传入的视频地址,绑定 src,为了获取这个视频标签的实例对象,需要给该标签绑定 ref,也就是 ref="realtimeVideo"

  • 在 setup() 中写逻辑(video.js 库操作):
  • 声明各种变量:
setup(props) {
    const state = reactive({
      // video标签 dom元素 实例
      realtimeVideo: {} as HTMLElement,
      // video.js 这个库的实例(播放器)
      player: null,
    });

    // 是否全屏显示
    const showViewer = ref(false);

    // videojs index.html引入 或者 import引入
    const video = (window as any).videojs || videojs;
}
  • 注意:
  1. 如果项目中存在 video.js,一般会被放在 public/index.html 下进行引入,此时组件内如果需要使用 video.js,就可以通过 const video = (window as any).videojs 导入
  2. 如果项目中未安装 video.js,而是采用 npm 安装的形式,则就相当于最上面说的,通过 import 导入,在 setup() 中获取方法:const video = videojs;
  • 获取 <video></video> 这种 dom元素实例 的方法:
  • 在标签上添加 ref 属性:ref="realtimeVideo"
  • 在 setup() 中声明与 ref 相同名的响应式变量:const state = reactive({ realtimeVideo: {} as HTMLElement })
  • 两者一定要同名,尽可能声明类型 xxx as HTMLElement

  • 打开全屏播放视频【重要】
    /**
     * 打开大图浏览
     */
    function openViewer() {
      showViewer.value = true; // 将全屏模块放出来

      // 将组件挂载到body标签里
      nextTick(() => {
        const body = document.querySelector('body');
        if (body.append) {
          body.append(document.getElementById('t-video-viewer'));
        } else {
          body.appendChild(document.getElementById('t-video-viewer'));
        }

        // 如果用户传入了 videojs 的自定义配置
        if (props.data.videojs) {
          console.log('onMounted realtimeVideo', state.realtimeVideo);
          state.player = video(state.realtimeVideo, props.data.videojs, () => {
            console.log('on ready');
          });
          console.log('onMounted player', state.player);
        }
      });
    }
  • 第一步是将全屏模块放出来,也就是满足全屏模块的 v-if 条件
  • 因为 v-if 改变之后,dom会进行重新渲染,也就是说,全屏模式的相关dom是后出现的,这时如果进行数据交互,应该放在 nextTick(() => {}) 函数中(个人理解,如果错了请指正 >_<)
  • 在 nextTick 函数中,先获取 body dom元素,然后在 body 中追加全屏模块 body.append(document.getElementById('t-video-viewer'));
  • 如果用户传入了 videojs 的自定义配置,就把最初声明的 响应式播放器变量 player 进行赋值,也就是调用 video() 方法,传入 video标签的ref 及 用户传入的 videojs 配置

  • 关闭全屏播放视频:
    /**
     * 关闭大图浏览
     */
    function closeViewer() {
      showViewer.value = false;
    }
  • onBeforeUnmount(() => {}) 组件卸载前执行的操作
    onBeforeUnmount(() => {
      if (state.player) {
        (state.player as any).dispose();
      }
    });
  • 组件卸载之前,如果还有播放器,应该进行 销毁(调用 video.js 中的 dispose()

  • 默认样式设置:
.t-video-base {
  // 缩略图外容器
  .video {
    position: relative;
    width: 100%;
    height: 119px;
    border-radius: 7px;
    box-shadow: 0 0 20px rgba(0, 0, 0,.8) inset;
    overflow: hidden;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    transition: all .5s;

    &:hover {
      box-shadow: 0 0 20px 8px rgba(0, 0, 0,.8) inset;
    }

    // 播放按钮
    &-play {
      width: 32px;
      height: 32px;
      background: rgba(9, 16, 41, .7);
      border-radius: 50%;
      color: #fff;
      font-size: 18px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }
}

// 全屏模块外容器
.video-viewer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 9000;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, .5);

  // 关闭按钮
  &__close {
    position: absolute;
    z-index: 2;
    top: 15px;
    right: 15px;
    width: 24px;
    height: 24px;
    font-size: 24px;
    line-height: 24px;
    color: #eee;
    cursor: pointer;
  }

  &__close:hover {
    color: #fff;
  }

  // 视频名称
  &__name {
    position: absolute;
    z-index: 2;
    left: 18px;
    top: 12px;
    font-size: 16px;
    color: #eee;
    line-height: 24px;
  }

  // 名字和关闭按钮的外容器
  &__box {
    position: relative;
    width: 90%;
    border-radius: 6px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  &__box::after { // 欸【?】
    content: ' ';
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    box-shadow: 0 28px 50px -3px rgba(0, 0, 0, 0.85) inset;
    pointer-events: none;
  }

  // 视频标签
  &__video {
    margin: 0;
    width: 100%;
    height: 100%;
    outline: none;
  }
}
  • 这里的关键点,是 pointer-events: none;

2.视频组件 video-base 使用

  • 导入组件及样式:
  • 一种是在 main.ts 中全局引入,另一种是在 单独的 .vue文件中按需引入
// main.ts
import TVideoBase from "@p-base/video-base";
import '@p-base/video-base/dist/cjs/index.css';
createApp(App).use(TVideoBase);


// xx.vue
import TVideoBase from "@p-base/video-base";
export default defineComponent({
  components: {
    TVideoBase,
  },
})

  • 使用标题组件:
      <!-- 轮播视频 -->
      <div v-if="videoData">
        <swiper :pagination="{ clickable: true }" :autoplay="true">
          <swiper-slide v-for="(item, index) in videoData" :key="index" class="t-mb16">
          <t-video-base :data="item"
          :c-style="{
            wrapper:{
              width:'100%',
              height: '240px'
            }
          }" />
          </swiper-slide>
        </swiper>
      </div>
      <div v-if="!videoData">暂无内容!</div>

// 视频封面
const videoCover = require('@/assets/images/icons/video-cover.png').default;

setup() {
    const state = reactive({
      player: [] as any[], // video.js 相关 - 播放器
    });

    // 视频数据
    const videoData: any = ref([]);

/**
 * 判断视频格式
 */
const checkTV = (str: string) => {
  let type;
  const index = str.lastIndexOf('.');
  const last = str.substr(index);
  if (last === '.MP4' || last === '.mp4') {
    type = 'video/mp4';
  } else if (last === '.m3u8' || last === '.M3U8') {
    type = 'application/x-mpegURL';
  }
  return type;
};

    /**
    * 获取视频信息
    * @params code 点位编码
    */
    const getAnimalDetail = (code: string | number) => {
      const params = {
        code, // 点位编码
        videoPeriods: state.numberPeriodsVideo, // 视频期数
      };
      getAnimalDetailJson(params).then((data) => {
        const res: any = data.data;
        if (res && res.code === 200) {
          // 视频信息
          videoData.value = res.data.data.videoList.map((item: any) => ({
            // 用户自行传入 videojs 的配置
            videojs: {
              aspectRatio: '16:7',
              height: 300,
              autoplay: true,
              controls: false,
              sources: [{
                src: item.url,
                type: checkTV(item.url), // 判断视频格式
              }],
            },
            videoName: '物种详情',
            desc: '这是物种详情的视频',
            coverSrc: videoCover,
          }));
        }
      });
    };

    return {
      ...toRefs(state),
      videoData,
    };

}
  • 注意:
  • 如果仅仅设置了 videoSrc,视频会采用 videoSrc 的源文件
  • 如果同时设置了 videoSrc 及 videojs,视频会优先采用 videojs 中的配置项

标签:视频,const,示例,js,video,全屏,Vue3,组件,videojs
来源: https://blog.csdn.net/Lyrelion/article/details/118574462

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有