Bootstrap

vue&react视频截图

使用原生js+canvas实现时实视频截图,图片选择,图片预览等功能,js部分可兼容vue和react。

实现思路:

  1. canvas 有自己的drawImage 方法 可以传入五个参数 第一个就是video,所以要先获取video的元素。
  2. 使用canvas drawImage方法创建好了后 在通过createEelement创建一个image,使用canvas.toDataURL生成url。
  3. 生成出的url是base64格式不能实现在线预览,所以要将base64转为blob,在将blob转换成file对象,有了file对象就可以使用URL.createObjectURL生成可预览的url。

页面元素

<div class="home">
    <el-row>
      <el-col :span="24">
        <!--  视频    -->
        <video class="videoName" controls src="../assets/monster.mp4"></video>
      </el-col>
    </el-row>
    <el-row>
      <el-col :span="24">
        <!--   截图   -->
        <el-button type="primary" round @click="screenshot">点击截图</el-button>
        <!--   选择   -->
        <el-button
          type="success"
          round
          @click="clickModel"
          :close-on-click-modal="false"
          :close-on-press-escape="false"
          v-if="selectedList.length !== 0"
          >选择完成</el-button
        >
      </el-col>
    </el-row>
    <el-row :gutter="10" style="margin-top: 10px">
       <!--  截图    -->
      <el-col :span="4" v-for="item in imagesUrl" :key="item" id="row-item">
        <div
          :class="`demo-image__placeholder item-xxx${item.id}`"
          @click="selected(item, `.item-xxx${item.id}`)"
          :key="item.id"
        >
          <div class="block">
            <p class="demonstration">{{ item.time }}</p>
            <p class="demonstration">{{ item.videoTime }}</p>
            <el-image :src="item.url" fit="cover" />
          </div>
        </div>
      </el-col>
    </el-row>

    <el-dialog
      v-model="dialogVisible"
      title="图片操作"
      width="70%"
      :fullscreen="true"
      :draggable="true"
      :before-close="handleClose"
    >
      <el-row>
        <el-col :span="24">
          <p>
            {{
              `共选择${selectedList.length}张图片 剩余${selectedList.length}张图片未处理`
            }}
          </p>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <div class="canvas-view">
            <canvas
              id="myCanvas"
              ref="myCanvas"
            >
            </canvas>
          </div>
        </el-col>
      </el-row>
      <el-row style="margin-top: 24px" v-if="selectedList">
        <el-col :span="24">
          <el-carousel
            :autoplay="false"
            type="card"
            arrow="never"
            height="350px"
          >
            <el-carousel-item
              v-for="item in selectedList"
              :key="item"
              @click="carouselChange(item)"
            >
              <el-image :src="item.url" fit="cover" />
            </el-carousel-item>
          </el-carousel>
        </el-col>
      </el-row>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">关闭</el-button>
          <el-button type="primary" @click="dialogVisible = false"
            >保存</el-button
          >
        </span>
      </template>
    </el-dialog>
  </div>

初始化数据

data() {
    return {
       //截图数据
      imagesUrl: [],
        //选中数据
      selectedList: [],
        //弹出窗
      dialogVisible: false,
    };
  },

页面事件

// 截图
screenshot() {
      const video = document.querySelector("video");
      let canvas = document.createElement("canvas");
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas
        .getContext("2d")
        .drawImage(video, 0, 0, canvas.width, canvas.height);
      const img = document.createElement("img");
      img.src = canvas.toDataURL("image/png");
       //转blob
      const blob = this.baseToBlob(img.src);
        //file 对象 方便提交数据
      const files = new File(
        [blob],
        `${new Date().Format("yyyy-MM-dd HH:mm:ss")}  ${(
          video.currentTime / 100
        ).toFixed(2)}/${(video.duration / 60 - 0.03).toFixed(2)}`,
        {
          type: "image/png",
          lastModified: Date.now(),
        }
      );
      //需要在页面渲染的截图数据数组
      this.imagesUrl.push({
        id: this.imagesUrl.length + 1,
        time: `截图时间:${new Date().Format("yyyy-MM-dd HH:mm:ss")}  `,
        videoTime: `截图视频时间:${(video.currentTime / 100).toFixed(2)}/${(
          video.duration / 60 -
          0.03
        ).toFixed(2)}`,
        url: URL.createObjectURL(files),
        file: files,
      });
    },
    //选中
    selected(params, docs) {
      console.log(docs);
      const doc = document.querySelector(docs);
      const isIndex = this.selectedList.findIndex(
        (item) => item.id === params.id
      );
      if (isIndex === -1) {
        doc.style.borderStyle = "solid";
        doc.style.borderColor = "#42b983";
        this.selectedList.push(params);
      } else {
        doc.style.borderStyle = "none";
        this.selectedList.splice(isIndex, 1);
      }
    },
    clickModel(url) {
      if (!this.dialogVisible) {
        this.dialogVisible = true;
        this.changeURL(url);
      } else {
        this.changeURL(url);
      }
    },
    //排除未选择的数据保留在页面上
    handleClose() {
      this.dialogVisible = false;
      const arr = this.imagesUrl.concat(this.selectedList);
      this.selectedList = [];
      this.imagesUrl = arr.filter((item) => {
        return arr.indexOf(item) === arr.lastIndexOf(item);
      });
    },
    changeURL(url) {
      this.$nextTick(() => {
        const canvasEvent = this.$refs.myCanvas;
        const imageObj = new Image();
        if (typeof url !== "object") {
          imageObj.src = url;
        } else {
          imageObj.src = this.selectedList[0].url;
        }
        canvasEvent
          .getContext("2d")
          .drawImage(imageObj, 0, 0, canvasEvent.width, canvasEvent.height);
      });
    },
    carouselChange(val) {
      this.clickModel(val.url);
    },
    //转blob
    baseToBlob(data) {
      let arr = data.split(","),
        fileType = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        l = bstr.length,
        u8Arr = new Uint8Array(l);

      while (l--) {
        u8Arr[l] = bstr.charCodeAt(l);
      }
      return new Blob([u8Arr], {
        type: fileType,
      });
    },

css

.videoName {
  height: 500px;
}

#myCanvas {
  width: 800px;
  height: 400px;
}
.demo-image__placeholder {
  padding: 5px;
  cursor: pointer;
}

;