vue3 带箭头的线段运动动画

我想实现一个带箭头的线段从起点出发,沿着设定的几个坐标运动该如何实现哎,线段可以改宽度渐变色,

img

可以使用Vue 3中的<canvas>元素和requestAnimationFrame()方法,实现带箭头的线段从一个坐标点开始,并沿指定路径运动。

  1. 定义需要绘制的坐标点数组。
  2. 定义需要绘制的线段的宽度和渐变色。
  3. 在mounted钩子函数中获取canvas元素,并获取其绘画上下文。
  4. 在绘画函数中用requestAnimationFrame()方法循环调用绘制函数,以使线段动起来。
  5. 在绘制函数中,清除画布,设置线段样式(宽度和颜色),依次连接各点,绘制箭头。

下面是Vue 3中的示例代码,可以根据需要修改坐标点数组:

<template>
  <canvas ref="canvas" style="width:100%; height:100%;" />
</template>

<script>
export default {
  mounted() {
    this.ctx = this.$refs.canvas.getContext('2d');
    this.width = this.$refs.canvas.width;
    this.height = this.$refs.canvas.height;

    // 定义坐标点
    this.points = [
      { x: 100, y: 100 },
      { x: 200, y: 200 },
      { x: 300, y: 100 },
      { x: 400, y: 200 },
      { x: 300, y: 300 },
      { x: 200, y: 200 },
      { x: 100, y: 300 },
    ];

    // 定义线段样式
    this.lineWidth = 6;
    this.gradient = this.ctx.createLinearGradient(0, 0, this.width, 0);
    this.gradient.addColorStop(0, '#00ff00');
    this.gradient.addColorStop(1, '#0000ff');

    // 绘制
    this.animate();
  },
  methods: {
    animate() {
      // 绘制函数
      const draw = (t) => {
        this.ctx.clearRect(0, 0, this.width, this.height);

        // 设置线段样式
        this.ctx.lineWidth = this.lineWidth;
        this.ctx.strokeStyle = this.gradient;
        this.ctx.lineCap = 'round';
        this.ctx.lineJoin = 'round';
        this.ctx.setLineDash([]);

        // 开始绘制
        this.ctx.beginPath();
        this.ctx.moveTo(this.points[0].x, this.points[0].y);

        for (let i = 1; i < this.points.length; i++) {
          const dx = this.points[i].x - this.points[i - 1].x;
          const dy = this.points[i].y - this.points[i - 1].y;

          // 计算箭头角度和长度
          const angle = Math.atan2(dy, dx);
          const len = Math.sqrt(dx * dx + dy * dy);
          const arrowLen = Math.min(20, len / 3);

          // 计算箭头坐标
          const arrowX = this.points[i - 1].x + dx - arrowLen * Math.cos(angle);
          const arrowY = this.points[i - 1].y + dy - arrowLen * Math.sin(angle);

          // 连接线段
          this.ctx.lineTo(this.points[i].x, this.points[i].y);

          if (i == this.points.length - 1) {
            // 最后一段线段时绘制箭头
            this.ctx.moveTo(arrowX, arrowY);
            this.ctx.lineTo(this.points[i - 1].x + dx, this.points[i - 1].y + dy);
            this.ctx.lineTo(this.points[i - 1].x + dx - arrowLen * Math.cos(angle - Math.PI / 6), this.points[i - 1].y + dy - arrowLen * Math.sin(angle - Math.PI / 6));
            this.ctx.lineTo(this.points[i - 1].x + dx - arrowLen * Math.cos(angle + Math.PI / 6), this.points[i - 1].y + dy - arrowLen * Math.sin(angle + Math.PI / 6));
            this.ctx.lineTo(arrowX, arrowY);
          }
        }

        this.ctx.stroke();
        requestAnimationFrame(draw);
      };

      requestAnimationFrame(draw);
    },
  },
};
</script>

该代码中,requestAnimationFrame方法会传递一个时间戳参数t。在实现动画过程中,我们可以根据这个值,计算出运动过程中的每个样式值,实现更丰富的动画效果。

在Vue3组件中,创建一个SVG元素,并在其中添加一个路径元素来表示线段。

<template>
  <svg width="100%" height="100%">
    <path
      ref="line"
      stroke="#000"
      stroke-width="2"
      fill="transparent"
      marker-end="url(#arrow)"
      :d="path"
    />
    <defs>
      <marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3"
              orient="auto">
        <path d="M0,0 L0,6 L9,3 z" fill="#000"/>
      </marker>
    </defs>
  </svg>
</template>
2.在Vue3组件中,定义路径数据(path)和动画驱动器(progress),并将路径数据通过路径动画驱动器来实现渐变动画效果
```javascript
<script>
import { ref, watchEffect } from 'vue';

export default {
  setup() {
    const line = ref(null);
    const points = ref([
      { x: 100, y: 100 },
      { x: 200, y: 300 },
      { x: 400, y: 200 },
      { x: 500, y: 400 },
    ]);
    const path = ref('');
    const progress = ref(0);

    watchEffect(() => {
      const len = line.value.getTotalLength();
      const segmentLength = len / (points.value.length - 1);
      path.value = `M${points.value[0].x},${points.value[0].y}${
        points.value
          .slice(1)
          .map((point, index) =>
            `L${point.x},${point.y} ${getArrowPath(
              progress.value * segmentLength,
              index === points.value.length - 2
            )}`
          )
          .join('')
      }`;
    });

    function getArrowPath(progress, lastSegment) {
      if (lastSegment) {
        return '';
      }
      const x1 = progress - 4;
      const x2 = progress + 4;
      return `M${x1},-3 L${x2},-3 L${progress},3 z`;
    }

    return {
      line,
      points,
      path,
      progress,
    };
  },
  mounted() {
    this.progress = 1;
    setTimeout(() => {
      this.progress = 0;
    }, 1000);
  },
};
</script>
在上述代码中,我们使用SVG路径元素的getTotalLength()方法获取路径总长度,通过计算线段划分后的每段长度并动态更新路径来实现图形的动画效果。

在getArrowPath()方法中,根据当前线段的进度计算箭头的位置和方向,并返回对应的SVG路径数据。

可以通过修改SVG的stroke和stroke-width属性以及颜色来定义渐变色和线条宽度等更多样式。

最后,在组件挂载到DOM中后,我们通过设置progress的值来触发路径动画,在一定时间后将progress的值重置为0,以便下次动画的触发。

注意:上述代码使用了vue3的Composition API,需要使用Vue3及以上版本才能正常运行

```

保存下面代码到html,直接运行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<svg width="500" height="500">
    <g id="my-arrow" transform="translate(50,37)">
        <path id="my-path" d="M 50,50 L 100,150 L 150,100" stroke="#ffff" fill="none"/>
        <polygon points="0,0 0,24 24,12" fill="black" stroke="none"/>
    </g>
    <path id="my-path-copy"
          d="M 50,50 L 100,150 L 150,100"
          stroke-width="2"
          stroke="black"
          fill="none"/>
</svg>

<script src="https://cdn.jsdelivr.net/npm/animejs"></script>
<script>
  const myPath = document.getElementById("my-path");
  const myPathCopy = document.getElementById("my-path-copy");
  const pathLength = myPath.getTotalLength();

  myPathCopy.style.strokeDasharray = pathLength;
  myPathCopy.style.strokeDashoffset = pathLength;

  const myArrow = document.getElementById("my-arrow");
  anime({
    targets: [myArrow, myPathCopy],
    easing: "linear",
    strokeDashoffset: [pathLength, 0],
    // translateX: pathLength,
    duration: 2000,
    loop: 3,//3 、true
    direction: "alternate",
    //update 是 AnimeJS 的一个选项,它指定了在动画执行期间要调用的回调函数。在每次更新时执行此函数,可以使用它来更新元素的属性,或根据当前进度计算其他动画的参数。
    update: function (a) {
      //这个函数会被动画对象在每一帧进行调用,下一个帧将基于 linear 缓动函数在 duration 毫秒时间后执行。update 函数的参数 a 包含了动画执行的各种数据,包括当前进度,easing 等,我们可以在回调内使用 a.progress 获取当前进度并进行其他操作。
      // 通过使用该回调,我们可以在动画执行期间对 SVG 元素进行非常详细的控制。
      console.log(a)
      //在 AnimeJS 库中,a 参数表示动画对象的状态。它是一个包含各种数据的对象,可以用它来进行动画的各种细节控制。
      // 具体来说,a 对象包含以下属性:
      // a.progress:动画进度的百分比值(0-100)。
      // a.currentTime:当前动画的绝对时间(毫秒)。
      // a.duration:动画的总时间(毫秒)。
      // a.delay:动画的延迟时间(毫秒)。
      // a.beginTime:动画的开始时间(毫秒)。
      // a.paused:动画是否暂停。
      // a.easing:动画使用的缓动函数。
      // a.direction:动画的播放方向。
      // a.loop:动画的循环方式。
      // a.autoplay:动画是否自动播放。
      // a.update:动画每一帧更新回调函数。
      // 通过使用 a 对象,我们可以在动画执行期间编写更高级的控制逻辑,并根据动画的状态来进行回调函数。
      var progress = (pathLength / 100) * a.progress;
      var point = myPath.getPointAtLength(progress);
      myArrow.setAttribute("transform", "translate(" + point.x + "," + point.y + ")");
    }
  });
</script>
</body>
</html>