<template> <div class="container-height box"> <q-btn color="primary" @click="onStart">开始</q-btn> <q-btn color="primary" @click="onStop">暂停</q-btn> <div class="content"> <canvas ref="canvasRef" id="myCanvas" :width="700" :height="700"></canvas> <div v-if="img.show" class="img" :style="{ width: `${img.w}px`, height: `${img.h}px`, transform: `translate(${img.x}px, ${img.y}px) rotate(${img.direction}deg)`, }" > 123 </div> </div> </div> </template> <script lang="ts"> export default { name: 'PAGE4', }; </script> <script setup lang="ts"> // 有动画 import { onMounted, reactive, ref } from 'vue'; const canvasRef = ref<any>(null); const state = reactive({ keyPoints: [ { x: 250, y: 200, direction: '30' }, { x: 550, y: 200, direction: '30' }, { x: 250, y: 500, direction: '30' }, { x: 550, y: 500, direction: '30' }, { x: 250, y: 200, direction: '30' }, ] as any[], prevX: null as any, prevY: null as any, nextX: null as any, nextY: null as any, stop: false, }); const img = reactive({ w: 50, h: 50, halfW: 25, halfH: 25, x: null as any, y: null as any, direction: null as any, show: false, }); onMounted(() => { let data = state.keyPoints; for (let i = 0; i < data.length; i++) { let el = data[i]; let el2 = data[i + 1]; if (el2) { let x1 = el.x; let y1 = el.y; let x2 = el2.x; let y2 = el2.y; let A = y1 - y2; let B = x2 - x1; let angle = Math.atan2(A, B); let theta = angle * (180 / Math.PI); el.direction = 0 - theta + 90; } else { el.direction = Number(el.direction) || 0; } } // console.log('data=', state.keyPoints); let ctx = canvasRef.value?.getContext('2d'); if (ctx) { getCanvas(ctx, state.keyPoints); } }); function onStart() { state.stop = true; // 先停止 let ctx = canvasRef.value?.getContext('2d'); if (ctx) { setTimeout(() => { ctx.clearRect(0, 0, 700, 700); state.stop = false; getCanvas(ctx, state.keyPoints); }, 500); } } function onStop() { state.stop = true; } function getCanvas(ctx: any, keyPoints: any[]) { if (keyPoints.length === 0) { img.show = false; return; } img.show = true; if (keyPoints.length === 1) { // 如果只有1个点 img.x = keyPoints[0].x; img.y = keyPoints[0].y; img.direction = keyPoints[0].direction; return; } // 设置线条样式 ctx.strokeStyle = '#ff502f'; ctx.lineWidth = 12; ctx.lineJoin = 'round'; ctx.lineCap = 'round'; // 定义起点和终点的坐标 state.prevX = keyPoints[0].x; state.prevY = keyPoints[0].y; // 第一帧执行的时间 let startTime: number; // 期望动画持续的时间 1s内执行60次【60帧/秒】 const duration = 3000; // 动画被切分成若干段,每一段所占总进度的比例 const partProportion = 1 / (keyPoints.length - 1); // 缓存绘制第n段线段的n值,为了在进行下一段绘制前把这一段线段的末尾补齐 let lineIndexCache = 1; const step = (currentTime: number) => { if (state.stop) { console.log('动画已停止'); return; } console.log('stop'); // 第一帧绘制时记录下开始的时间 !startTime && (startTime = currentTime); // 已经过去的时间(ms) const timeElapsed = currentTime - startTime; // 动画执行的进度 {0,1} const progress = Math.min(timeElapsed / duration, 1); // 描述当前所绘制的是第几段线段 const lineIndex = Math.min( Math.floor(progress / partProportion) + 1, keyPoints.length - 1 ); // 当前线段的进度 {0,1} const partProgress = (progress - (lineIndex - 1) * partProportion) / partProportion; // 绘制方法 const draw = () => { if (state.stop) return; ctx.beginPath(); ctx.moveTo(state.prevX, state.prevY); // 当绘制下一段线段前,把上一段末尾缺失的部分补齐 if (lineIndex !== lineIndexCache) { ctx.lineTo(keyPoints[lineIndex - 1].x, keyPoints[lineIndex - 1].y); lineIndexCache = lineIndex; } state.prevX = state.nextX = ~~( keyPoints[lineIndex - 1].x + (keyPoints[lineIndex].x - keyPoints[lineIndex - 1].x) * partProgress ); state.prevY = state.nextY = ~~( keyPoints[lineIndex - 1].y + (keyPoints[lineIndex].y - keyPoints[lineIndex - 1].y) * partProgress ); img.x = state.nextX - img.halfW; img.y = state.nextY - img.halfH; img.direction = keyPoints[lineIndex - 1].direction; ctx.lineTo(state.nextX, state.nextY); ctx.stroke(); }; draw(); if (progress < 1) { requestAnimationFrame(step); } else { state.stop = true; console.log('动画执行完毕'); } }; requestAnimationFrame(step); } </script> <style lang="scss" scoped> .box { display: flex; justify-content: center; align-items: center; .content { width: 700px; height: 700px; position: relative; #myCanvas { background-color: rgb(199, 197, 197); } .img { position: absolute; background-image: url('./imgs/MCS_AGV_o.svg'); background-size: 100% 100%; top: 0; left: 0; } } } </style>