vue.js3: 自定义video可拖动的进度条(vue@3.2.36)


一,js代码:

<template>
<div>
  <div style="position:relative;">

  <video ref="homeVideo"
         style="position: absolute; width: 100vh; height: 100vw; left: -60vw; top:60vw;transform: rotate(90deg);"
         poster="static/video/cover.png" id="video"
         class="video" src="static/video/guotou.mp4" type="video/mp4"
         playsinline="true" webkit-playsinline="true"
         @canplay="getVideoInfo"
         @timeupdate="getCurrentInfo"
         x5-video-player-fullscreen="true" x5-video-orientation="portraint"
         loop="-1">
    <p>你的浏览器不支持video标签.p>
  video>

  <div id="controls" style="top:200px;width:100%;height:50px;background: rgb(255, 255, 0); position: absolute; z-index: 99;">
       <button @click="playOrPause">playOrPausebutton>
       <button @click="openOrSlient">开关音量button>
       <button @click="fullScreen">全屏button>
       <div>
         总时长:{{duration}}
         当前位置:{{currentTime}}
       div>

       <div style="width:100%;margin-left:10px;">
         
         <div id="control" ref="control" class="control" @click="setProgress" @touchmove="controlMove" @touchend="controlEnd">
           
           <div class="progress" :style="{background:'#00ff00', height:'10px',width: progressWidth+'px' }">
           div>
             
             <div class="slider_circle" :style="{left: (progressWidth-10)+'px' }" @touchstart="sliderStart" />
             <div class="slider_circle_large" :style="{left: (progressWidth - 25)+'px' }" @touchstart="sliderStart" />
         div>
       div>
  div>

  div>
div>
template>

<script>
import { ref } from "vue";

export default {
  name: "Video2Img",
  components: {

  },
  setup() {
    //video的ref
    const homeVideo = ref(null);

    //播放暂停
    const playOrPause = () => {
        console.log("homeVideo.value.paused:"+homeVideo.value.paused);
        if (homeVideo.value.paused == true) {
            homeVideo.value.play();
        } else {
            homeVideo.value.pause();
        }
    }

    //开关音量
    const openOrSlient = () => {
      console.log("muted1:"+homeVideo.value.muted);
      if (homeVideo.value.muted == true) {
        homeVideo.value.muted = false;
      } else {
        homeVideo.value.muted = true;
      }
    }

    //全屏
    const fullScreen = () => {
      homeVideo.value.requestFullscreen();
    }

    //显示总时长
    const duration = ref(0);

    //得到视频的时长等信息
    const getVideoInfo = () => {
      //console.log(parseInt(homeVideo.value.duration)); // 101
      //console.log(formatTime(parseInt(homeVideo.value.duration))); // 01:41
      duration.value = formatTime(parseInt(homeVideo.value.duration));
    }

    //显示播放到的当前时间
    const currentTime = ref(0);

    //滑动到位置的宽度
    const progressWidth = ref(0);
    //得到视频当前播放到哪里的信息
    const getCurrentInfo = () => {
      // 视频已经播放时长
      currentTime.value = formatTime(parseInt(homeVideo.value.currentTime));

      //计算得到宽度:
      let ratio = homeVideo.value.currentTime / homeVideo.value.duration;
      //console.log("ratio:"+ratio);
      let allWidth = document.getElementById('control').getBoundingClientRect().width;
      //console.log("allwidth:"+allWidth);
      progressWidth.value = allWidth * ratio;
      //console.log("progressWidth:"+progressWidth.value);
    }

    // 格式化时间函数
    const formatTime = (s) => {
      var t;
      if(s > -1) {
        var hour = Math.floor(s / 3600);
        var min = Math.floor(s / 60) % 60;
        var sec = s % 60;
        if(hour < 0 || hour == 0) {
          t = '';
        } else if(0 < hour < 10) {
          t = '0' + hour + ":";
        } else {
          t = hour + ":";
        }

        if(min < 10) {
          t += "0";
        }
        t += min + ":";
        if(sec < 10) {
          t += "0";
        }
        t += sec;
      }
      return t;
    }

    //设置进度条的长度
    const setProgress = (e) => {
      e.preventDefault()
      //
      const {left, width } = document.getElementById('control').getBoundingClientRect()
      // left: 进度条容器control到最左侧的距离
      //15: 按钮宽度的一半
      const proWidth = e.clientX - left
      progressWidth.value = proWidth;

      updadteCurrentTime(progressWidth.value, width);
    }

    //设置视频播放到指定的长度
    const updadteCurrentTime = (progressWidth, width) => {
      let dest = (progressWidth / width) *  homeVideo.value.duration;
      //console.log("dest:"+dest);
      homeVideo.value.currentTime = Math.floor(dest);
    }

    //移动的类型,用来标明是否在拖动进度条
    const moveType = ref("");
    //拖动开始时点击的位置
    const moveOffsetX = ref(0);
    //拖动开始时进度条的宽度
    const curWidth = ref(0);
    //当开始触摸开始在圆点按下时
    const sliderStart = (e) => {
        e.preventDefault();
      moveOffsetX.value = e.touches[0].clientX;
      moveType.value = "slider";
      curWidth.value = progressWidth.value;
    }

    //当触摸在controls上移动时
    const controlMove = (e) => {
      if (moveType.value !== 'slider') {
        return false;
      }
      e.preventDefault()
      // 滑动距离可视区域左侧的距离
      const X = e.touches[0].clientX
      //得到本次拖动已经过的距离
      const cl = X - moveOffsetX.value;
      //容器的宽度
      const {width } = document.getElementById('control').getBoundingClientRect()
      //得到已拖动到的宽度
      const ml =  curWidth.value + cl
      let proWidth
      if (ml <= 0) {
        //进度条长度最小和最大值的界定
        proWidth = 0
      } else if (ml >= width) {
        proWidth = width
      } else {
        proWidth = ml
      }
      progressWidth.value = proWidth;
      // 更新当前时间
      updadteCurrentTime(progressWidth.value, width);
    }

    //滑动结束
    const controlEnd = () => {
      moveType.value = "";
    }

    return {
      homeVideo,
      playOrPause,
      openOrSlient,
      fullScreen,
      getVideoInfo,
      duration,
      getCurrentInfo,
      currentTime,
      progressWidth,
      setProgress,
      //拖动
      sliderStart,
      controlMove,
      controlEnd,
    }
  }
}
script>

<style scoped>
.control {
   width: calc(100% - 20px);
   background: #ff0000;
   height: 10px;
  position: relative;
}
.slider_circle {
  position: absolute;
  width:20px;
  height: 20px;
  border-radius: 10px;
  border: 1px;
  background: #0000ff;
  margin-top: -15px;
}

.slider_circle_large {
  position: absolute;
  width:50px;
  height: 50px;
  border-radius: 25px;
  border: 1px;
  background: #00ffff;
  opacity: 0.8;
  top: -20px;
  left:-20px;
}

style>

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

         对应的源码可以访问这里获取: https://github.com/liuhongdi/
         或: https://gitee.com/liuhongdi

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,测试效果

三,查看vue框架的版本:

liuhongdi@lhdpc:/data/vue/imgtouch$ npm list vue
imgtouch@0.1.0 /data/vue/imgtouch
├─┬ @vue/cli-plugin-babel@5.0.4
│ └─┬ @vue/babel-preset-app@5.0.4
│   └── vue@3.2.36 deduped
├─┬ element-plus@2.2.2
│ ├─┬ @element-plus/icons-vue@1.1.4
│ │ └── vue@3.2.36 deduped
│ ├─┬ @vueuse/core@8.6.0
│ │ ├─┬ @vueuse/shared@8.6.0
│ │ │ └── vue@3.2.36 deduped
│ │ ├─┬ vue-demi@0.13.1
│ │ │ └── vue@3.2.36 deduped
│ │ └── vue@3.2.36 deduped
│ └── vue@3.2.36 deduped
└─┬ vue@3.2.36
  └─┬ @vue/server-renderer@3.2.36
    └── vue@3.2.36 deduped