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转视频播放功能
示例图: 一、vue2版本 <template><div class"canvas-video"><canvasref"myCanvasByVideo"class"myCanvas"id"myCanvasByVideo":width"width":height"height"></canvas><d…...

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

[python]python利用pyaudio录制系统声音没有立体声混音怎么录制系统音频
当电脑没有立体声混音导致Python写代码无法使用pyaudio进行录制系统声音怎么办?查阅资料和安装驱动等方法都不行,难道没办法了吗?那为什么电脑其他软件可以做到呢?因此研究了一下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 这里安装的是下面的版本: 3. 安装 …...
kotlin first/last/indexOf/elementAt
kotlin 中 first 是取集合元素中第一个元素 last 是取集合元素中最后一个元素 indexOf 根据元素寻找下标,默认是第一个 elementAt 根据下标找元素 下面写一个demo 说明下他们几个的使用 val list listOf("A", "D", "A", "…...
计算机网络——网络中要解决的问题
1. 从网络管理的角度看 1.1 配置管理 追踪所有部署的硬件和软件资源,包括设备配置和软件版本。 1.2 故障管理 监控设备的运行状态,以确保所有组件都正常工作,以及快速响应和修复任何故障。 1.3 计费管理 监控资源消耗并进行计费…...

初识STL
目录 💡STL 💡STL六大组件 💡三大组件介绍 💡容器 💡算法 💡迭代器 💡示例 💡STL C STL(标准模板库)是一套功能强大的 C 模板类,提供了…...

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

imazing破解版百度云2.17.3(附激活许可证下载)
iMazing是一款强大的 iOS 设备管理软件,不管是 iPhone、iPad 或 iPod Touch 设备,只要将 iOS 设备连接到计算机,就可以处理不同类型的数据。 iPhone 和 iPad 备份 借助 iMazing 的独有 iOS 备份技术(无线、隐私和自动)…...

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

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

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

【数据库系统概念】第7-14章集合
文章目录 第七章 数据库设计和E-R模型(重点!!!)~~7.1 设计过程概览(了解)~~7.1.1 设计阶段7.1.2 设计选择 7.2 实体-联系模型(重点掌握)7.2.1 实体集7.2.2 联系集联系集的…...

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

C#使用 OpenHardwareMonitor获取CPU或显卡温度、使用率、时钟频率相关方式
C# 去获取电脑相关的基础信息,还是需要借助 外部的库,我这边尝试了自己去实现它 网上有一些信息,但不太完整,都比较零碎,这边尽量将代码完整的去展示出来 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"相关的内容,本章回中将介绍另外一个三方包:bluetooth_enable_fork.闲话休提,让我们一起Talk Flu…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...

JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...

Java后端检查空条件查询
通过抛出运行异常:throw new RuntimeException("请输入查询条件!");BranchWarehouseServiceImpl.java // 查询试剂交易(入库/出库)记录Overridepublic List<BranchWarehouseTransactions> queryForReagent(Branch…...

Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目
应用场景: 1、常规某个机器被钓鱼后门攻击后,我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后,我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...

CMS内容管理系统的设计与实现:多站点模式的实现
在一套内容管理系统中,其实有很多站点,比如企业门户网站,产品手册,知识帮助手册等,因此会需要多个站点,甚至PC、mobile、ipad各有一个站点。 每个站点关联的有站点所在目录及所属的域名。 一、站点表设计…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...