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…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...
