Animated rotate circle SVG in reactjs - 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;
}

Related

Best way to add conic gradient to the circle in SVG

I have a donut-like circle (progress circle) in React component and I want to add conic gradient to it. How to do that?
I know that in SVG we can not use conic gradients. I thought it can be done by using mask and usual block with added css with gradient but not sure how to do it correctly.
Now it looks like this:
React component:
import React from 'react';
import { Box, Text } from '#chakra-ui/react';
const GradientProgress = ({ modifier, score, size, strokeWidth }) => {
const DIAMETER = 51;
const WIDTH = DIAMETER + strokeWidth;
const RADIUS = DIAMETER / 2;
const CIRC = 2 * Math.PI * RADIUS;
const foregroundCirc = (CIRC * score) / 100;
const frontCirc = (CIRC * modifier) / 100;
return (
<Box
position='relative'
style={{ width: `${size}px`, height: `${size}px` }}
sx={{
circle: {
background:
'conic-gradient(from 270deg, #ff4800 10%, #dfd902 35%, #20dc68, #0092f4, #da54d8 72% 75%, #ff4800 95%)',
},
}}
>
<svg
className='donut'
transform='rotate(-90)'
viewBox={`0 0 ${WIDTH} ${WIDTH}`}
>
<circle
className='donut-ring'
cx={RADIUS + strokeWidth / 2}
cy={RADIUS + strokeWidth / 2}
fill='transparent'
pathLength={CIRC}
r={RADIUS}
stroke='#d2d3d4'
strokeWidth={strokeWidth}
/>
<circle
className='donut-segment'
cx={RADIUS + strokeWidth / 2}
cy={RADIUS + strokeWidth / 2}
fill='transparent'
opacity={0.5}
pathLength={CIRC}
r={RADIUS}
stroke='green'
strokeDasharray={`${frontCirc} ${CIRC - frontCirc}`}
strokeDashoffset={0}
strokeLinecap='round'
strokeWidth={strokeWidth}
/>
<circle
className='donut-segment'
cx={RADIUS + strokeWidth / 2}
cy={RADIUS + strokeWidth / 2}
fill='transparent'
pathLength={CIRC}
r={RADIUS}
stroke='red'
strokeDasharray={`${foregroundCirc} ${CIRC - foregroundCirc}`}
strokeDashoffset={0}
strokeLinecap='round'
strokeWidth={strokeWidth}
/>
</svg>
<Text>{modifier || score}</Text>
</Box>
);
};
export default GradientProgress;
You can achieve this by converting your circle to a mask and assign it to a foreignObject, this object contains a div with the conic-gradient style.
Here is an example how it works:
const control = document.getElementById('control');
const circle = document.getElementsByClassName('circle')[0];
const bg = document.getElementsByClassName('bg')[0];
control.addEventListener('input', function(event) {
circle.style.setProperty('--progress', event.target.valueAsNumber);
const deg = (event.target.valueAsNumber/100) * 360;
bg.style.setProperty('background', `conic-gradient(#00bcd4, #ffeb3b ${deg}deg)`);
});
.root {
width: 400px;
height: 400px;
text-align: center;
}
svg {
}
.circle {
stroke: white;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: calc((2 * 3.14) * 45);
stroke-dashoffset: calc((2 * 3.14 * 45) * (1 - calc(var(--progress, 50) / 100)));
transform-origin: 50% 50%;
transform: rotate(-87deg);
}
.bg {
background: conic-gradient(#00bcd4, #ffeb3b 180deg);
width: 100%;
height: 100%;
}
<div class='wrap'>
<div class='root'>
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<mask id="mask">
<circle class='circle' cx="50" cy="50" r="45" stroke='white' stroke-width='3' fill='none' />
</mask>
</defs>
<foreignObject x="0" y="0" width="100" height="100" mask="url(#mask)">
<div class='bg'></div>
</foreignObject>
</svg>
<input id="control" type="range" value="60" />
</div>
</div>

React useState() & useEffect: DOM element changed but not rendering

I'm trying to achieve in react: click the heart to like/unlike a move, using useState() & useEffect(), but seems the element is applied with new class name, but the color didn't change when i clicked.
export function MovieCard2(props) {
const [heartClassName, setHeartClassName] = useState("heart heart-white")
useEffect(() => {
console.log(ref.current)
if(isLiked === false) {
setHeartClassName("heart heart-white")
} else {
setHeartClassName("heart heart-red")
}
}, [isLiked]);
return (
<>
<div
className={heartClassName}
onClick={function (ev) {
ev.stopPropagation();
setLiked(!isLiked)
>
❤
</div>
<style jsx>{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}</style>
</>
);
}
try this one:
const [heartClassName, setHeartClassName] = useState("heart heart-white");
const [isLiked,setLiked]=useState(false);
useEffect(() => {
if (isLiked === false) {
setHeartClassName("heart heart-white");
} else {
setHeartClassName("heart heart-red");
}
}, [isLiked]);
you HTML
<style jsx>{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}</style>
<>
<div
className={heartClassName}
onClick={function (ev) {
ev.stopPropagation();
setLiked(!isLiked);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="1em"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 1024 1024"
style={{ height: '10px', width:'10px' }}
>
<path
fill="currentColor"
d="M923 283.6a260.04 260.04 0 0 0-56.9-82.8a264.4 264.4 0 0 0-84-55.5A265.34 265.34 0 0 0 679.7 125c-49.3 0-97.4 13.5-139.2 39c-10 6.1-19.5 12.8-28.5 20.1c-9-7.3-18.5-14-28.5-20.1c-41.8-25.5-89.9-39-139.2-39c-35.5 0-69.9 6.8-102.4 20.3c-31.4 13-59.7 31.7-84 55.5a258.44 258.44 0 0 0-56.9 82.8c-13.9 32.3-21 66.6-21 101.9c0 33.3 6.8 68 20.3 103.3c11.3 29.5 27.5 60.1 48.2 91c32.8 48.9 77.9 99.9 133.9 151.6c92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3c56-51.7 101.1-102.7 133.9-151.6c20.7-30.9 37-61.5 48.2-91c13.5-35.3 20.3-70 20.3-103.3c.1-35.3-7-69.6-20.9-101.9z"
/>
</svg>
</div>
</>
From what i see on your browser you are using Next.js which automatically updates and renders out when a variable changes, and you are also not initializing the variable isLiked
This should fix your issue.
import {useState} from "react";
export function MovieCard2(props) {
const [heartClassName, setHeartClassName] = useState("heart heart-white");
const [isLiked, setIsLiked] = useState(false);
function handleLike() {
setIsLiked(!isLiked);
if (isLiked) {
setHeartClassName("heart heart-red");
} else {
setHeartClassName("heart heart-white");
}
}
return (
<>
<div className={heartClassName} onClick={handleLike}>
❤
</div>
<style jsx>
{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}
</style>
</>
);
}

Apply fill color to all circles in an SVG in React

I'm trying to use React to work with SVG's in a procedural manner.
Something I'd like to do is use a parent element to set the color of different circles in my svg:
const MySVG = (): JSX.Element => {
return (
//all circles in this SVG should have a yellow fill
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
style={{ width: "fit-content", height: "100%" }}
viewBox="0 0 971 191"
>
<SVGProcessor>
<circle cx="730.9" cy="109.6" r="52.9" />
<g>
<path d="M587.8,39.6c10.7,8,25.9,5.9,33.9-4.8L641.9,91c-13-3.1-26.1,4.9-29.2,17.9L587.8,39.6z" />
</g>
<circle cx="816.2" cy="53.9" r="33.4" />
<g>
<circle cx="547.5" cy="93.1" r="67" />
</g>
<circle cx="915.5" cy="95.5" r="55" />
</SVGProcessor>
</svg>
);
};
//should apply a yellow fill to all circles in the svg
const SVGProcessor = ({
children,
}: {
children: JSX.Element[];
}): JSX.Element => {
useEffect(() => {
children.forEach((child) => {
if (child.type.displayName === "circle") {
const c = child as React.SVGProps<SVGCircleElement>;
c.fill = "yellow"
}
});
}, []);
return <>{children}</>;
};
But this doesn't work with nested svg elements, as they are not direct children of the wrapper component.
Is there a good way to compose SVG's procedurally?

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

Manipulate svg circle origin to create rotation animation around center

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

Resources