简体   繁体   English

使用 Framer Motion 沿路径动画 SVG

[英]Animate SVG along path with Framer Motion

Is there a way to animate a SVG element along a path (preferably a SVG path itself) with Framer Motion?有没有办法使用 Framer Motion 沿路径(最好是 SVG 路径本身)为 SVG 元素设置动画? Furthermore, is it possible that the animated SVG element changes its rotation in turns?此外,动画SVG元素是否可能轮流改变其旋转? The following shows an example of what I would like to achieve (not exactly, just an example):下面显示了我想要实现的示例(不完全是,只是一个示例):

https://tympanus.net/codrops/2019/12/03/motion-paths-past-present-and-future/ https://tympanus.net/codrops/2019/12/03/motion-paths-past-present-and-future/

With the examples given in the Framer Motion documentation I do not find anything like that in the declarative ways, but I wonder whether this is achievable through MotionValues, the onUpdate method or the imperative AnimationControls somehow?对于 Framer Motion 文档中给出的示例,我没有在声明性方式中找到任何类似的东西,但我想知道这是否可以通过 MotionValues、onUpdate 方法或命令式 AnimationControls 以某种方式实现?

You can animate the pathLength property by using a motion.path .您可以使用motion.pathpathLength属性设置动画。 Then pair that with the offsetDistance on the element that is following the path.然后将其与跟随路径的元素上的offsetDistance配对。

import React from "react"
import { motion } from "framer-motion"
import "./styles.css"

const transition = { duration: 4, yoyo: Infinity, ease: "easeInOut" }

export default function App() {
  return (
    <div className="container">
      <svg xmlns="http://www.w3.org/2000/svg" width="451" height="437">
        <motion.path
          d="M 239 17 C 142 17 48.5 103 48.5 213.5 C 48.5 324 126 408 244 408 C 362 408 412 319 412 213.5 C 412 108 334 68.5 244 68.5 C 154 68.5 102.68 135.079 99 213.5 C 95.32 291.921 157 350 231 345.5 C 305 341 357.5 290 357.5 219.5 C 357.5 149 314 121 244 121 C 174 121 151.5 167 151.5 213.5 C 151.5 260 176 286.5 224.5 286.5 C 273 286.5 296.5 253 296.5 218.5 C 296.5 184 270 177 244 177 C 218 177 197 198 197 218.5 C 197 239 206 250.5 225.5 250.5 C 245 250.5 253 242 253 218.5"
          fill="transparent"
          strokeWidth="12"
          stroke="rgba(255, 255, 255, 0.69)"
          strokeLinecap="round"
          initial={{ pathLength: 0 }}
          animate={{ pathLength: 1 }}
          transition={transition}
        />
      </svg>
      <motion.div
        className="box"
        initial={{ offsetDistance: "0%", scale: 2.5 }}
        animate={{ offsetDistance: "100%", scale: 1 }}
        transition={transition}
      />
    </div>
  )
}

Example source: Matt Perry: https://codesandbox.io/s/framer-motion-motion-along-a-path-41i3v示例来源:Matt Perry: https://codesandbox.io/s/framer-motion-motion-along-a-path-41i3v

I don't believe offset-path works on Safari just yet so I was able to implement this without offset-path using Framer Motion's useMotionValue hook.我不认为offset-path 在 Safari 上有效,所以我能够使用Framer Motion 的useMotionValue钩子在没有offset-path的情况下实现它。

This starts by getting the total length possible of our progress path.首先是获取我们的进度路径的可能总长度。 Then, we get the starting path length from the motion listener.然后,我们从运动侦听器中获取起始路径长度。 This is 0 to start.这是0开始。

Now we multiply the starting length (0) with the total path length to get the starting X and Y coordinates of our circles.现在我们将起始长度 (0) 乘以总路径长度以获得圆的起始 X 和 Y 坐标。

ProgressX and ProgressY are tied to the center of the circles. ProgressX 和 ProgressY 与圆心相连。

In this case, it should place our circles at exactly the start point of the path.在这种情况下,它应该将我们的圆圈恰好放在路径的起点。

Doing it this way allows the circles to start at any distance from the start by modifying the motion listener's initial value.这样做允许圆圈通过修改运动侦听器的初始值从起点的任何距离开始。

Then, just listen to the progressLength's updates and update the center of the circles whenever it changes.然后,只听 progressLength 的更新,并在它发生变化时更新圆心。

import { motion, useMotionValue } from "framer-motion";
import { useRef, useEffect } from "react";

export default function App() {
  const pathRefForeground = useRef(null);

  const progressLength = useMotionValue(0);
  const progressX = useMotionValue(0);
  const progressY = useMotionValue(0);

  useEffect(() => {
    const pathElementForeground = pathRefForeground.current;
    const totalPathLength = pathElementForeground.getTotalLength();
    const initialProgress = progressLength.get();

    const initialCoords = pathElementForeground.getPointAtLength(
      initialProgress * totalPathLength
    );

    progressX.set(initialCoords.x);
    progressY.set(initialCoords.y);

    const unsubscribe = progressLength.onChange((latestPercent) => {
      const latestPathProgress = pathElementForeground.getPointAtLength(
        latestPercent * totalPathLength
      );

      progressX.set(latestPathProgress.x);
      progressY.set(latestPathProgress.y);
    });

    return unsubscribe;
  }, []);

  const transition = {
    repeat: Infinity,
    bounce: 0.75,
    type: "spring",
    duration: 2
  };

  const progress = 50;

  return (
    <div
      className="App"
      style={{
        minHeight: 500,
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
        background: "#d9eefd"
      }}
    >
      <motion.svg width="500" height="50" viewBox="0 0 500 30">
        <path
          stroke="white"
          strokeWidth="10"
          strokeLinecap="round"
          d="M15 15 H490"
        />
        <motion.path
          d="M15 15 H490"
          stroke="#1f88eb"
          strokeWidth="10"
          strokeLinecap="round"
          ref={pathRefForeground}
          pathLength={progressLength}
          initial={{ pathLength: 0 }}
          animate={{ pathLength: progress / 100 }}
          transition={transition}
        />
        <motion.circle cx={progressX} cy={progressY} r="15" fill="#1f88eb" />
        <motion.circle cx={progressX} cy={progressY} r="5" fill="white" />
      </motion.svg>
    </div>
  );
}

Here's a live demo: https://codesandbox.io/s/patient-sea-nbhs5u?file=/src/App.js这是一个现场演示: https ://codesandbox.io/s/patient-sea-nbhs5u?file=/src/App.js

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM