IndexPage.vue 5.27 KB
<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>