vue实现画笔回放canvas转视频播放功能

 更新时间:2024年01月04日 11:54:53   作者:禾小毅  
这篇文章主要介绍了vue实现画笔回放,canvas转视频播放功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

示例图:

一、vue2版本

<template>
  <div class="canvas-video">
    <canvas
      ref="myCanvasByVideo"
      class="myCanvas"
      id="myCanvasByVideo"
      :width="width"
      :height="height"
    ></canvas>
    <div class="btnDiv">
      <div
        v-if="!isPlayVideo && !isStartVideo"
        class="playback"
        @click.stop="playVideo"
      >
        <div>回放</div>
        <img src="@/assets/image/play.png" alt="" />
      </div>
      <div
        v-if="isPlayVideo && isStartVideo"
        class="playback"
        @click.stop="pauseVideo"
      >
        <div>暂停</div>
        <img src="@/assets/image/pause.png" alt="" />
      </div>
      <div
        v-if="!isPlayVideo && isStartVideo"
        class="playback"
        @click.stop="continueVideo"
      >
        <div>继续</div>
        <img src="@/assets/image/play.png" alt="" />
      </div>
      <div class="rocket">
        <img
          v-show="isRocket"
          src="@/assets/image/rocket.png"
          alt=""
          @click="playRocket"
        />
        <img
          v-show="!isRocket"
          src="@/assets/image/rocket_noChoose.png"
          alt=""
          @click="playRocket"
        />
      </div>
      <div class="mySlider">
        <el-slider
          v-model="nowTime"
          :max="allTime"
          @change="changeVideoSilder"
        ></el-slider>
      </div>
      <div class="myTime">
        {{ getFormatTime(nowTime) }} / {{ getFormatTime(allTime) }}
      </div>
      <div class="mySpeed">
        <div @click.stop="isShowSpeedBox = !isShowSpeedBox">
          {{ speedList.filter((item) => item.value === nowSpeed)[0].name }}
        </div>
        <div class="speedList" v-show="isShowSpeedBox">
          <div
            class="speedItem"
            :class="item.value === nowSpeed ? 'active' : ''"
            v-for="(item, index) in speedList"
            :key="index"
            @click.stop="changeSpeed(item.value)"
          >
            {{ item.name }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "canvasVideo",
  components: {},
  props: {
    width: {
      type: Number,
      default: 500,
    },
    height: {
      type: Number,
      default: 500,
    },
    lineWidth: {
      type: Number,
      default: 1,
    },
    backgroundColor: {
      type: String,
      default: "black",
    },
    color: {
      type: String,
      default: "red",
    },
    pointData: {
      type: Array,
      default: [
        [
          {
            x: 144.42779541015625,
            y: 112.7576904296875,
            time: 1702536449825,
          },
        ],
      ],
    },
  },
  data() {
    return {
      canvasHistory: null,
      myCanvasByVideo: null, // 视频播放画布
      ctxByVideo: null, // 视频播放画布
      drawLineTimer: null,
      drawStepTimer: null,
      isPlayVideo: false, // 是否正在播放
      isStartVideo: false, // 是否开始播放
      nowTime: 0, // 当前播放时间
      allTime: 0, // 回放总时间
      nowPoints: [], // 当前学生视频的所有绘制点坐标
      indexStep: 0, // 当前播放绘制线条的下标
      indexPoint: 0, // 当前播放绘制点的下标
      nowTimer: null, // 计算当前播放时间的定时器
      isShowSpeedBox: false, // 是否展示速度调整列表
      nowSpeed: 1, // 当前速度
      speedList: [
        {
          name: "3X",
          value: 3,
        },
        {
          name: "2X",
          value: "2",
        },
        {
          name: "1.5X",
          value: 1.5,
        },
        {
          name: "1X",
          value: 1,
        },
        {
          name: "0.5X",
          value: 0.5,
        },
      ],
      isRocket: false, // 是否快速播放
      gdbl: 2.4583, // 两种纸的坐标对应比例  小纸 2.4583 = 4720/1920  大纸 2.9167 = 5600/1920
    };
  },
  mounted() { 
    this.nowPoints  = this.pointData
    this.initCanvasByVideo();
    this.allTime = Math.ceil(
      (this.nowPoints[this.nowPoints.length - 1][
        this.nowPoints[this.nowPoints.length - 1].length - 1
      ].time -
        this.nowPoints[0][0].time) /
        1000
    ); // 最后一个坐标的时间 - 第一个坐标的时间
  },
  methods: {
    // 快速播放
    async playRocket() {
      // 把所有状态清零
      this.isPlayVideo = false;
      this.isStartVideo = false;
      this.indexStep = 0;
      this.indexPoint = 0;
      if (this.nowTimer) {
        clearInterval(this.nowTimer);
      }
      if (this.drawStepTimer) {
        clearTimeout(this.drawStepTimer);
      }
      if (this.drawLineTimer) {
        clearTimeout(this.drawLineTimer);
      }
      this.nowTime = 0;
      if (this.isRocket) {
        this.isRocket = false;
        return;
      }
      this.isPlayVideo = false;
      this.isRocket = true;
      this.ctxByVideo.strokeStyle = this.color;
      this.ctxByVideo.translate(0.5, 0.5); // 抗锯齿(好像没得卵用)
      this.ctxByVideo.antialias = "smooth"; // 抗锯齿(好像没得卵用)
      this.ctxByVideo.imageSmoothingEnabled = true; // 抗锯齿(好像没得卵用)
      this.ctxByVideo.lineJoin = "round"; // 设置圆角
      this.ctxByVideo.lineWidth = this.lineWidth; // 设置粗细
      this.ctxByVideo.shadowBlur = 1;
      this.ctxByVideo.shadowColor = this.color;
      this.ctxByVideo.clearRect(
        0,
        0,
        this.myCanvasByVideo.width,
        this.myCanvasByVideo.height
      ); // 清空
      if (this.drawStepTimer) {
        clearTimeout(this.drawStepTimer);
      }
      if (this.drawLineTimer) {
        clearTimeout(this.drawLineTimer);
      }
      for (let j = 0; j < this.nowPoints.length; j++) {
        this.indexStep = j;
        this.ctxByVideo.beginPath();
        let timeout = 0;
        if (j !== 0) {
          if (this.nowPoints[j].length && this.nowPoints[j - 1].length) {
            timeout =
              this.nowPoints[j][0].time -
              this.nowPoints[j - 1][this.nowPoints[j - 1].length - 1].time;
          }
        }
        await this.drawStepByRocket(this.nowPoints[j], 50);
      }
      this.isRocket = false;
    },
    //答题笔迹
    async playVideo() {
      if (this.isRocket) {
        this.isPlayVideo = false;
        clearInterval(this.nowTimer);
        clearTimeout(this.drawLineTimer);
        clearTimeout(this.drawStepTimer);
        this.myCanvasByVideo.width = this.myCanvasByVideo.width; // 清空
        this.canvasHistory = null;
        // 清空操作
        this.nowTime = 0;
        this.isPlayVideo = false;
        this.isStartVideo = false;
        this.indexStep = 0;
        this.indexPoint = 0;
        clearInterval(this.nowTimer);
        this.canvasHistory = null;
        if (this.nowTimer) {
          clearInterval(this.nowTimer);
        }
        if (this.drawStepTimer) {
          clearTimeout(this.drawStepTimer);
        }
        if (this.drawLineTimer) {
          clearTimeout(this.drawLineTimer);
        }
      }
      this.isRocket = false;
      this.isPlayVideo = true;
      this.isStartVideo = true;
      this.ctxByVideo.strokeStyle = this.color;
      this.ctxByVideo.translate(0.5, 0.5); // 抗锯齿(好像没得卵用)
      this.ctxByVideo.antialias = "smooth"; // 抗锯齿(好像没得卵用)
      this.ctxByVideo.imageSmoothingEnabled = true; // 抗锯齿(好像没得卵用)
      this.ctxByVideo.lineJoin = "round"; // 设置圆角
      this.ctxByVideo.lineWidth = this.lineWidth; // 设置粗细
      this.ctxByVideo.shadowBlur = 1;
      this.ctxByVideo.shadowColor = this.color;
      this.ctxByVideo.clearRect(
        0,
        0,
        this.myCanvasByVideo.width,
        this.myCanvasByVideo.height
      ); // 清空
      this.nowTimer = setInterval(() => {
        if (this.nowTime < this.allTime) {
          this.nowTime++;
        }
      }, 1000 / this.nowSpeed);
      for (let j = 0; j < this.nowPoints.length; j++) {
        this.indexStep = j;
        this.ctxByVideo.beginPath();
        let timeout = 0;
        if (j !== 0) {
          if (this.nowPoints[j].length && this.nowPoints[j - 1].length) {
            timeout =
              this.nowPoints[j][0].time -
              this.nowPoints[j - 1][this.nowPoints[j - 1].length - 1].time;
          }
        }
        await this.drawStep(this.nowPoints[j], timeout / this.nowSpeed);
      }
      this.nowTime = this.allTime;
      this.isPlayVideo = false;
      clearInterval(this.nowTimer);
    },
    // 暂停播放
    pauseVideo() {
      this.isPlayVideo = false;
      clearInterval(this.nowTimer);
      clearTimeout(this.drawLineTimer);
      clearTimeout(this.drawStepTimer);
      this.ctxByVideo.stroke();
    },
    // 继续播放
    async continueVideo() {
      if (this.nowTime === this.allTime) {
        // 播放完了重新播放
        this.nowTime = 0;
        this.playVideo();
        return;
      }
      this.isPlayVideo = true;
      let startIndex = JSON.parse(JSON.stringify(this.indexStep));
      this.nowTimer = setInterval(() => {
        if (this.nowTime < this.allTime) {
          this.nowTime++;
        }
      }, 1000 / this.nowSpeed);
      console.log("从这开始", this.indexStep, this.nowPoints);
      // this.ctx.moveTo(0, 0)
      // this.ctx.lineTo(500, 500)
      // this.ctx.stroke();
      // this.ctx.closePath();
      for (let j = startIndex; j < this.nowPoints.length; j++) {
        this.indexStep = j;
        let timeout = 0;
        if (j !== 0) {
          if (this.nowPoints[j].length && this.nowPoints[j - 1].length) {
            timeout =
              this.nowPoints[j][0].time -
              this.nowPoints[j - 1][this.nowPoints[j - 1].length - 1].time;
          }
        }
        await this.drawStep(this.nowPoints[j], timeout / this.nowSpeed);
      }
      this.nowTime = this.allTime;
      clearInterval(this.nowTimer);
      this.isPlayVideo = false;
    },
    // 改变进度条(根据当前时间获取)
    async changeVideoSilder() {
      console.log("改变了");
      clearInterval(this.nowTimer);
      clearTimeout(this.drawLineTimer);
      clearTimeout(this.drawStepTimer);
      this.ctxByVideo.clearRect(
        0,
        0,
        this.myCanvasByVideo.width,
        this.myCanvasByVideo.height
      ); // 清空
      this.ctxByVideo.strokeStyle = this.color;
      this.ctxByVideo.translate(0.5, 0.5); // 抗锯齿(好像没得卵用)
      this.ctxByVideo.antialias = "smooth"; // 抗锯齿(好像没得卵用)
      this.ctxByVideo.imageSmoothingEnabled = true; // 抗锯齿(好像没得卵用)
      this.ctxByVideo.lineJoin = "round"; // 设置圆角
      this.ctxByVideo.lineWidth = this.lineWidth; // 设置粗细
      this.ctxByVideo.shadowBlur = 1;
      this.ctxByVideo.shadowColor = this.color;
      let allTime = 0;
      // 直接把进度条当前定位之前的画出来不加任何延时
      here: for (let i = 0; i < this.nowPoints.length; i++) {
        this.ctxByVideo.beginPath();
        if (i) {
          allTime +=
            this.nowPoints[i][0].time -
            this.nowPoints[i - 1][this.nowPoints[i - 1].length - 1].time;
          console.log("1级时间", allTime, i);
        }
        for (let j = 0; j < this.nowPoints[i].length; j++) {
          if (j) {
            allTime +=
              this.nowPoints[i][j].time - this.nowPoints[i][j - 1].time;
            console.log("2级时间", allTime, i, j);
          }
          if (j !== this.nowPoints[i].length - 1) {
            this.ctxByVideo.moveTo(
              this.nowPoints[i][j].x / this.gdbl,
              this.nowPoints[i][j].y / this.gdbl
            );
            this.ctxByVideo.lineTo(
              this.nowPoints[i][j + 1].x / this.gdbl,
              this.nowPoints[i][j + 1].y / this.gdbl
            );
            this.ctxByVideo.stroke();
            this.ctxByVideo.closePath();
          }
          if (allTime >= this.nowTime * 1000) {
            this.indexStep = i;
            this.indexPoint = j;
            break here;
          }
        }
      }
      if (this.isPlayVideo) {
        // 如果是播放状态 则继续播放
        this.continueVideo();
      }
    },
    drawStep(data, time) {
      return new Promise((resolve) => {
        this.drawStepTimer = setTimeout(async () => {
          let startIndex = JSON.parse(JSON.stringify(this.indexPoint));
          for (let i = startIndex; i < data.length - 1; i++) {
            this.indexPoint = i;
            let timeLine = data[i + 1].time - data[i].time;
            if (timeLine) {
              // 点阵笔有很多点位不同但时间相同的点 不做异步处理
              await this.drawLine(
                data[i].x / this.gdbl,
                data[i].y / this.gdbl,
                data[i + 1].x / this.gdbl,
                data[i + 1].y / this.gdbl,
                (data[i].x + data[i + 1].x) / 2 / this.gdbl,
                (data[i].y + data[i + 1].y) / 2 / this.gdbl,
                timeLine / this.nowSpeed
              );
            } else {
              this.ctxByVideo.moveTo(
                data[i].x / this.gdbl,
                data[i].y / this.gdbl
              );
              this.ctxByVideo.lineTo(
                data[i + 1].x / this.gdbl,
                data[i + 1].y / this.gdbl
              );
              this.ctxByVideo.stroke();
              this.ctxByVideo.closePath();
            }
          }
          this.indexPoint = 0;
          this.ctxByVideo.stroke();
          resolve();
        }, time);
      });
    },
    // 相同速度播放
    drawStepByRocket(data, time) {
      return new Promise((resolve) => {
        this.drawStepTimer = setTimeout(async () => {
          let startIndex = JSON.parse(JSON.stringify(this.indexPoint));
          for (let i = startIndex; i < data.length - 1; i++) {
            this.indexPoint = i;
            let timeLine = data[i + 1].time - data[i].time;
            if (timeLine) {
              // 点阵笔有很多点位不同但时间相同的点 不做异步处理
              await this.drawLine(
                data[i].x / this.gdbl,
                data[i].y / this.gdbl,
                data[i + 1].x / this.gdbl,
                data[i + 1].y / this.gdbl,
                (data[i].x + data[i + 1].x) / 2 / this.gdbl,
                (data[i].y + data[i + 1].y) / 2 / this.gdbl,
                5
              );
            } else {
              this.ctxByVideo.moveTo(
                data[i].x / this.gdbl,
                data[i].y / this.gdbl
              );
              this.ctxByVideo.lineTo(
                data[i + 1].x / this.gdbl,
                data[i + 1].y / this.gdbl
              );
              this.ctxByVideo.stroke();
              this.ctxByVideo.closePath();
            }
          }
          this.indexPoint = 0;
          this.ctxByVideo.stroke();
          resolve();
        }, time);
      });
    },
    drawLine(x1, y1, x2, y2, controlX, controlY, time) {
      return new Promise((resolve) => {
        this.drawLineTimer = setTimeout(() => {
          this.ctxByVideo.moveTo(x1, y1);
          this.ctxByVideo.lineTo(x2, y2);
          // this.ctx.quadraticCurveTo(controlX, controlY, x2, y2)
          this.ctxByVideo.stroke();
          this.ctxByVideo.closePath();
          resolve();
        }, time);
      });
    },
    // 改变播放速度
    changeSpeed(speed) {
      this.nowSpeed = speed;
      this.isShowSpeedBox = false;
      if (this.nowTimer) {
        clearInterval(this.nowTimer);
        if (this.isPlayVideo) {
          this.nowTimer = setInterval(() => {
            if (this.nowTime < this.allTime) {
              this.nowTime++;
            }
          }, 1000 / this.nowSpeed);
        }
      }
    },
    getFormatTime(second) {
      this.formatSecond(second);
    },
    initCanvasByVideo() {
      this.myCanvasByVideo = document.getElementById("myCanvasByVideo");
      this.ctxByVideo = this.myCanvasByVideo.getContext("2d");
    },
    formatSecond(allSecond) {
      let minute = 0;
      let second = 0;
      second = allSecond % 60;
      if (second < 10) {
        second = "0" + second;
      }
      minute = Math.trunc(allSecond / 60);
      if (minute < 10) {
        minute = "0" + minute;
      }
      return minute + ":" + second;
    },
  },
};
</script>
<style lang="scss" scoped>
.canvas-video {
  width: 100%;
  height: 100%;
  .myCanvas {
    background: black;
  }
  .btnDiv {
    display: flex;
    justify-content: center;
    margin-top: 5px;
    .playback {
      display: flex;
      justify-content: space-between;
      color: #ffffff;
      text-align: center;
      padding: 0 15px;
      border-radius: 4px;
      background: #00b386;
      height: 28px;
      line-height: 28px;
      cursor: pointer;
      img {
        width: 10px;
        height: 14px;
        margin-top: 7px;
        margin-left: 12px;
      }
    }
    .rocket {
      margin-top: 3px;
      margin-left: 10px;
      cursor: pointer;
    }
    .mySlider {
      width: 500px;
      margin-left: 20px;
      ::v-deep .el-slider__bar {
        border-radius: 17px;
        background: #00b386;
      }
      ::v-deep .el-slider__button {
        border: 1px solid #00b386;
      }
      ::v-deep .el-slider__runway {
        border-radius: 17px;
        background: #444;
      }
    }
    .myTime {
      margin-left: 14px;
      color: #a9a9a9;
      line-height: 35px;
    }
    .mySpeed {
      cursor: pointer;
      position: relative;
      border-radius: 2px;
      background: #ef8714;
      width: 60px;
      height: 20px;
      line-height: 20px;
      text-align: center;
      color: #ffffff;
      margin-left: 20px;
      margin-top: 5px;
      .speedList {
        position: absolute;
        bottom: 20px;
        border-radius: 2px;
        background: #232322;
        padding: 10px 0;
        width: 60px;
        .speedItem {
          width: 100%;
          text-align: center;
          margin-bottom: 10px;
          color: #ffffff;
        }
        .speedItem.active {
          color: #ef8714;
          font-size: 14px;
        }
        .speedItem:last-child {
          margin-bottom: 0px;
        }
        z-index: 2000;
      }
    }
    .slider-warpper {
      width: 320px;
      height: 16px;
      position: absolute;
      left: 280px;
      // background: #ef9e00 !important;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      .slider-content {
        width: 304px;
        height: 16px;
        background: #2d2d2d;
        border: 1px solid rgba(255, 186, 33, 0.16);
        border-radius: 22px;
        position: relative;
        .slider {
          border-radius: 22px;
          position: absolute;
          left: 0;
          top: 0;
          width: 30px;
          height: 16px;
          background: #ffba21;
        }
      }
      .persent {
        margin-left: 10px;
        color: #ffba21;
        font-size: 18px;
        font-weight: 600;
        letter-spacing: 1.26px;
      }
    }
  }
}
</style>

二、vue3版本

<template>
    <div class="canvas-video" :style="{width:props.width+'px',height:props.height+'px'}">
        <img :src="props.backgroundColor" alt="">
        <canvas ref="myCanvasByVideo" class="myCanvas" id="myCanvasByVideo" :width="props.width"></canvas>
    </div>
    <div class="btnDiv">
        <div v-if="!state.isPlayVideo && !state.isStartVideo" class="playback" @click.stop="playVideo">
            <div>回放</div>
        </div>
        <div v-if="state.isPlayVideo && state.isStartVideo" class="playback" @click.stop="pauseVideo">
            <div>暂停</div>
        </div>
        <div v-if="!state.isPlayVideo && state.isStartVideo" class="playback" @click.stop="continueVideo">
            <div>继续</div>
        </div>
        <div class="mySlider">
            <el-slider v-model="state.nowTime" :max="state.allTime" @change="changeVideoSilder"></el-slider>
        </div>
        <div class="myTime">{{getFormatTime(state.nowTime)}} / {{getFormatTime(state.allTime)}}</div>
        <!-- <div class="rocket playback">
            <div v-show="!state.isRocket" @click="playRocket">快速回播</div>
            <div v-show="state.isRocket" @click="playRocket">暂停播放</div>
        </div> -->
        <!-- <div class="mySpeed">
            <div @click.stop="state.isShowSpeedBox = !state.isShowSpeedBox">{{state.speedList.filter((item:any) => item.value === state.nowSpeed)[0].name}}</div>
            <div class="speedList" v-show="state.isShowSpeedBox">
                <div class="speedItem" :class="item.value === state.nowSpeed ? 'active' : ''" v-for="(item, index) in state.speedList" :key="index" @click.stop="changeSpeed(item.value)">
                    {{item.name}}
                </div>
            </div>
        </div> -->
    </div>
</template>
<script setup lang="ts">
import { onMounted, reactive } from 'vue'
type TProps = {
    width: number
    height: number,
    lineWidth: number,
    backgroundColor: string,
    color: string,
    pointData: any
}
const props = withDefaults(defineProps<TProps>(), {})
const state = reactive<any>({
    timeout:0,
    minute:0,
    second:0,
    canvasHistory: null,
    myCanvasByVideo: null, // 视频播放画布
    ctxByVideo: null, // 视频播放画布
    drawLineTimer: null,
    drawStepTimer: null,
    isPlayVideo: false, // 是否正在播放
    isStartVideo: false, // 是否开始播放
    nowTime: 0, // 当前播放时间
    allTime: 0, // 回放总时间
    nowPoints: [], // 当前学生视频的所有绘制点坐标
    indexStep: 0, // 当前播放绘制线条的下标
    indexPoint: 0, // 当前播放绘制点的下标
    nowTimer: null, // 计算当前播放时间的定时器
    isShowSpeedBox: false, // 是否展示速度调整列表
    nowSpeed: 1, // 当前速度
    speedList: [{
        name: '3X',
        value: 3
    }, {
        name: '2X',
        value: '2'
    }, {
        name: '1.5X',
        value: 1.5
    }, {
        name: '1X',
        value: 1
    }, {
        name: '0.5X',
        value: 0.5
    }],
    isRocket: false, // 是否快速播放
    gdbl: 2.4583 // 两种纸的坐标对应比例  小纸 2.4583 = 4720/1920  大纸 2.9167 = 5600/1920
})
onMounted(()=> {
    state.nowPoints = props.pointData
    initCanvasByVideo()
    state.allTime = Math.ceil((state.nowPoints[state.nowPoints.length - 1][state.nowPoints[state.nowPoints.length - 1].length - 1].time - state.nowPoints[0][0].time) / 1000) // 最后一个坐标的时间 - 第一个坐标的时间
})
// 快速播放
const playRocket = async () => {
    // 把所有状态清零
    state.isPlayVideo = false
    state.isStartVideo = false
    state.indexStep = 0
    state.indexPoint = 0
    if (state.nowTimer) {
        clearInterval(state.nowTimer)
    }
    if (state.drawStepTimer) {
        clearTimeout(state.drawStepTimer)
    }
    if (state.drawLineTimer) {
        clearTimeout(state.drawLineTimer)
    }
    state.nowTime = 0
    if (state.isRocket) {
        state.isRocket = false
        return
    }
    state.isPlayVideo = false
    state.isRocket = true
    state.ctxByVideo.strokeStyle = props.color
    state.ctxByVideo.translate(0.5, 0.5) // 抗锯齿(好像没得卵用)
    state.ctxByVideo.antialias = 'smooth' // 抗锯齿(好像没得卵用)
    state.ctxByVideo.imageSmoothingEnabled = true // 抗锯齿(好像没得卵用)
    state.ctxByVideo.lineJoin = 'round' // 设置圆角
    state.ctxByVideo.lineWidth = props.lineWidth // 设置粗细
    state.ctxByVideo.shadowBlur = 1;
    state.ctxByVideo.shadowColor = props.color;
    state.ctxByVideo.clearRect(0, 0, state.myCanvasByVideo.width, state.myCanvasByVideo.height); // 清空
    if (state.drawStepTimer) {
        clearTimeout(state.drawStepTimer)
    }
    if (state.drawLineTimer) {
        clearTimeout(state.drawLineTimer)
    }
    for (let j = 0; j < state.nowPoints.length; j++) {
        state.indexStep = j
        state.ctxByVideo.beginPath();
        state.timeout = 0
        if (j !== 0) {
            if (state.nowPoints[j].length && state.nowPoints[j - 1].length) {
                state.timeout = state.nowPoints[j][0].time - state.nowPoints[j - 1][state.nowPoints[j - 1].length - 1].time
            }
        }
        await drawStepByRocket(state.nowPoints[j], 50)
    }
    state.isRocket = false
}
//答题笔迹
const playVideo = async () => {
    if (state.isRocket) {
        state.isPlayVideo = false
        clearInterval(state.nowTimer)
        clearTimeout(state.drawLineTimer)
        clearTimeout(state.drawStepTimer)
        state.myCanvasByVideo.width = state.myCanvasByVideo.width // 清空
        state.canvasHistory = null
        // 清空操作
        state.nowTime = 0
        state.isPlayVideo = false
        state.isStartVideo = false
        state.indexStep = 0
        state.indexPoint = 0
        clearInterval(state.nowTimer)
        state.canvasHistory = null
        if (state.nowTimer) {
            clearInterval(state.nowTimer)
        }
        if (state.drawStepTimer) {
            clearTimeout(state.drawStepTimer)
        }
        if (state.drawLineTimer) {
            clearTimeout(state.drawLineTimer)
        }
    }
    state.isRocket = false
    state.isPlayVideo = true
    state.isStartVideo = true
    state.ctxByVideo.strokeStyle = props.color
    state.ctxByVideo.translate(0.5, 0.5) // 抗锯齿(好像没得卵用)
    state.ctxByVideo.antialias = 'smooth' // 抗锯齿(好像没得卵用)
    state.ctxByVideo.imageSmoothingEnabled = true // 抗锯齿(好像没得卵用)
    state.ctxByVideo.lineJoin = 'round' // 设置圆角
    state.ctxByVideo.lineWidth = props.lineWidth // 设置粗细
    state.ctxByVideo.shadowBlur = 1;
    state.ctxByVideo.shadowColor = props.color;
    state.ctxByVideo.clearRect(0, 0, state.myCanvasByVideo.width, state.myCanvasByVideo.height); // 清空
    state.nowTimer = setInterval(()=> {
        if (state.nowTime < state.allTime) {
            state.nowTime++
        }
    }, 1000 / state.nowSpeed)
    for (let j = 0; j < state.nowPoints.length; j++) {
        state.indexStep = j
        state.ctxByVideo.beginPath();
        state.timeout = 0
        if (j !== 0) {
            if (state.nowPoints[j].length && state.nowPoints[j - 1].length) {
                state.timeout = state.nowPoints[j][0].time - state.nowPoints[j - 1][state.nowPoints[j - 1].length - 1].time
            }
        }
        await drawStep(state.nowPoints[j], props.lineWidth / state.nowSpeed)
    }
    state.nowTime = JSON.parse(JSON.stringify(state.allTime))
    state.isPlayVideo = false
    clearInterval(state.nowTimer)
}
// 暂停播放
const pauseVideo = () => {
    state.isPlayVideo = false
    clearInterval(state.nowTimer)
    clearTimeout(state.drawLineTimer)
    clearTimeout(state.drawStepTimer)
    state.ctxByVideo.stroke()
}
// 继续播放
const continueVideo = async () => {
    if (state.nowTime === state.allTime) { // 播放完了重新播放
        state.nowTime = JSON.parse(JSON.stringify(state.allTime))
        pauseVideo()
        state.isPlayVideo = false
        state.isStartVideo = false
        state.isRocket = true
        return
    }
    state.isPlayVideo = true
    let startIndex = JSON.parse(JSON.stringify(state.indexStep))
    state.nowTimer = setInterval(()=> {
        if (state.nowTime < state.allTime) {
            state.nowTime++
        }
    }, 1000 / state.nowSpeed)
    console.log('从这开始', state.indexStep, state.nowPoints)
    // state.ctx.moveTo(0, 0)
    // state.ctx.lineTo(500, 500)
    // state.ctx.stroke();
    // state.ctx.closePath();
    for (let j = startIndex; j < state.nowPoints.length; j++) {
        state.indexStep = j
        state.timeout = 0
        if (j !== 0) {
            if (state.nowPoints[j].length && state.nowPoints[j - 1].length) {
                state.timeout = state.nowPoints[j][0].time - state.nowPoints[j - 1][state.nowPoints[j - 1].length - 1].time
            }
        }
        await drawStep(state.nowPoints[j], state.timeout / state.nowSpeed)
    }
    state.nowTime = JSON.parse(JSON.stringify(state.allTime))
    clearInterval(state.nowTimer)
    state.isPlayVideo = false
}
// 改变进度条(根据当前时间获取)
const changeVideoSilder = () => {
    console.log('改变了')
    clearInterval(state.nowTimer)
    clearTimeout(state.drawLineTimer)
    clearTimeout(state.drawStepTimer)
    state.ctxByVideo.clearRect(0, 0, state.myCanvasByVideo.width, state.myCanvasByVideo.height); // 清空
    state.ctxByVideo.strokeStyle = props.color
    state.ctxByVideo.translate(0.5, 0.5) // 抗锯齿(好像没得卵用)
    state.ctxByVideo.antialias = 'smooth' // 抗锯齿(好像没得卵用)
    state.ctxByVideo.imageSmoothingEnabled = true // 抗锯齿(好像没得卵用)
    state.ctxByVideo.lineJoin = 'round' // 设置圆角
    state.ctxByVideo.lineWidth = props.lineWidth // 设置粗细
    state.ctxByVideo.shadowBlur = 1;
    state.ctxByVideo.shadowColor = props.color;
    let allTime = 0
    // 直接把进度条当前定位之前的画出来不加任何延时
    here: for (let i = 0;i < state.nowPoints.length;i++) {
        state.ctxByVideo.beginPath();
        if (i) {
            allTime += (state.nowPoints[i][0].time - state.nowPoints[i - 1][state.nowPoints[i - 1].length - 1].time)
            // console.log('1级时间', allTime, i)
        }
        for (let j = 0;j < state.nowPoints[i].length;j++) {
            if (j) {
                allTime += (state.nowPoints[i][j].time - state.nowPoints[i][j - 1].time)
                // console.log('2级时间', allTime, i, j)
            }
            if (j !== state.nowPoints[i].length - 1) {
                state.ctxByVideo.moveTo(state.nowPoints[i][j].x / state.gdbl, state.nowPoints[i][j].y / state.gdbl)
                state.ctxByVideo.lineTo(state.nowPoints[i][j+1].x / state.gdbl, state.nowPoints[i][j+1].y / state.gdbl)
                state.ctxByVideo.stroke();
                state.ctxByVideo.closePath();
            }
            if (allTime >= (state.nowTime * 1000)) {
                state.indexStep = i
                state.indexPoint = j
                break here
            }
        }
    }
    if (state.isPlayVideo) { // 如果是播放状态 则继续播放
        continueVideo()
    }
}
const drawStep = (data:any, time:any) => {
    return new Promise((resolve) => {
        state.drawStepTimer = setTimeout(async () => {
            let startIndex = JSON.parse(JSON.stringify(state.indexPoint))
            for (let i = startIndex; i < data.length - 1; i++) {
                state.indexPoint = i
                let timeLine = data[i + 1].time - data[i].time
                if (timeLine) { // 点阵笔有很多点位不同但时间相同的点 不做异步处理
                    await drawLine(data[i].x / state.gdbl, data[i].y / state.gdbl, data[i + 1].x / state.gdbl, data[i + 1].y / state.gdbl, (data[i].x + data[i + 1].x) / 2 / state.gdbl, (data[i].y + data[i + 1].y) / 2 / state.gdbl, timeLine / state.nowSpeed)
                } else {
                    state.ctxByVideo.moveTo(data[i].x / state.gdbl, data[i].y / state.gdbl)
                    state.ctxByVideo.lineTo(data[i + 1].x / state.gdbl, data[i + 1].y / state.gdbl)
                    state.ctxByVideo.stroke()
                    state.ctxByVideo.closePath()
                }
            }
            state.indexPoint = 0
            state.ctxByVideo.stroke();
            resolve()
        }, time)
    })
}
// 相同速度播放
const drawStepByRocket = (data:any, time:any) => {
    return new Promise((resolve) => {
        state.drawStepTimer = setTimeout(async () => {
            let startIndex = JSON.parse(JSON.stringify(state.indexPoint))
            for (let i = startIndex; i < data.length - 1; i++) {
                state.indexPoint = i
                let timeLine = data[i + 1].time - data[i].time
                if (timeLine) { // 点阵笔有很多点位不同但时间相同的点 不做异步处理
                    await drawLine(data[i].x / state.gdbl, data[i].y / state.gdbl, data[i + 1].x / state.gdbl, data[i + 1].y / state.gdbl, (data[i].x + data[i + 1].x) / 2 / state.gdbl, (data[i].y + data[i + 1].y) / 2 / state.gdbl, 5)
                } else {
                    state.ctxByVideo.moveTo(data[i].x / state.gdbl, data[i].y / state.gdbl)
                    state.ctxByVideo.lineTo(data[i + 1].x / state.gdbl, data[i + 1].y / state.gdbl)
                    state.ctxByVideo.stroke()
                    state.ctxByVideo.closePath()
                }
            }
            state.indexPoint = 0
            state.ctxByVideo.stroke();
            resolve()
        }, time)
    })
}
const drawLine = (x1:any, y1:any, x2:any, y2:any, controlX:any, controlY:any, time:any) => {
    return new Promise((resolve) => {
        state.drawLineTimer = setTimeout(() => {
            state.ctxByVideo.moveTo(x1, y1)
            state.ctxByVideo.lineTo(x2, y2)
            // state.ctx.quadraticCurveTo(controlX, controlY, x2, y2)
            state.ctxByVideo.stroke();
            state.ctxByVideo.closePath();
            resolve()
        }, time)
    })
}
// 改变播放速度
const changeSpeed = (speed:any) => {
    state.nowSpeed = speed
    state.isShowSpeedBox = false
    if (state.nowTimer) {
        clearInterval(state.nowTimer)
        if (state.isPlayVideo) {
            state.nowTimer = setInterval(()=> {
                if (state.nowTime < state.allTime) {
                    state.nowTime++
                }
            }, 1000 / state.nowSpeed)
        }
    }
}
const getFormatTime = (allSecond:any) => {
    let minute:any = 0
    let second:any = 0
    second = allSecond % 60
    if (second < 10) {
        second = '0' + second
    }
    minute = Math.trunc(allSecond / 60)
    if (minute < 10) {
        minute = '0' + minute
    }
    return minute + ':' + second
}
const initCanvasByVideo = () => {
    state.myCanvasByVideo = document.getElementById("myCanvasByVideo")
    state.ctxByVideo = state.myCanvasByVideo.getContext("2d")
}
</script>
<style lang="scss" scoped>
.canvas-video{
    width: 100%;
    height: 100%;
    background: white;
    // .myCanvas{
    //     z-index: 99;
    // }
    img{
        max-width: 100%;
    }
}
.btnDiv{
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 20px;
    .playback{
        display: flex;
        justify-content: space-between;
        color: #ffffff;
        text-align: center;
        padding: 0 20px;
        height: 40px;
        line-height: 40px;
        border-radius: 4px;
        border: 1px solid #00B386;
        font-size: 20px;
        cursor: pointer;
        img{
            width: 10px;
            height: 14px;
            margin-top: 7px;
            margin-left: 12px;
        }
    }
    .rocket{
        margin-top: 3px;
        margin-left: 10px;
        cursor: pointer;
    }
    :deep(.mySlider){
        width: 500px;
        margin-left: 20px;
        .el-slider__bar{
            border-radius: 17px;
            background: #00B386;
        }
        .el-slider__button{
            border: 1px solid #00B386;
        }
        .el-slider__runway{
            border-radius: 17px;
            background: #444;
        }
    }
    .myTime{
        margin-left: 14px;
        color: #A9A9A9;
        line-height: 35px;
        font-size: 18px;
    }
    .mySpeed{
        cursor: pointer;
        position: relative;
        border-radius: 2px;
        background: #EF8714;
        width: 60px;
        height: 40px;
        line-height: 40px;
        text-align: center;
        color: #ffffff;
        margin: 5px 0 0 20px;
        font-size: 18px;
        .speedList{
            position: absolute;
            bottom: 20px;
            border-radius: 2px;
            background: #232322;
            padding: 10px 0;
            width: 60px;
            .speedItem{
                width: 100%;
                text-align: center;
                margin-bottom: 10px;
                color: #FFFFFF;
            }
            .speedItem.active{
                color: #EF8714;
                font-size: 20px;
            }
            .speedItem:last-child{
                margin-bottom: 0px;
            }
            z-index: 2000;
        }
    }
    .slider-warpper {
        width: 320px;
        height: 16px;
        position: absolute;
        left: 280px;
        // background: #ef9e00 !important;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        .slider-content {
            width: 304px;
            height: 16px;
            background: #2D2D2D;
            border: 1px solid rgba(255, 186, 33, 0.16);
            border-radius: 22px;
            position: relative;
            .slider {
                border-radius: 22px;
                position: absolute;
                left: 0;
                top: 0;
                width: 30px;
                height: 16px;
                background: #FFBA21;
            }
        }
        .persent {
            margin-left: 10px;
            color: #FFBA21;
            font-size: 18px;
            font-weight: 600;
            letter-spacing: 1.26px;
        }
    }
}
.dialog-btn{
    display: flex;
    justify-content: center;
    .btn1{
        border: 1px solid #00B386;
        background: #00B386;
        text-align: center;
        margin-right: 50px;
        color: #FFF;
        color: #fff;
        font-size: 18px;
        padding: 10px 65px;
        cursor: pointer;
        &:active{
        opacity: 0.8;
        }
    }
    .btn2{
        border: 1px solid #00B386;
        color: #fff;
        font-size: 18px;
        text-align: center;
        padding: 10px 65px;
        cursor: pointer;
        &:active{
            opacity: 0.8;
        }
    }
}
</style>

三、页面调用

1、vue2

(1)安装 canvasvideo-vue

npm install canvasvideo-vue --save

(2)main.js引入

import { createApp } from 'vue'
import App from './App.vue'
import canvasVideo from "canvasvideo-vue"
import "../node_modules/canvasvideo-vue/canvasVideo-vue.css"
const app = createApp(App)
app.use(canvasVideo)
.mount("#app")

(3)页面调用

<canvas-video :width="1220" :height="500" :backgroundColor="'blue'" :color="'red'" :lineWidth="1" :pointData="backPlayList" />

2、vue3

<canvas-video :width="1220" :height="500" :backgroundColor="'blue'" :color="'red'" :lineWidth="1" :pointData="backPlayList" />
<script setup lang="ts">
import { ref } from 'vue'
import CanvasVideo from "./components/CanvasVideo.vue"
const backPlayList = ref<any[]>([])
// backPlayList 去接收处理后台返回的数据
</script>

四、数据结构

let datas = [
    [
        {
            "x": 144.42779541015625,
            "y": 112.7576904296875,
            "time": 1702536449825
        },
        {
            "x": 144.42779541015625,
            "y": 122.65827941894531,
            "time": 1702536449857
        },
        {
            "x": 144.42779541015625,
            "y": 128.27886962890625,
            "time": 1702536449871
        },
        {
            "x": 144.42779541015625,
            "y": 134.2593231201172,
            "time": 1702536449889
        },
        {
            "x": 144.42779541015625,
            "y": 146.72254943847656,
            "time": 1702536449939
        },
        {
            "x": 144.42779541015625,
            "y": 157.4418182373047,
            "time": 1702536449955
        },
        {
            "x": 145.78329467773438,
            "y": 162.1194610595703,
            "time": 1702536449971
        },
        {
            "x": 145.76043701171875,
            "y": 167.31605529785156,
            "time": 1702536449989
        },
        {
            "x": 146.4267578125,
            "y": 172.4877471923828,
            "time": 1702536450007
        },
        {
            "x": 146.4267578125,
            "y": 177.4159698486328,
            "time": 1702536450020
        },
        {
            "x": 146.4267578125,
            "y": 182.0648651123047,
            "time": 1702536450039
        },
        {
            "x": 147.09307861328125,
            "y": 187.20314025878906,
            "time": 1702536450053
        },
        {
            "x": 147.762451171875,
            "y": 192.70411682128906,
            "time": 1702536450070
        },
        {
            "x": 148.41329956054688,
            "y": 197.30494689941406,
            "time": 1702536450088
        },
        {
            "x": 149.09210205078125,
            "y": 202.69898986816406,
            "time": 1702536450103
        },
        {
            "x": 149.7584228515625,
            "y": 207.8609161376953,
            "time": 1702536450121
        },
        {
            "x": 150.42477416992188,
            "y": 212.64430236816406,
            "time": 1702536450140
        },
        {
            "x": 151.09112548828125,
            "y": 217.8363494873047,
            "time": 1702536450158
        },
        {
            "x": 151.09112548828125,
            "y": 223.17628479003906,
            "time": 1702536450178
        },
        {
            "x": 151.7574462890625,
            "y": 229.3798065185547,
            "time": 1702536450194
        },
        {
            "x": 151.7574462890625,
            "y": 234.6603240966797,
            "time": 1702536450204
        },
        {
            "x": 151.7574462890625,
            "y": 239.9730987548828,
            "time": 1702536450221
        },
        {
            "x": 151.7574462890625,
            "y": 245.2152862548828,
            "time": 1702536450246
        },
        {
            "x": 151.7574462890625,
            "y": 251.24928283691406,
            "time": 1702536450257
        },
        {
            "x": 151.09112548828125,
            "y": 257.2041473388672,
            "time": 1702536450270
        },
        {
            "x": 151.09112548828125,
            "y": 261.8277130126953,
            "time": 1702536450288
        },
        {
            "x": 151.09112548828125,
            "y": 267.2612762451172,
            "time": 1702536450308
        },
        {
            "x": 151.09112548828125,
            "y": 273.2233123779297,
            "time": 1702536450322
        },
        {
            "x": 151.09112548828125,
            "y": 279.8961639404297,
            "time": 1702536450342
        },
        {
            "x": 151.09112548828125,
            "y": 285.8682403564453,
            "time": 1702536450358
        },
        {
            "x": 152.39996337890625,
            "y": 291.18141174316406,
            "time": 1702536450370
        },
        {
            "x": 153.7384033203125,
            "y": 297.18141174316406,
            "time": 1702536450389
        },
        {
            "x": 155.09494018554688,
            "y": 303.9618377685547,
            "time": 1702536450405
        },
        {
            "x": 156.41146850585938,
            "y": 309.8865203857422,
            "time": 1702536450420
        },
        {
            "x": 158.44903564453125,
            "y": 314.70338439941406,
            "time": 1702536450442
        },
        {
            "x": 161.03179931640625,
            "y": 319.8107147216797,
            "time": 1702536450455
        },
        {
            "x": 163.73126220703125,
            "y": 325.8739776611328,
            "time": 1702536450473
        },
        {
            "x": 165.68637084960938,
            "y": 331.1151580810547,
            "time": 1702536450488
        },
        {
            "x": 168.38128662109375,
            "y": 336.5032501220703,
            "time": 1702536450503
        },
        {
            "x": 171.0692138671875,
            "y": 341.2170867919922,
            "time": 1702536450521
        },
        {
            "x": 173.73162841796875,
            "y": 345.2093963623047,
            "time": 1702536450538
        },
        {
            "x": 176.42041015625,
            "y": 348.57081604003906,
            "time": 1702536450554
        },
        {
            "x": 179.12631225585938,
            "y": 351.2758026123047,
            "time": 1702536450571
        },
        {
            "x": 181.71502685546875,
            "y": 353.8636932373047,
            "time": 1702536450588
        },
        {
            "x": 184.35678100585938,
            "y": 355.19776916503906,
            "time": 1702536450605
        },
        {
            "x": 187.10076904296875,
            "y": 356.5692901611328,
            "time": 1702536450621
        },
        {
            "x": 188.40576171875,
            "y": 357.2216033935547,
            "time": 1702536450636
        }
    ],
    [
        {
            "x": 278.3201904296875,
            "y": 225.33656311035156,
            "time": 1702536451411
        },
        {
            "x": 290.136962890625,
            "y": 225.3314971923828,
            "time": 1702536451431
        },
        {
            "x": 305.58203125,
            "y": 225.9976043701172,
            "time": 1702536451442
        },
        {
            "x": 319.9619140625,
            "y": 225.9976043701172,
            "time": 1702536451457
        },
        {
            "x": 331.8698425292969,
            "y": 226.6862030029297,
            "time": 1702536451472
        },
        {
            "x": 342.2525329589844,
            "y": 227.32032775878906,
            "time": 1702536451488
        },
        {
            "x": 351.42987060546875,
            "y": 227.32984924316406,
            "time": 1702536451504
        },
        {
            "x": 361.1939697265625,
            "y": 227.32984924316406,
            "time": 1702536451521
        },
        {
            "x": 370.506103515625,
            "y": 226.6363983154297,
            "time": 1702536451540
        },
        {
            "x": 377.578857421875,
            "y": 226.01075744628906,
            "time": 1702536451585
        },
        {
            "x": 396.9224853515625,
            "y": 223.3396759033203,
            "time": 1702536451589
        },
        {
            "x": 404.81201171875,
            "y": 222.0262908935547,
            "time": 1702536451603
        },
        {
            "x": 412.2171630859375,
            "y": 220.68153381347656,
            "time": 1702536451621
        },
        {
            "x": 418.94720458984375,
            "y": 219.33851623535156,
            "time": 1702536451637
        },
        {
            "x": 424.103515625,
            "y": 218.0503692626953,
            "time": 1702536451654
        },
        {
            "x": 429.489013671875,
            "y": 218.00428771972656,
            "time": 1702536451671
        },
        {
            "x": 433.5123291015625,
            "y": 218.00428771972656,
            "time": 1702536451687
        },
        {
            "x": 436.1656494140625,
            "y": 217.33815002441406,
            "time": 1702536451703
        },
        {
            "x": 438.26458740234375,
            "y": 217.33815002441406,
            "time": 1702536451720
        },
        {
            "x": 439.58154296875,
            "y": 217.33815002441406,
            "time": 1702536451740
        },
        {
            "x": 439.613525390625,
            "y": 217.33815002441406,
            "time": 1702536451761
        }
    ],
    [
        {
            "x": 359.6776123046875,
            "y": 148.3515167236328,
            "time": 1702536452241
        },
        {
            "x": 360.95751953125,
            "y": 166.9501495361328,
            "time": 1702536452258
        },
        {
            "x": 361.6524658203125,
            "y": 186.5827178955078,
            "time": 1702536452270
        },
        {
            "x": 362.3128662109375,
            "y": 203.9375762939453,
            "time": 1702536452288
        },
        {
            "x": 362.9931640625,
            "y": 218.0919647216797,
            "time": 1702536452306
        },
        {
            "x": 364.331787109375,
            "y": 232.8119354248047,
            "time": 1702536452320
        },
        {
            "x": 364.984130859375,
            "y": 247.2077178955078,
            "time": 1702536452337
        },
        {
            "x": 365.6175537109375,
            "y": 260.9715118408203,
            "time": 1702536452354
        },
        {
            "x": 366.2884521484375,
            "y": 275.6442413330078,
            "time": 1702536452371
        },
        {
            "x": 367.6258544921875,
            "y": 289.0420379638672,
            "time": 1702536452390
        },
        {
            "x": 368.8919677734375,
            "y": 301.1228790283203,
            "time": 1702536452403
        },
        {
            "x": 370.292724609375,
            "y": 313.0811309814453,
            "time": 1702536452421
        },
        {
            "x": 372.9156494140625,
            "y": 323.0240936279297,
            "time": 1702536452438
        },
        {
            "x": 374.9552001953125,
            "y": 330.4568634033203,
            "time": 1702536452455
        },
        {
            "x": 377.5841064453125,
            "y": 337.0873565673828,
            "time": 1702536452471
        },
        {
            "x": 378.9256591796875,
            "y": 342.3609161376953,
            "time": 1702536452488
        },
        {
            "x": 380.2994384765625,
            "y": 347.8545379638672,
            "time": 1702536452505
        },
        {
            "x": 381.62762451171875,
            "y": 351.8480682373047,
            "time": 1702536452520
        },
        {
            "x": 382.308837890625,
            "y": 354.4903106689453,
            "time": 1702536452545
        },
        {
            "x": 382.308837890625,
            "y": 354.55714416503906,
            "time": 1702536452556
        }
    ],
    [
        {
            "x": 586.8844604492188,
            "y": 151.2805633544922,
            "time": 1702536453141
        },
        {
            "x": 585.511962890625,
            "y": 165.02943420410156,
            "time": 1702536453157
        },
        {
            "x": 584.8742065429688,
            "y": 183.56068420410156,
            "time": 1702536453173
        },
        {
            "x": 584.8742065429688,
            "y": 201.2294158935547,
            "time": 1702536453188
        },
        {
            "x": 584.8742065429688,
            "y": 215.3689422607422,
            "time": 1702536453204
        },
        {
            "x": 584.8742065429688,
            "y": 230.58912658691406,
            "time": 1702536453222
        },
        {
            "x": 585.54052734375,
            "y": 243.74464416503906,
            "time": 1702536453239
        },
        {
            "x": 586.8107299804688,
            "y": 256.01344299316406,
            "time": 1702536453256
        },
        {
            "x": 587.5194091796875,
            "y": 267.7810821533203,
            "time": 1702536453271
        },
        {
            "x": 588.8597412109375,
            "y": 279.1865692138672,
            "time": 1702536453288
        },
        {
            "x": 590.2003784179688,
            "y": 289.90699768066406,
            "time": 1702536453304
        },
        {
            "x": 591.52001953125,
            "y": 301.1284942626953,
            "time": 1702536453321
        },
        {
            "x": 592.8444213867188,
            "y": 311.0792694091797,
            "time": 1702536453339
        },
        {
            "x": 594.8500366210938,
            "y": 320.5182342529297,
            "time": 1702536453355
        },
        {
            "x": 596.1693115234375,
            "y": 328.3832244873047,
            "time": 1702536453371
        },
        {
            "x": 597.5363159179688,
            "y": 336.58277893066406,
            "time": 1702536453390
        },
        {
            "x": 598.8732299804688,
            "y": 343.2628936767578,
            "time": 1702536453404
        },
        {
            "x": 600.2008666992188,
            "y": 348.56568908691406,
            "time": 1702536453420
        },
        {
            "x": 600.8662109375,
            "y": 353.1544647216797,
            "time": 1702536453438
        },
        {
            "x": 600.8662109375,
            "y": 356.4942169189453,
            "time": 1702536453454
        },
        {
            "x": 600.8662109375,
            "y": 358.5269012451172,
            "time": 1702536453472
        },
        {
            "x": 600.8662109375,
            "y": 358.5537872314453,
            "time": 1702536453482
        }
    ],
    [
        {
            "x": 679.638916015625,
            "y": 223.9750518798828,
            "time": 1702536453873
        },
        {
            "x": 698.049560546875,
            "y": 220.69044494628906,
            "time": 1702536453889
        },
        {
            "x": 715.046142578125,
            "y": 218.70338439941406,
            "time": 1702536453912
        },
        {
            "x": 731.1329956054688,
            "y": 217.30799865722656,
            "time": 1702536453922
        },
        {
            "x": 743.7023315429688,
            "y": 216.6453094482422,
            "time": 1702536453939
        },
        {
            "x": 755.0431518554688,
            "y": 216.6720428466797,
            "time": 1702536453957
        },
        {
            "x": 764.043701171875,
            "y": 216.6720428466797,
            "time": 1702536453972
        },
        {
            "x": 770.6744995117188,
            "y": 216.6720428466797,
            "time": 1702536453988
        },
        {
            "x": 776.098388671875,
            "y": 216.6720428466797,
            "time": 1702536454005
        },
        {
            "x": 780.066650390625,
            "y": 216.6720428466797,
            "time": 1702536454021
        },
        {
            "x": 783.4193115234375,
            "y": 216.6720428466797,
            "time": 1702536454041
        },
        {
            "x": 785.4308471679688,
            "y": 216.6720428466797,
            "time": 1702536454057
        },
        {
            "x": 786.7714233398438,
            "y": 216.6720428466797,
            "time": 1702536454070
        },
        {
            "x": 786.7733154296875,
            "y": 216.6720428466797,
            "time": 1702536454083
        }
    ],
    [
        {
            "x": 733.5531616210938,
            "y": 316.62501525878906,
            "time": 1702536454456
        },
        {
            "x": 756.4210815429688,
            "y": 315.2781524658203,
            "time": 1702536454472
        },
        {
            "x": 775.4960327148438,
            "y": 315.2565460205078,
            "time": 1702536454488
        },
        {
            "x": 790.3649291992188,
            "y": 317.1736297607422,
            "time": 1702536454506
        },
        {
            "x": 804.3414916992188,
            "y": 319.8348846435547,
            "time": 1702536454524
        },
        {
            "x": 815.0912475585938,
            "y": 323.1543731689453,
            "time": 1702536454540
        },
        {
            "x": 823.2052612304688,
            "y": 325.8422393798828,
            "time": 1702536454556
        },
        {
            "x": 829.9567260742188,
            "y": 328.5275115966797,
            "time": 1702536454571
        },
        {
            "x": 835.1862182617188,
            "y": 329.8536834716797,
            "time": 1702536454589
        },
        {
            "x": 839.3932495117188,
            "y": 330.5771026611328,
            "time": 1702536454606
        },
        {
            "x": 841.41259765625,
            "y": 330.5771026611328,
            "time": 1702536454618
        }
    ],
    [
        {
            "x": 783.1222534179688,
            "y": 122.69233703613281,
            "time": 1702536454959
        },
        {
            "x": 807.8834838867188,
            "y": 125.26026916503906,
            "time": 1702536454974
        },
        {
            "x": 833.0866088867188,
            "y": 128.6871337890625,
            "time": 1702536454989
        },
        {
            "x": 854.1300659179688,
            "y": 132.88661193847656,
            "time": 1702536455006
        },
        {
            "x": 869.70361328125,
            "y": 138.1724853515625,
            "time": 1702536455021
        },
        {
            "x": 882.982666015625,
            "y": 144.5460968017578,
            "time": 1702536455042
        },
        {
            "x": 895.916748046875,
            "y": 152.63514709472656,
            "time": 1702536455056
        },
        {
            "x": 906.02685546875,
            "y": 160.70143127441406,
            "time": 1702536455071
        },
        {
            "x": 916.085693359375,
            "y": 170.0878448486328,
            "time": 1702536455090
        },
        {
            "x": 925.06689453125,
            "y": 182.9326934814453,
            "time": 1702536455105
        },
        {
            "x": 932.54638671875,
            "y": 195.7455291748047,
            "time": 1702536455121
        },
        {
            "x": 937.921142578125,
            "y": 209.0699920654297,
            "time": 1702536455139
        },
        {
            "x": 941.3206787109375,
            "y": 221.79115295410156,
            "time": 1702536455159
        },
        {
            "x": 943.363037109375,
            "y": 235.3358612060547,
            "time": 1702536455171
        },
        {
            "x": 943.3616943359375,
            "y": 248.3235626220703,
            "time": 1702536455187
        },
        {
            "x": 942.05517578125,
            "y": 261.0668182373047,
            "time": 1702536455205
        },
        {
            "x": 938.763427734375,
            "y": 275.0695037841797,
            "time": 1702536455221
        },
        {
            "x": 933.5555419921875,
            "y": 287.5211639404297,
            "time": 1702536455238
        },
        {
            "x": 928.054443359375,
            "y": 298.5670928955078,
            "time": 1702536455255
        },
        {
            "x": 921.485107421875,
            "y": 307.7720489501953,
            "time": 1702536455271
        },
        {
            "x": 913.521728515625,
            "y": 316.4197235107422,
            "time": 1702536455288
        },
        {
            "x": 905.4949951171875,
            "y": 325.1339569091797,
            "time": 1702536455305
        },
        {
            "x": 896.376953125,
            "y": 333.6233673095703,
            "time": 1702536455321
        },
        {
            "x": 885.5185546875,
            "y": 342.4713592529297,
            "time": 1702536455339
        },
        {
            "x": 874.749755859375,
            "y": 350.5452117919922,
            "time": 1702536455356
        },
        {
            "x": 863.4857177734375,
            "y": 359.8206329345703,
            "time": 1702536455370
        },
        {
            "x": 852.8712768554688,
            "y": 367.1151580810547,
            "time": 1702536455391
        },
        {
            "x": 841.2532958984375,
            "y": 374.6466522216797,
            "time": 1702536455404
        },
        {
            "x": 829.65380859375,
            "y": 381.0709991455078,
            "time": 1702536455421
        },
        {
            "x": 817.5537109375,
            "y": 387.79103088378906,
            "time": 1702536455437
        },
        {
            "x": 805.4242553710938,
            "y": 393.8605499267578,
            "time": 1702536455454
        },
        {
            "x": 793.5094604492188,
            "y": 399.15431213378906,
            "time": 1702536455472
        },
        {
            "x": 782.9738159179688,
            "y": 403.1088409423828,
            "time": 1702536455489
        },
        {
            "x": 772.8463745117188,
            "y": 406.4949493408203,
            "time": 1702536455504
        },
        {
            "x": 764.2553100585938,
            "y": 409.1325225830078,
            "time": 1702536455522
        },
        {
            "x": 756.42041015625,
            "y": 411.1269073486328,
            "time": 1702536455540
        },
        {
            "x": 749.0506591796875,
            "y": 413.7377471923828,
            "time": 1702536455557
        },
        {
            "x": 742.9489135742188,
            "y": 416.42872619628906,
            "time": 1702536455572
        },
        {
            "x": 737.5338745117188,
            "y": 418.4693145751953,
            "time": 1702536455588
        },
        {
            "x": 733.5607299804688,
            "y": 420.4709014892578,
            "time": 1702536455605
        },
        {
            "x": 730.177734375,
            "y": 422.4792022705078,
            "time": 1702536455622
        },
        {
            "x": 728.816650390625,
            "y": 424.4844512939453,
            "time": 1702536455638
        },
        {
            "x": 728.80224609375,
            "y": 427.1183624267578,
            "time": 1702536455655
        },
        {
            "x": 732.04443359375,
            "y": 429.1317901611328,
            "time": 1702536455671
        },
        {
            "x": 739.4037475585938,
            "y": 431.8090362548828,
            "time": 1702536455688
        },
        {
            "x": 752.61962890625,
            "y": 435.11402893066406,
            "time": 1702536455712
        },
        {
            "x": 767.9056396484375,
            "y": 437.7860870361328,
            "time": 1702536455727
        },
        {
            "x": 784.0560302734375,
            "y": 439.1490020751953,
            "time": 1702536455738
        },
        {
            "x": 800.2537841796875,
            "y": 441.17530822753906,
            "time": 1702536455755
        },
        {
            "x": 820.0858154296875,
            "y": 443.8155059814453,
            "time": 1702536455772
        },
        {
            "x": 837.1102905273438,
            "y": 445.79103088378906,
            "time": 1702536455787
        },
        {
            "x": 852.6246948242188,
            "y": 447.1361846923828,
            "time": 1702536455803
        },
        {
            "x": 865.9520263671875,
            "y": 449.1322784423828,
            "time": 1702536455820
        },
        {
            "x": 876.6416015625,
            "y": 451.1309356689453,
            "time": 1702536455844
        },
        {
            "x": 886.6123046875,
            "y": 453.1099395751953,
            "time": 1702536455855
        },
        {
            "x": 894.085205078125,
            "y": 454.4803009033203,
            "time": 1702536455871
        },
        {
            "x": 899.9359130859375,
            "y": 455.77760314941406,
            "time": 1702536455888
        },
        {
            "x": 904.62646484375,
            "y": 458.4120635986328,
            "time": 1702536455904
        },
        {
            "x": 908.64892578125,
            "y": 460.4477081298828,
            "time": 1702536455920
        },
        {
            "x": 908.71240234375,
            "y": 460.4689483642578,
            "time": 1702536455930
        }
    ]
]

到此这篇关于vue实现画笔回放,canvas转视频播放功能的文章就介绍到这了,更多相关vue canvas视频播放内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于vue+face-api.js实现前端人脸识别功能

    基于vue+face-api.js实现前端人脸识别功能

    基于face-api.js要实现人脸识别功能,首先要将自己需要的模型文件下载保存在静态目录下,可以通过cdn的方式在index.html中引入face-api.js,本文给大家介绍vue+face-api.js实现前端人脸识别功能,感兴趣的朋友一起看看吧
    2023-12-12
  • vue-router跳转方式的区别解析

    vue-router跳转方式的区别解析

    在Vue中,router-link称为声明式路由,:to绑定为跳转的目标地址,一种是通过name,另一种是path,这篇文章主要介绍了vue-router跳转方式的区别,需要的朋友可以参考下
    2022-12-12
  • 解决elementUI 切换tab后 el_table 固定列下方多了一条线问题

    解决elementUI 切换tab后 el_table 固定列下方多了一条线问题

    这篇文章主要介绍了解决elementUI 切换tab后 el_table 固定列下方多了一条线问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • vue3使用vue-router嵌套多级路由的方法

    vue3使用vue-router嵌套多级路由的方法

    Vue3 嵌套路由的使用和 Vue2 相差不大,主要的区别是 Vue3 的路由实例化使用了 createApp() 方法,所以实例化路由时需要传入根组件,这篇文章主要介绍了vue3使用vue-router嵌套路由(多级路由),需要的朋友可以参考下
    2023-12-12
  • 浅谈VUE uni-app 核心知识

    浅谈VUE uni-app 核心知识

    这篇文章主要给大家介绍了关于uniapp的核心知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-10-10
  • vue深拷贝的3种实现方式小结

    vue深拷贝的3种实现方式小结

    当使用同一个对象产生冲突时,可以使用lodash包,对该对象进行深拷贝,从而使操作的对象为不同的对象,这篇文章主要给大家介绍了关于vue深拷贝的3种实现方式,需要的朋友可以参考下
    2023-02-02
  • vue引入jquery时报错 $ is not defined的问题及解决

    vue引入jquery时报错 $ is not defined的问题及解决

    这篇文章主要介绍了vue引入jquery时报错 $ is not defined的问题及解决,具有很好的参考价值,希望对大家有所帮助。
    2022-09-09
  • vue实现垂直无限滑动日历组件

    vue实现垂直无限滑动日历组件

    这篇文章主要为大家详细介绍了vue实现垂直无限滑动日历组件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Vue shopCart 组件开发详解

    Vue shopCart 组件开发详解

    这篇文章主要介绍了Vue shopCart 组件开发详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 详解Vue中的Props与Data细微差别

    详解Vue中的Props与Data细微差别

    这篇文章主要介绍了详解Vue中的Props与Data细微差别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论