freamer motion pathLength props in SVG animation not working - reactjs

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>

Related

Animated rotate circle SVG in reactjs

https://i.stack.imgur.com/kG5Sg.png
How can i dot blue running around circle. I have tried using transform:rotate in css but no success. Here is my code.
import './loadingicon.css'
const LoadingIcon = () => {
return (
< >
<div className='circle'>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<circle
class='track'
cx="80"
cy="80"
r="70"
fill="none"
stroke="#374a67"
stroke-dasharray="26.416, 5"
stroke-width="4"
/>
<circle class="filled" cx="80" cy="80" r="70" />
</svg>
</div>
</>
)
}
export default LoadingIcon;
loadingicon.css
.track,
.filled {
stroke-width: 10;
fill: none;
}
.track {
stroke: #eee;
}
.filled {
stroke: blue;
stroke-dashoffset: 420;
stroke-dasharray: 440;
}

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>
);
}

Reusable Component in React

I wanted to create a reusable component in my React app using Styled-Components. My problem is that i got the error
Error: Element type is invalid: expected a string (for built-in
components) or a class/function (for composite components) but got:
undefined. You likely forgot to export your component from the file
it's defined in, or you might have mixed up default and named imports.
pls check my code below:
import styled from 'styled-components'
import React from 'react'
const CustomSpinner = styled.svg`
animation: rotate 1s linear infinite;
width: 50px;
height: 30px;
& .path {
stroke: ${(props) => props.theme.colors.black};
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
#keyframes rotate {
100% {
transform: rotate(360deg);
}
}
#keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
`
export const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
Just need to change the export const Spinner = (...) => to export default (...) =>, like so:
export default ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
But I would suggest exporting like this instead so you can get full autocomplete from your code editor when importing components:
const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
export default Spinner;
It has to do with how you are importing this component.
When you do
export const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
Then you need to import it like
import {Spinner} from 'path/to/Spinner
and you are likely importing it like
import Spinner from 'path/to/Spinner'
The above line imports the default export from spinner, but you don't have a default export, so you need to change your import, or change your export to
export default ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
instead

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.
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

Position SVG path under image with dynamic height

I'm trying to position a custom path underneath images that are being loaded, the images will have the same width but the height will vary.
I'm using react-measure to get width, height of my image in svg to later on use that height to position the next element, but due to presumably aspect ratio, I get wrong values.
<svg viewBox="0 0 1000 2000">
<svg
x="150"
y="200"
>
<Measure
bounds
onResize={ (contentRef) => {
this.setState(prevState => ({ imgDimensions: contentRef.bounds }));
} }
>
{ ({ measureRef }) => (
<svg x="0">
<image className={ classes.img } ref={ measureRef } width="200" xlinkHref={ image } />
</svg>
) }
</Measure>
<rect x="5" y={ this.state.imgDimensions.height } height="2" width={ width } />
</svg>
</svg>
Example values:
imgDimensions:
bottom: 504.6000061035156
height: 176.39999389648438
left: 595
right: 835
top: 328.20001220703125
width: 240
and how it's positioned:
https://imgur.com/a/o4bbFG2
That black line should be exactly where the image ends.
But if I resize the website and refresh it - this is what I get: https://imgur.com/a/n9sxTic

Resources