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…...
告别Source Insight卡顿!用Vim + Ctags + Cscope打造Linux下丝滑的C/C++代码阅读环境
打造Linux下极致流畅的C/C代码阅读环境:Vim Ctags Cscope实战指南 第一次在Linux服务器上打开一个大型C项目时,我盯着终端里密密麻麻的代码手足无措。图形化IDE在远程桌面上的卡顿让我几乎无法工作,每次跳转定义都要等待数秒,开…...
AD7656与DSP通信时序深度解析:如何用示波器搞定数据跳变和读取为0的故障
AD7656与DSP通信时序深度解析:如何用示波器搞定数据跳变和读取为0的故障 在高速数据采集系统的调试现场,AD7656模数转换器与DSP的通信问题堪称经典案例。当示波器屏幕上出现异常波形时,工程师需要像侦探破案一样,从时序关系的蛛丝…...
PX4混控器加载流程与多旋翼输出实现剖析
1. PX4混控器的作用与基本概念 混控器在PX4飞控系统中扮演着关键角色,它负责将飞行控制器计算出的姿态控制指令(如滚转、俯仰、偏航力矩)转换为实际电机或舵机的输出信号。简单来说,就像汽车的方向盘和油门需要通过传动系统转换为…...
如何获取并定制化订货系统源码以适应企业需求?
在数字化转型的浪潮中,构建自主可控的 B2B 业务平台已成为众多企业的核心战略。对于希望深度掌控业务流程、实现数据私有化部署的企业而言,直接获取并二次开发订货系统源码是最高效的路径。这不仅意味着拥有系统的完全所有权,更代表了能够根据…...
如何用 style.setProperty 修改带有优先级的 CSS 变量属性
不能用 style.setProperty 直接设置带 !important 的 CSS 变量,因为内联样式不支持 !important,浏览器会静默丢弃该声明;应通过 insertRule 动态注入带 !important 的 CSS 规则,或依靠作用域、类名切换等更健壮的方式管理变量优先…...
2026届必备的十大AI学术网站解析与推荐
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 知网已正式推出AIGC检测服务系统,此系统目的在于识别学术文本里由人工智能生成的…...
如何在 macOS 上为 PHP 8.0 正确集成 XML-RPC 支持
php 8.0 已移除内置 xmlrpc 扩展,macos 下不应通过 pecl 安装废弃扩展,而应采用 composer 引入纯 php 实现(如 phpxmlrpc/polyfill-xmlrpc)以安全兼容 odoo 等系统。 php 8.0 已移除内置 xmlrpc 扩展,macos 下不应…...
如何实现课堂学习自由:3个场景的完整学习辅助工具指南
如何实现课堂学习自由:3个场景的完整学习辅助工具指南 【免费下载链接】JiYuTrainer 极域电子教室防控制软件, StudenMain.exe 破解 项目地址: https://gitcode.com/gh_mirrors/ji/JiYuTrainer 在数字化教学环境中,学生常常面临学习自主权受限的困…...
HCIP学习18 静态路由跨公网互通实验
实验拓扑实验设备设备类型设备名称型号数量用途路由器AR1AR22201左侧私网出口路由器路由器ISPAR22201公网核心路由器路由器AR3AR22201右侧私网出口路由器拓扑结构拓扑链路与接口连接表本端设备本端接口对端设备对端接口链路网段所属网络AR1GE0/0/0ISPGE0/0/012.0.0.0/24公网ISP…...
用STM32CubeMX和HAL库5分钟搞定BMP280气压传感器驱动(附完整代码)
STM32CubeMX与HAL库快速集成BMP280气压传感器的完整指南 气压传感器在现代嵌入式系统中扮演着重要角色,从无人机高度控制到气象站数据采集,BMP280凭借其高精度和稳定性成为工程师的热门选择。传统寄存器级开发方式虽然灵活,但对于追求开发效率…...
