Manipulate svg circle origin to create rotation animation around center - reactjs

I have a loader (spinner) drawn on a page via two <circle />. Need to spin both paths in a different direction with origin centered, so, circles spin around the center of an SVG and don't translate, per say.
Trying to animate it transform: rotate(360deg). Paths go haywire and have origin somewhere else. Tried managing viewBox for intended results and didn't succeed.
import React, { PureComponent } from 'react';
import styled from 'styled-components';
import { prop } from 'styled-tools';
class Loader extends PureComponent {
render() {
return (
<Spinner
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
preserveAspectRatio="xMidYMid"
viewBox="0 0 100 100"
>
<circle
className='outer'
cx="50"
cy="50"
r="40"
fill="none"
stroke="#374a67"
stroke-dasharray="63 63"
stroke-linecap="round"
stroke-width="4"
/>
<circle
className='inner'
cx="50"
cy="50"
r="35"
fill="none"
stroke="#d50000"
stroke-dasharray="55 55"
stroke-dashoffset="55"
stroke-linecap="round"
stroke-width="4"
/>
</Spinner>
)
}
}
const Spinner = styled.svg`
& .outer {
animation: rotate 2s linear infinite;
}
& .inner {
animation: reverseRotate 2s linear infinite;
}
#keyframes rotate {
100% {
transform: rotate(360deg);
}
}
#keyframes reverseRotate {
100% {
transform: rotate(-360deg);
}
}
`;
export default Loader;
Don't know how to make an actual working snippet out of my piece of code, sry
Here's an example of my current animation:

You need to set the transform-origin in the center of your svg. However you may do it differently. Instead of animating the transform you may animate the stroke-dashoffset like this:
.outer {
stroke-dashoffset:0;
animation: rotate 2s linear infinite;
}
.inner {
animation: reverseRotate 2s linear infinite;
}
#keyframes rotate {
100% {
stroke-dashoffset:126px;
}
}
#keyframes reverseRotate {
100% {
stroke-dashoffset:-55px;
}
}
svg{border:1px solid}
<svg xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
preserveAspectRatio="xMidYMid"
viewBox="0 0 100 100"
>
<circle
class='outer'
cx="50"
cy="50"
r="40"
fill="none"
stroke="#374a67"
stroke-dasharray="63"
stroke-linecap="round"
stroke-width="4"
/>
<circle
class='inner'
cx="50"
cy="50"
r="35"
fill="none"
stroke="#d50000"
stroke-dasharray="55"
stroke-dashoffset="55"
stroke-linecap="round"
stroke-width="4"
/>
</svg>

Welcome to Stack.
You need to make a few small tweaks to get it working.
Just use one animation that goes from 0% to 100%.
Animate from 0deg to 360deg
#keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
For the reverse animation, you can reverse the direction using
animation-direction: alternate; in your CSS

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

SVG Image viewbox resizing

I have this code that you can run below in the snippet. My issue is that in my production environment I have 2 SVGs, one on top of the other, each containing all sorts of content. When the user clicks on either side of the header, as seen in the snippet, the SVG that is on top should grow or shrink to either show more or less of the SVG underneath (I know this isn't perhaps performant with regards to the SVG potentially being drawn beneath - but my question likely involves this, as the answer probably has something to do with a lack of understanding of SVG viewbox on my part).
The issue is that because I want the images contents to remain the same size as the SVG on top grows or shrinks I decided to use Greensock in order to animate the grow/shrink of both the width AND the viewbox. This though causes very nasty glitching - in the snippet, run the code and click the Black box (pay attention especially to the bottom left corner of the image, but also to the jumps in the leftmost circle)!
Am I mistaken in changing both? Is there a way to only alter the width? Would this still cause the glitch as seen below?
Appreciate your help!
class Header extends React.Component {
constructor(props) {
super(props);
this.leftPanel = null;
this.leftPanelTween = null;
this.rightPanel = null;
this.rightPanelTween = null;
}
slideLeftHandler = () => {
this.leftPanelTween = TweenMax.to(this.leftPanel, 2, {width: '100%', attr: {viewBox: "0 0 500 150"}});
};
slideRightHandler = () => {
this.rightPanelTween = TweenMax.to(this.leftPanel, 2, {width: 0, attr: {viewBox: "0 0 0 150"}});
};
render() {
return (
<div class="Header">
<svg width="100%" viewBox="0 0 500 150" ref={el => this.rightPanel = el} onClick={this.slideRightHandler}>
<rect id = "middle" width="100%" height="100%" fill="black">
</rect>
<circle cx="400" cy="75" r="25" fill="red">
</circle>
<circle cx="100" cy="75" r="25" fill="red">
</circle>
</svg>
<svg width="50%" viewBox="0 0 250 150" ref={el => this.leftPanel = el} onClick={this.slideLeftHandler}>
<rect id = "middle" width="100%" height="100%" fill="red">
</rect>
<circle cx="100" cy="75" r="25" fill="black">
</circle>
<circle cx="400" cy="75" r="25" fill="black">
</circle>
</svg>
</div>
);
}
}
ReactDOM.render(
<Header />,
document.getElementById('app')
);
.Header {
margin: 0;
cursor: pointer;
}
.Header svg {
position: absolute;
}
.Header .leftPanel {
top: 0;
left: 0;
}
.Header .rightPanel {
top: 0;
right: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.2/TweenMax.min.js"></script>
<div id="app"></div>
Animating the viewBox is not performant. I recommend making the SVG as large as it needs to be at the end all of the time and just animate the width of the rectangle within the SVG that was clicked.

SVG stroke not showing inside of clipPath

I have the following Codepen, where I'm trying to animate a stroke of a circle around an image.
So far, I've got a circle SVG which is clipping an image, but it doesn't show the stroke inside of clipPath.
How do I get the border to show?
class App extends React.Component {
render() {
return (
<svg width='48' height='48'>
<defs>
<clipPath id='circleView'>
<circle cx='24' cy='24' r='23' fill='none' stroke='red' strokeWidth='2' />
</clipPath>
</defs>
<image width='48' height='48' xlinkHref={'https://source.unsplash.com/random'} clipPath='url(#circleView)' />
</svg>
)
}
}
As Robert said, the child SVG figures in the clip-path line are not displayed.
Therefore, for the animation you need to add another circle outside the clip-path
<circle cx="25" cy="24" r="14" fill="none" stroke="red" strokeWidth="2" />
.container {
width:25%;
height:25%;
}
<div class="container">
<svg viewBox="0 0 48 48" >
<defs>
<clipPath id='circleView'>
<circle cx='24' cy='22' r='16' fill='none' stroke='red' stroke-width='2' />
</clipPath>
</defs>
<image width="100%" height="100%" xlink:href='https://i.stack.imgur.com/O9eO8.jpg'
clip-path='url(#circleView)' />
<circle cx='24' cy='22' r='16' fill='none' stroke='red' strokeWidth='2' />
</svg>
</div>
To animate a circle, use the change in the stroke-dashoffset attribute from maximum to zero. values="(100.48;0)"
Animation starts after click
.container {
width:25%;
height:25%;
}
<div class="container">
<svg id="svg1" viewBox="0 0 48 48">
<defs>
<clipPath id='circleView'>
<circle cx='24' cy='22' r='16' fill='none' stroke='red' stroke-width='2' />
</clipPath>
</defs>
<image width="100%" height="100%" xlink:href='https://i.stack.imgur.com/O9eO8.jpg' clip-path='url(#circleView)' />
<circle transform="rotate(-90 24 22)" cx="24" cy="22" r="16" fill='none' stroke='red' strokeWidth='2'
stroke-dasharray="100.48" stroke-dashoffset="100.48" >
<animate
attributeName="stroke-dashoffset"
dur="1s"
begin="svg1.click"
values="100.48;0"
fill="freeze"/>
</circle>
</svg>
</div>
Variant of animation with CSS
I added transparency animation to the circle animation.
The animation begins when you hover the mouse cursor.
.container {
width:25%;
height:25%;
}
#crc1 {
fill:skyblue;
stroke-width:1;
stroke:red;
stroke-dasharray:100.48;
stroke-dashoffset:100.48;
fill-opacity:0.9;
}
#crc1:hover {
animation: dash 1.5s ease-out forwards;
}
#keyframes dash {
0% { stroke-dashoffset: 100.48; fill-opacity:0.9; }
50% { fill-opacity:0.45;}
100% { stroke-dashoffset: 0; fill-opacity:0; }
}
#img1 {
clip-path: url(#circleView);
}
<div class="container">
<svg id="svg1" viewBox="0 0 48 48">
<defs>
<clipPath id='circleView'>
<circle cx='24' cy='22' r='16'/>
</clipPath>
</defs>
<image width="100%" height="100%" xlink:href='https://i.stack.imgur.com/O9eO8.jpg'
clip-path='url(#circleView)' />
<circle id="crc1" cx="24" cy="22" r="16" />
</svg>
</div>

Resources