当前位置: 首页 > news >正文

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

示例图:

一、vue2版本

<template><div class="canvas-video"><canvasref="myCanvasByVideo"class="myCanvas"id="myCanvasByVideo":width="width":height="height"></canvas><div class="btnDiv"><divv-if="!isPlayVideo && !isStartVideo"class="playback"@click.stop="playVideo"><div>回放</div><img src="@/assets/image/play.png" alt="" /></div><divv-if="isPlayVideo && isStartVideo"class="playback"@click.stop="pauseVideo"><div>暂停</div><img src="@/assets/image/pause.png" alt="" /></div><divv-if="!isPlayVideo && isStartVideo"class="playback"@click.stop="continueVideo"><div>继续</div><img src="@/assets/image/play.png" alt="" /></div><div class="rocket"><imgv-show="isRocket"src="@/assets/image/rocket.png"alt=""@click="playRocket"/><imgv-show="!isRocket"src="@/assets/image/rocket_noChoose.png"alt=""@click="playRocket"/></div><div class="mySlider"><el-sliderv-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"><divclass="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.pointDatathis.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) {let theTime:any = parseInt(second)// 秒let theTime1:any = 0// 分let theTime2:any = 0// 小时if (theTime > 60) {theTime1 = (theTime / 60).toFixed(0)theTime = (theTime % 60).toFixed(0)if (theTime1 > 60) {theTime2 = (theTime1 / 60).toFixed(0)theTime1 = (theTime1 % 60).toFixed(0)}}let result:any = "" + theTimeif(result < 10){result = '0' + result}if (theTime1 > 0) {result = "" + parseInt(theTime1) + ":" + resultif(theTime1 < 10){result = '0' + result}} else{result = '00:' + result}if (theTime2 > 0) {result = "" + parseInt(theTime2) + ":" + resultif(theTime2 < 10){result = '0' + result}} else{result = '00:' + result}return result},initCanvasByVideo() {this.myCanvasByVideo = document.getElementById("myCanvasByVideo");this.ctxByVideo = this.myCanvasByVideo.getContext("2d");}}
};
</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" :show-tooltip="false"></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: numberheight: 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.pointDatainitCanvasByVideo()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 = falsestate.isStartVideo = falsestate.indexStep = 0state.indexPoint = 0if (state.nowTimer) {clearInterval(state.nowTimer)}if (state.drawStepTimer) {clearTimeout(state.drawStepTimer)}if (state.drawLineTimer) {clearTimeout(state.drawLineTimer)}state.nowTime = 0if (state.isRocket) {state.isRocket = falsereturn}state.isPlayVideo = falsestate.isRocket = truestate.ctxByVideo.strokeStyle = props.colorstate.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 = jstate.ctxByVideo.beginPath();state.timeout = 0if (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 = falseclearInterval(state.nowTimer)clearTimeout(state.drawLineTimer)clearTimeout(state.drawStepTimer)state.myCanvasByVideo.width = state.myCanvasByVideo.width // 清空state.canvasHistory = null// 清空操作state.nowTime = 0state.isPlayVideo = falsestate.isStartVideo = falsestate.indexStep = 0state.indexPoint = 0clearInterval(state.nowTimer)state.canvasHistory = nullif (state.nowTimer) {clearInterval(state.nowTimer)}if (state.drawStepTimer) {clearTimeout(state.drawStepTimer)}if (state.drawLineTimer) {clearTimeout(state.drawLineTimer)}}state.isRocket = falsestate.isPlayVideo = truestate.isStartVideo = truestate.ctxByVideo.strokeStyle = props.colorstate.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 = jstate.ctxByVideo.beginPath();state.timeout = 0if (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 = falseclearInterval(state.nowTimer)
}
// 暂停播放
const pauseVideo = () => {state.isPlayVideo = falseclearInterval(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 = falsestate.isStartVideo = falsestate.isRocket = truereturn}state.isPlayVideo = truelet 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 = jstate.timeout = 0if (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.colorstate.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 = istate.indexPoint = jbreak 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 = ilet timeLine = data[i + 1].time - data[i].timeif (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 = 0state.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 = ilet timeLine = data[i + 1].time - data[i].timeif (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 = 0state.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 = speedstate.isShowSpeedBox = falseif (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 theTime:any = parseInt(allSecond)// 秒let theTime1:any = 0// 分let theTime2:any = 0// 小时if (theTime > 60) {theTime1 = (theTime / 60).toFixed(0)theTime = (theTime % 60).toFixed(0)if (theTime1 > 60) {theTime2 = (theTime1 / 60).toFixed(0)theTime1 = (theTime1 % 60).toFixed(0)}}let result:any = "" + theTimeif(result < 10){result = '0' + result}if (theTime1 > 0) {result = "" + parseInt(theTime1) + ":" + resultif(theTime1 < 10){result = '0' + result}} else{result = '00:' + result}if (theTime2 > 0) {result = "" + parseInt(theTime2) + ":" + resultif(theTime2 < 10){result = '0' + result}} else{result = '00:' + result}return result
}
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;// }overflow-x: none;overflow-y: scroll;&::-webkit-scrollbar{width: 6px;height: 0;}&::-webkit-scrollbar-thumb{border-radius: 20px;background: #ddd;}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: 80px;height: 40px;line-height: 40px;text-align: center;color: #ffffff;margin: 5px 0 0 20px;font-size: 18px;.speedList{position: absolute;bottom: 40px;border-radius: 2px;background: #232322;padding: 10px 0;width: 80px;.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

(1)页面调用

<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>

(2)调整字号:其中整体坐标展示后,文字会显示的很小,使用x,y轴去乘倍数即可

backPlayList.value.map((item:any) => {item.x = item.x * 2item.y = item.y * 2return item
})

四、数据结构

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转视频播放功能

示例图&#xff1a; 一、vue2版本 <template><div class"canvas-video"><canvasref"myCanvasByVideo"class"myCanvas"id"myCanvasByVideo":width"width":height"height"></canvas><d…...

Docker中镜像的相关操作

1.辅助操作 docker version&#xff1a;用查看docker客户端引擎和server端引擎版本信息。 docker info&#xff1a;用来查看docker引擎的详细信息。 docker --help&#xff1a;用来查看帮助信息。 2.镜像Image docker images&#xff1a;查看当前本地仓库中存在哪些镜像。 …...

[python]python利用pyaudio录制系统声音没有立体声混音怎么录制系统音频

当电脑没有立体声混音导致Python写代码无法使用pyaudio进行录制系统声音怎么办&#xff1f;查阅资料和安装驱动等方法都不行&#xff0c;难道没办法了吗&#xff1f;那为什么电脑其他软件可以做到呢&#xff1f;因此研究了一下pyaudio在没有立体声混音情况下确实无法录制声音&a…...

使用echarts的bmap配置项绘制区域轮廓遮罩

示例图 代码 <template><div id"map" style"width: 100%; height: 100vh"></div> </template><script> import * as echarts from "echarts"; import "echarts/extension/bmap/bmap"; export default…...

第3章 【课后习题】(完整版)

【3.18】写出下面程序的运行结果 //3.18写出下面程序的运行结果 #include <iostream> using namespace std; class test{public:test();~test() {};private:int i; }; test::test() {i25;for(int ctr0;ctr<10;ctr){cout<<"Counting at "<<ctr…...

redis安装与配置

目录 1. 切换到 root 用户 2. 搜索安装包 3. 安装 redis 4. 查看 redis 是否正常存在 5. 修改ip 6. 重新启动服务器 7. 连接服务器 1. 切换到 root 用户 通过 su 命令切换到 root 用户。 2. 搜索安装包 apt search redis 这里安装的是下面的版本&#xff1a; 3. 安装 …...

kotlin first/last/indexOf/elementAt

kotlin 中 first 是取集合元素中第一个元素 last 是取集合元素中最后一个元素 indexOf 根据元素寻找下标&#xff0c;默认是第一个 elementAt 根据下标找元素 下面写一个demo 说明下他们几个的使用 val list listOf("A", "D", "A", "…...

计算机网络——网络中要解决的问题

1. 从网络管理的角度看 1.1 配置管理 追踪所有部署的硬件和软件资源&#xff0c;包括设备配置和软件版本。 1.2 故障管理​​​​​ 监控设备的运行状态&#xff0c;以确保所有组件都正常工作&#xff0c;以及快速响应和修复任何故障。 1.3 计费管理 监控资源消耗并进行计费…...

初识STL

目录 ​&#x1f4a1;STL &#x1f4a1;STL六大组件 &#x1f4a1;三大组件介绍 &#x1f4a1;容器 &#x1f4a1;算法 &#x1f4a1;迭代器 &#x1f4a1;示例 &#x1f4a1;STL C STL&#xff08;标准模板库&#xff09;是一套功能强大的 C 模板类&#xff0c;提供了…...

程序员副业之无人直播助眠

介绍和概览 大家好&#xff0c;我是小黑&#xff0c;本文给大家介绍一个比较轻松简单的副业&#xff0c;无人直播助眠副业。 这个项目的核心就是通过直播一些助眠素材来赚钱。比如你可以放一些舒缓的雨声之类的&#xff0c;吸引观众进来。然后&#xff0c;咱们可以挂个小程序…...

imazing破解版百度云2.17.3(附激活许可证下载)

iMazing是一款强大的 iOS 设备管理软件&#xff0c;不管是 iPhone、iPad 或 iPod Touch 设备&#xff0c;只要将 iOS 设备连接到计算机&#xff0c;就可以处理不同类型的数据。 iPhone 和 iPad 备份 借助 iMazing 的独有 iOS 备份技术&#xff08;无线、隐私和自动&#xff09…...

VS+QT五子棋游戏开发

1、首先安装好VS软件和QT库&#xff0c;将其配置好&#xff0c;具体不在此展开说明。 2、文件结构如下图&#xff1a; 3、绘制棋盘代码&#xff0c;如下&#xff1a; void Qwzq::paintEvent(QPaintEvent* event) {QPainter painter(this);painter.setRenderHint(QPainter::An…...

SpringBoot中动态注册接口

1. 说明 接口注册&#xff0c;使用RequestMappingHandlerMapping来实现mybatis中动态执行sql使用github上的SqlMapper工具类实现 2. 核心代码片段 以下代码为spring动态注册接口代码示例 Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping;publ…...

CSS 实现两个圆圈重叠部分颜色不同

这是期望实现的效果&#xff0c;由图可知&#xff0c;圆圈底图透明度是0.4&#xff0c;左侧要求重叠部分透明度是0.7&#xff0c;所以不能通过简单的透明度叠加来实现最右侧的效果。 这就需要另外新建一个图层来叠加在两个圆圈重叠上方。 直接看代码 .circle_hight {width: 1…...

【数据库系统概念】第7-14章集合

文章目录 第七章 数据库设计和E-R模型&#xff08;重点&#xff01;&#xff01;&#xff01;&#xff09;~~7.1 设计过程概览&#xff08;了解&#xff09;~~7.1.1 设计阶段7.1.2 设计选择 7.2 实体-联系模型&#xff08;重点掌握&#xff09;7.2.1 实体集7.2.2 联系集联系集的…...

Kibana

Kibana是一个针对Elastic Search的开源分析及可视化的平台&#xff0c;使用kibana可以查询、查看并与存储在ES索引的数据进行交互操作&#xff0c;可以理解为一个客户端的工具&#xff0c;比如mysql和navicat。 使用kibana能执行高级的数据分析&#xff0c;并能以图表、表格和地…...

C#使用 OpenHardwareMonitor获取CPU或显卡温度、使用率、时钟频率相关方式

C# 去获取电脑相关的基础信息&#xff0c;还是需要借助 外部的库&#xff0c;我这边尝试了自己去实现它 网上有一些信息&#xff0c;但不太完整&#xff0c;都比较零碎&#xff0c;这边尽量将代码完整的去展示出来 OpenHardwareMonitor获取CPU的温度和频率需要管理员权限 在没…...

K8S--- volumesvolumeMount

一、Volume 简介 在容器当中的磁盘文件(on-disk file )是短暂的(ephemeral),这会对重要的应用程序或者数据产生一些问题。当容器崩溃或停止时,会出现一个问题,即容器状态不会被保存,因此在容器生命周期内被创建或者修改的文件都将丢失。在容器崩溃期间,kubelet会以干净状…...

AntV-G6 -- 将G6图表应用到项目中

1. 效果图 2. 安装依赖 npm install --save antv/g6 3. 代码 import { useEffect } from alipay/bigfish/react; import G6 from antv/g6;const data {id: root,label: 利息收入,subLabel: 3,283.456,ratio: 3,children: [{id: child-a,label: 平均利息,subLabel: 9%,ratio:…...

第二百五十回

文章目录 1. 概念介绍2. 使用方法2.1 简单用法2.2 自定义用法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"三方包open_settings"相关的内容&#xff0c;本章回中将介绍另外一个三方包&#xff1a;bluetooth_enable_fork.闲话休提&#xff0c;让我们一起Talk Flu…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

Canal环境搭建并实现和ES数据同步

作者&#xff1a;田超凡 日期&#xff1a;2025年6月7日 Canal安装&#xff0c;启动端口11111、8082&#xff1a; 安装canal-deployer服务端&#xff1a; https://github.com/alibaba/canal/releases/1.1.7/canal.deployer-1.1.7.tar.gz cd /opt/homebrew/etc mkdir canal…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

SQLSERVER-DB操作记录

在SQL Server中&#xff0c;将查询结果放入一张新表可以通过几种方法实现。 方法1&#xff1a;使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构&#xff08;包括列名和数据类型&#xff09;将与查询结果匹配。 SELECT * INTO 新…...