Reputation: 992
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
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