当前位置: 首页 > 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…...

如何把硬盘(分区)一分为二?重装系统的小伙伴不可不看

注意事项&#xff1a;本教程操作不当会导致数据丢失 请谨慎操作 请谨慎操作 请谨慎操作 前言 相信各位小伙伴都会切土豆吧&#xff0c;本教程就是教大家如何切土豆切得好的教程。 啊哈哈哈&#xff0c;开玩笑的。 比如你有一个D盘是200GB&#xff0c;想要把它变成两个100G…...

【AI视野·今日NLP 自然语言处理论文速览 第六十六期】Tue, 31 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Tue, 31 Oct 2023 (showing first 100 of 141 entries) Totally 100 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers The Eval4NLP 2023 Shared Task on Prompting Large Language Models a…...

解决Canvas画图清晰度问题

最近在开发Web端远程桌面的时候遇到的一个问题&#xff0c;解决记录一下&#xff0c;分享给各位有需要用到的朋友。 先吹下水&#xff1a;远程桌面的连接我们是通过Websocket连接后&#xff0c;后端不断返回远程端的界面二进制数据流&#xff0c;我接收到之后转为图像&#xf…...

zookeeper经典应用场景之分布式锁

1. 什么是分布式锁 在单体的应用开发场景中涉及并发同步的时候&#xff0c;大家往往采用Synchronized&#xff08;同步&#xff09;或者其他同一个JVM内Lock机制来解决多线程间的同步问题。在分布式集群工作的开发场景中&#xff0c;就需要一种更加高级的锁机制来处理跨机器的进…...

红队专题-Web安全/渗透测试-文件上传/下载/包含

文件上传/下载/包含 招募六边形战士队员利用目录穿越反弹SHELL实战测试2.2 提交报文修改检测3.2 文件内容检测绕过完整文件结构 检测 第四章&#xff1a;解析漏洞第一节 常见解析漏洞iis/nginx php fastcgi 取值错误 解析漏洞 &#xff08;配置错误&#xff09;nginx 文件名逻…...

安装阿里云CLI之配置阿里云凭证信息

有时候需要再主机上通过 OpenAPI 的调用访问阿里云&#xff0c;并完成控制&#xff0c;此时就需要在服务器上安装阿里云CLI&#xff0c;并完成账号的设置。 1. 登录阿里云创建账号 1.1 点击阿里云头像 ——》 控制访问 ——》创建一个拥有DNS权限的用户 这个用户不用太多权限…...

阿里云和腾讯云2核2G3M服务器上传速度多少?

2核2G3M服务器上传速度多少&#xff1f;上传是按10M带宽算&#xff0c;上传速度是1280KB/秒&#xff0c;即1.25M/秒&#xff1b;下载速度按3M带宽计算&#xff0c;下载速度是384KB/秒。本文阿腾云atengyun.com是以阿里云为例的&#xff0c;阿里云服务器当公网带宽小于10M及10M以…...

Python中的cls语法

在Python中&#xff0c;cls 是一个用于指代类本身的约定性名称&#xff0c;通常用作类方法&#xff08;class method&#xff09;中的第一个参数。cls 类似于 self&#xff0c;它是对类的引用&#xff0c;而不是对实例的引用。cls 通常在类方法中用于访问类级别的属性和方法。举…...

【Java】java -jar 读取jar包之外的yml

需求描述 springboot项目接入nacos配置&#xff0c;代码中使用bootstrap.yml来指定nacos信息&#xff0c;为了防止不同环境的来回切换&#xff0c;服务器中都单独在放一个bootstrap.yml&#xff0c;来指定具体环境的nacos配置&#xff0c;如sit服务器使用sit的nacos配置&#…...

遥感影像-语义分割数据集:山体滑坡数据集详细介绍及训练样本处理流程

原始数据集详情 简介&#xff1a;该遥感滑坡数据集由卫星光学图像、滑坡边界的形状文件和数字高程模型组成。该数据集中的所有图像&#xff0c;即770张滑坡图像&#xff08;红点&#xff09;和2003张非滑坡图像&#xff0c;都是从2018年5月至8月拍摄的TripleSat卫星图像中截取…...

wordpress字体路径设置/网站制作河南

说到夜晚发版这个事&#xff0c;有些时候事真的想不明白&#xff0c;为什么发版要夜晚发版。 有的人说&#xff0c;夜晚发版&#xff0c;影响到的用户数是少数的。why&#xff1f;发版为什么不灰度发&#xff1f;大多数项目都是集群的吧&#xff0c;6台机器&#xff0c;先发3台…...

wordpress编写模板/ciliba最佳磁力搜索引擎

好问题.如果你问我,你正在解决PHP中的一个黑暗面.if语句就像在任何其他语言中一样,我可以想象如果将参数计算为true或false.由于PHP并不真正知道类型,因此可以将任何表达式作为参数放置,然后将其作为整体转换为bool以下值被视为“假”> boolean FALSE>整数0>浮动0.0&g…...

政府门户网站集约化建设的探索/重庆seo公司排名

林炳文Evankaka 原创作品。转载请注明出处 http://blog.csdn.net/evankaka摘要&#xff1a;本文将使用Python3.4爬网页、爬图片、自动登录。并对HTTP协议做了一个简单的介绍。在进行爬虫之前&#xff0c;先简单来进行一个HTTP协议的讲解&#xff0c;这样下面再来进行爬虫就是理…...

公司企业网站怎么建设/做网络推广费用

2019年5月&#xff0c;我们发布了《7款路由器口碑评价》&#xff0c;不少消费者评论&#xff0c;希望看到更高端路由器的口碑推荐。2019年8月&#xff0c;《消费者报道》汇总了京东、天猫、苏宁平台上的约3万条消费者评价&#xff0c;对市面热销的14款200元以上的中高端路由器进…...

做网站需要用到什么/今天的新闻联播

/* * 创建人&#xff1a;李要南 * 创建时间&#xff1a;2008-12-7 4:22 * 说明&#xff1a;新闻类别表业务类 * 版权所有&#xff1a;李要南 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System…...

网站建设seo网络推广/今日国内重大新闻事件

cvx注册&#xff1a;向已有license添加新的host id或者user name准备工作注册准备工作 在matlab命令行敲入cvx_version获取未注册的username 和host id&#xff1b; 之前注册的license邮箱账户名&#xff1b; 安装cvx、首次申请license及安装license请参考 链接。 注册 如上图…...