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

目录

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 中的配置项
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值