BlackMath
BlackMath

Reputation: 992

Animating svg text using framer motion in React

I would like to animate the outline of letters of an svg text in React using Framer Motion, such that the line starts at certain point, and then completed gradually over a duration. I have the following example code

import { useState, useRef, useEffect } from "react";
import { motion } from "framer-motion";
import "./styles.css";

export default function App() {
  const [letterLength, setLetterLength] = useState(0);
  const letterRef = useRef();

  useEffect(() => {
    setLetterLength(letterRef.current.getTotalLength());
  }, []);

  return (
    <svg
      id="logo"
      width="998"
      height="108"
      viewBox="0 0 998 108"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <mask
        id="path-1-outside-1"
        maskUnits="userSpaceOnUse"
        x="0.867188"
        y="0.21875"
        width="998"
        height="108"
        fill="black"
      >
        <rect fill="white" x="0.867188" y="0.21875" width="998" height="108" />
        <path d="M15.3672 105H1.86719V2.625H15.3672V105Z" />
      </mask>
      <motion.path
        initial={{
          strokeDasharray: letterLength,
          strokeDashoffset: letterLength
        }}
        animate={{
          strokeDasharray: letterLength,
          strokeDashoffset: 0
        }}
        transition={{ duration: 2 }}
        d="M15.3672 105H1.86719V2.625H15.3672V105Z"
        stroke="#EAE3DC"
        strokeWidth="2"
        mask="url(#path-1-outside-1)"
        ref={letterRef}
      />
    </svg>
  );
}

The above code however, behaves strangely. It seems that the line is animated multiple times, before the desired result, which I don't want. I don't have this issue when animating the letters using vanilla JS and CSS. I think the issue has to do with the state variables, but I am not sure what it is exactly.

This is the code on codesandbox.

Upvotes: 0

Views: 3043

Answers (1)

Joshua Wootonn
Joshua Wootonn

Reputation: 1276

The weirdness is coming from the size of the dash array being animated. I don't think this was your intention, but letterLength is initialized to 0 and then changed to 230 on the second render.

I found this out by just setting letterLength to a const value.

I would suggest not messing with refs here and just using percentages

      <motion.path
        initial={{
          strokeDasharray: "100%",
          strokeDashoffset: "100%"
        }}
        animate={{
          strokeDashoffset: "0%"
        }}
        transition={{ duration: 2 }}
        d="M15.3672 105H1.86719V2.625H15.3672V105Z"
        stroke="#EAE3DC"
        strokeWidth="2"
        mask="url(#path-1-outside-1)"
      />

Like this: https://codesandbox.io/s/framer-motion-animate-stroke-with-dasharrayoffset-ezyuj?file=/src/App.js

Note: I have yet to find a nice way of using refs in animation without just hiding the elements with opacity during the ref initialization. Let me know if you find anything on the subject 🧐

**Edit from later in the day: **

You can also just set pathLength to 100 so you know the length ahead of time.

        <motion.path
          // this line is the important part
          pathLength={100}
          initial={{
            strokeDasharray: 100,
            strokeDashoffset: 100
          }}
          animate={{
            strokeDashoffset: 0
          }}
          transition={{ duration: 2 }}
          d="M15.3672 105H1.86719V2.625H15.3672V105Z"
          stroke="#aceca1"
          strokeWidth="2"
          mask="url(#path-1-outside-1)"
        />

Thanks @kirdes https://discordapp.com/channels/341919693348536320/716908973713784904/855851823578218507

Upvotes: 1

Related Questions