Animating svg text using framer motion in React - reactjs

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.

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

Related

how to make a useref function to intial state in react js

In the below code i have used frame motion library where in the motion.img element can be dragged
here i need to make a button with onclick listener where once i click the button the img element will be in the intial position.so what should i do in this casetake a Look at this ui for reference
const constraintsRef = useRef(null);
return(
<motion.div class="row md-row-cols-3 row-cols-4 justify-content-center conainer "ref={constraintsRef}>
<motion.img
className="box"
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
src={logo1}
height="60"
width="60"
alt=" v"
drag dragConstraints={constraintsRef}
/>
</motion.div>)};
i have tried usestate but it didnt showup in the framer motion docs they used ref function for this
https://codesandbox.io/s/t9uoz?module=%2Fsrc%2FExample.tsx

Animate input width from right to left framer motion

I'm trying to make my input appears with an animation making its width size going from 0 to 50%. For this, I've used framer-motion. I've managed to make the animation but by default it's growing from the left to the right. Is there any way I could make it change to have it growing from the right to the left ?
sandbox simple reproduction
import { motion } from "framer-motion";
import { useState } from "react";
export default function App() {
const [toggle, setToggle] = useState(false);
return (
<div>
<button onClick={() => setToggle(!toggle)}>toggle input</button>
<div style={{ marginTop: 25 }}>
{toggle && (
<motion.input
initial={{ width: "0%" }}
animate={{ width: "50%" }}
transition={{ duration: 1, origin: 1 }}
/>
)}
</div>
</div>
);
}
Pretty sure it's not the best way of doing this but I think changing the following should fix your problem. Added x with 50vw = 50% of viewport width (0vw = 0% width) for the initial "position" and for animate x with 0 for the ending position as well as width of 50vw.
<motion.input
initial={{ width: "0vw", x: "50vw" }}
animate={{ width: "50vw", x: 0 }}
transition={{ duration: 1, origin: 1 }}
/>
You can find more details about it on the API Documentation of the motion component under Value type conversion https://www.framer.com/docs/component/##value-type-conversion.

freamer motion pathLength props in SVG animation not working

i got the below svg , i wanted the svg image to be drawn on the screen as soon as the page loads but just only the opacity aspect of the animation is playing , am i doing anything wrong here? , below i defined my variant ina saparate file then imported it to the component file , i set the pathLength to o at the hidden stage and 1 at the animate stage with duration of 2
export const pathVariants = {
hidden: {
opacity: 0,
pathLength: 0,
},
visible: {
opacity: 1,
pathLength: 1,
transition: {
duration: 2,
ease: "easeInOut",
},
},
};
import { motion } from "framer-motion";
import { pathVariants } from "../animations/motion-variants";
<Box className={classes.musicnote}>
<svg
version="1.1"
id="musical_notes"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="180px"
height="180px"
viewBox="0 0 353 413"
enable-background="new 0 0 353 413"
>
<motion.path
fill-rule="evenodd"
clip-rule="evenodd"
d="M253.084,7.255c14.601,14.299,22.646,33.409,41.014,44.169
c14.282,8.366,35.335,8.5,44.169,22.084c5.939,9.133,7.419,27.392,0,35.756c-5.068-35.817-35.035-32.236-57.841-49.428
c-5.958-6.66-11.919-13.321-17.878-19.981c4.557,41.711,9.115,83.435,13.671,125.146c2.243,12.553,8.561,32.938,4.207,46.273
c-4.949,15.16-21.632,26.625-37.859,30.498c-30.948,7.385-66.418-20.607-55.737-51.531c11.528-33.374,57.613-34.349,83.08-14.723
C264.303,119.437,258.693,63.337,253.084,7.255z M91.131,16.72c18.335,0.109,23.3,7.739,29.446,19.981
c24.146,48.092-3.925,92.918-25.239,125.146c3.855,16.124,7.712,32.252,11.568,48.376c83.31-2.216,86.255,108.859,28.394,127.249
c20.391,66.648-69.765,92.072-88.338,46.272c0.701-3.504,1.402-7.011,2.104-10.516c6.455-4.587,11.014-5.638,19.981-3.155
c3.118,4.554,4.93,6.783,5.258,14.724c-3.031,5.075-6.147,5.788-2.103,9.464c34.421,22.119,59.37-18.465,51.531-51.53
C53.013,354.385-7.3,304.607,16.464,229.152c12.56-39.879,41.575-60.509,63.099-90.441C69.406,100.691,53.458,37.627,91.131,16.72z
M94.286,27.236c-27.187,17.962-17.569,72.817-9.465,105.165c0.351-0.701,0.702-1.402,1.052-2.104
c15.737-12.747,50.499-82.22,15.774-100.958C99.194,28.639,96.739,27.938,94.286,27.236z M86.925,171.312
c-17.209,23.01-38.111,44.916-47.324,75.719c-12.567,42.011,11.587,79.395,41.014,87.287c14.073,3.774,29.021-1.189,41.014-3.155
c0-1.402,0-2.804,0-4.207c-7.36-35.051-14.724-70.113-22.084-105.165c-24.299,5.054-45.932,43.274-32.601,74.667
c5.608,7.711,11.218,15.425,16.826,23.136c-1.402,0-2.805,0-4.206,0c-31.384-15.551-30.442-66.646-9.465-92.544
c6.799-8.395,18.998-9.923,27.343-16.826C94.635,200.104,93.974,177.967,86.925,171.312z M109.009,219.688
c8.412,34.701,16.827,69.412,25.239,104.113C172.229,297.401,164.674,221.407,109.009,219.688z M331.958,276.477
c-0.188,25.195-21.731,102.605-35.756,112.526c-8.288,5.863-32.662,13.989-43.118,4.206c-3.886-2.521-4.219-3.958-4.207-10.516
c6.199-14.664,30.017-25.876,49.428-16.827c3.337-18.992,16.644-43.981,12.62-62.047c-0.351,0-0.702,0-1.052,0
c-7.049,0.944-63.298,9.471-64.15,10.517c-13.705,27.973-22.646,121.783-77.821,84.132c0-3.505,0-7.011,0-10.517
c8.457-12.669,24.219-19.98,45.22-16.826c1.588-11.785,18.99-73.682,25.24-77.822c11.615-5.578,29.193,0.255,43.118-2.103
C301.044,287.886,312.064,277.23,331.958,276.477z M316.183,286.993c-15.666,10.852-43.111,10.694-66.253,13.671
c-0.351,1.402-0.701,2.805-1.052,4.207c0.701,0,1.402,0,2.104,0c17.538,7.495,54.068-5.281,65.202-11.568
C316.183,291.2,316.183,289.096,316.183,286.993z"
variants={pathVariants}
initial="hidden"
animate="visible"
/>
</svg>
</Box>

Draw a dashed path using Framer Motion in React

I'm trying to draw a dashed path, or at least give that illusion, using Framer Motion. Think animating a foot path on a treasure map. Animating the path length seems to be a common method, and so I've implemented it like below.
<motion.span
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
d="...a list of coordinates"
stroke="#000"
strokeWidth="5"
strokeDasharray="8"
/>
But it appears animating the path length doesn't work well with strokeDasharray. When I add the strokeDasharray value using the attribute, the path length animates but the strokeDasharray value, when inspected, reads 2000px instead of 8px. And when I add the strokeDasharray using CSS or inline styling, the path is dashed correctly, but the animation doesn't work.
From what I've read, strokeDasharray uses the path length when doing it's computations, so I'm guessing the initial "0" value is throwing things off. Might be way off. I don't know.
Is there a simple fix here? Or should I reassess how I go about the animation? Thank you!
Not a solution using Framer Motion, but found this pen by Ruskinz that does the job using some css animation. The HTML looks like this:
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="340" height="333" viewBox="0 0 340 333">
<defs>
<path id="path1" d="M66.039,133.545c0,0-21-57,18-67s49-4,65,8s30,41,53,27s66,4,58,32s-5,44,18,57s22,46,0,45s-54-40-68-16s-40,88-83,48s11-61-11-80s-79-7-70-41 C46.039,146.545,53.039,128.545,66.039,133.545z" />
<mask id="mask1"><use class="mask" xlink:href="#path1"></mask>
</defs>
<use class="paths" xlink:href="#path1" mask="url(#mask1)" />
</svg>
And the CSS looks like this:
.paths {
fill: none;
stroke: grey;
stroke-dasharray: 5;
stroke-width: 5;
stroke-linejoin: round;
}
.mask {
fill: none;
stroke: white;
stroke-width: 10;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear alternate infinite;
}
/* does not work in IE, need JS to animate there */
#keyframes dash {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}
See the full pen at https://codepen.io/elliz/pen/prYqwx
I had the same issue because i wanted to animate it with framer-motion and not in css. What i just did is i just put an exact copy of the path with the dashedArray line below the path which i'm going to animate.
It will act as an overlay. I just gave it the stroke color of the background and tweaked the stroke-width. I don't know how it would be with a linearGradient background. But in my case with a static background color it worked.
import { motion } from 'framer-motion';
export default function Path({ pathColor, bg }) {
return (
<svg
width="245.24878"
height="233.49042"
viewBox="0 0 64.888737 61.777671"
version="1.1"
id="svg1033">
<defs
id="defs1030" />
<g
id="layer1"
transform="translate(-20.472293,-22.027827)">
<g
id="g484"
transform="translate(11.886667,6.306109)"
>
<motion.path
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{
pathLength: { delay: 0.4, type: "tween", duration: 3, bounce: 0 }
}}
stroke={pathColor}
strokeDasharray='3.846, 1.282'
strokeDashoffset='0'
strokeWidth='0.641'
style={{ fill: 'none', fillRule: 'evenodd', strokeLinejoin: 'round' }}
d="m 70.258127,15.782623 c 0,0 -1.867161,10.194243 -5.854843,12.473363 -9.471023,5.413069 -22.204956,-6.41444 -32.583479,-3.054701 -9.553598,3.092694 -21.015474,9.948708 -22.6557013,19.855557 -1.7758628,10.726077 5.8258513,25.311914 16.2917403,28.255989 11.258271,3.166974 19.313188,-18.990719 30.80157,-16.800859 5.208004,0.992724 10.182339,12.218805 10.182339,12.218805"
id="path1154"
/>
<path
stroke={bg}
strokeDasharray='3.846, 2.282'
strokeDashoffset='0'
strokeWidth='1.641'
style={{ fill: 'none', fillRule: 'evenodd', strokeLinejoin: 'round' }}
d="m 70.258127,15.782623 c 0,0 -1.867161,10.194243 -5.854843,12.473363 -9.471023,5.413069 -22.204956,-6.41444 -32.583479,-3.054701 -9.553598,3.092694 -21.015474,9.948708 -22.6557013,19.855557 -1.7758628,10.726077 5.8258513,25.311914 16.2917403,28.255989 11.258271,3.166974 19.313188,-18.990719 30.80157,-16.800859 5.208004,0.992724 10.182339,12.218805 10.182339,12.218805"
id="path1155"
/>
</g>
</g>
</svg>
);
}

Framer Motion : How to fix {No initial value for "pathLength" can be inferred. Ensure an initial value for "pathLength" is defined on the component}

The error specified in the title is obtained when using framer motion's pathLength property to animate svg.
Other properties (I tried rotate and opacity) work as expected without any issues.
What might be the possible issues?
<svg width="84"
height="84"
viewBox="0 0 84 84"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<motion.path
d=" \\ some path"
initial={{
pathLength: 0,
}}
animate={{
pathLength: 1,
}} />
</svg>
Even if the error goes away, the svg still doesn't animate

Resources