How can I animate SVG in React so that the stroke animation starts from the middle?
I have an SVG file with a rectangle that needs to be animated so that the green line starts moving not from the beginning of the stroke, but from its middle. The current code uses requestAnimationFrame to animate stroke-dashoffset but doesn’t achieve the desired effect.
useEffect(() => {
if (!svgRef.current) return;
const svgElement = svgRef.current;
const animatedElement = svgElement.querySelector('#svg_5');
// Initialize stroke dasharray
const length = animatedElement.getTotalLength ? animatedElement.getTotalLength() : 1000;
animatedElement.style.strokeDasharray = length;
animatedElement.style.strokeDashoffset = length;
animatedElement.style.strokeWidth = 2;
animatedElement.style.stroke = "#00ff7f";
if (!isAnimated) return;
let startTime = null;
const duration = 6000;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
setIsProgress(progress);
const totalLength = animatedElement.getTotalLength ? animatedElement.getTotalLength() : 1000;
const startOffset = (startStoke / 100) * totalLength;
const endOffset = 0.6 * totalLength;
const currentOffset = endOffset + (startOffset-endOffset) * progress;
animatedElement.style.strokeDashoffset = currentOffset;
animatedElement.style.stroke = "#00ff7f";
if (progress < 1 && isAnimated) {
animationRef.current = requestAnimationFrame(animate);
} else if (progress >= 1) {
setIsAnimated(false);
}
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isAnimated, startStoke, endStoke]);
How do I properly configure the animation parameters so that the line starts moving from the middle of the SVG stroke element?
To animate an SVG stroke in React so that the animation starts from the middle, you need to properly configure the stroke-dasharray and stroke-dashoffset parameters. The current code has several issues in calculations and parameter setup.
Table of Contents
- Problem with Current Approach
- Correct Method for Middle Animation
- Full React Component Example
- Alternative CSS Animation Methods
- Parameter Calculations for Different SVG Types
- Performance Optimization
Problem with Current Approach
The current code has several key issues:
- Incorrect
startStokeandendStokeparameters - variables are used but not defined - Incorrect offset calculation - the offset animation logic doesn’t account for the middle of the path
- Lack of proper
stroke-dasharrayconfiguration - for middle animation, a different configuration is needed
As experts from CSS-Tricks note, the key idea is to “configure the offset to occupy the entire path” and use the correct dash and gap ratios.
Correct Method for Middle Animation
For middle animation, you need to use the following approach:
useEffect(() => {
if (!svgRef.current) return;
const svgElement = svgRef.current;
const animatedElement = svgElement.querySelector('#svg_5');
// Get the full path length
const length = animatedElement.getTotalLength();
// Configure parameters for middle animation
animatedElement.style.strokeDasharray = length / 2; // Half length for dash
animatedElement.style.strokeDashoffset = length / 4; // Start from quarter path (middle)
animatedElement.style.strokeWidth = 2;
animatedElement.style.stroke = "#00ff7f";
if (!isAnimated) return;
let startTime = null;
const duration = 6000;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
setIsProgress(progress);
const totalLength = animatedElement.getTotalLength();
// Animation from middle to end
const startOffset = length / 4; // Start from middle
const endOffset = length; // Move to end
const currentOffset = startOffset + (endOffset - startOffset) * (1 - progress);
animatedElement.style.strokeDashoffset = currentOffset;
if (progress < 1 && isAnimated) {
animationRef.current = requestAnimationFrame(animate);
} else if (progress >= 1) {
setIsAnimated(false);
}
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isAnimated]);
Full React Component Example
Here’s a complete component with stroke animation starting from the middle:
import React, { useRef, useEffect, useState } from 'react';
const SVGStrokeAnimation = () => {
const svgRef = useRef(null);
const animationRef = useRef(null);
const [isAnimated, setIsAnimated] = useState(false);
const [isProgress, setIsProgress] = useState(0);
const [isAnimatingFromMiddle, setIsAnimatingFromMiddle] = useState(false);
const startAnimation = () => {
setIsAnimated(true);
setIsAnimatingFromMiddle(true);
};
const resetAnimation = () => {
setIsAnimated(false);
setIsAnimatingFromMiddle(false);
};
useEffect(() => {
if (!svgRef.current) return;
const svgElement = svgRef.current;
const animatedElement = svgElement.querySelector('#svg_5');
if (!animatedElement) return;
// Get the full path length
const length = animatedElement.getTotalLength();
// Configure parameters for animation
animatedElement.style.strokeDasharray = length;
animatedElement.style.strokeDashoffset = length;
animatedElement.style.strokeWidth = 2;
animatedElement.style.stroke = "#00ff7f";
animatedElement.style.transition = 'none';
if (!isAnimated) return;
let startTime = null;
const duration = 6000;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
setIsProgress(progress);
const totalLength = animatedElement.getTotalLength();
if (isAnimatingFromMiddle) {
// Middle animation
const middleOffset = length / 2; // Middle of path
const endOffset = 0; // End of path
// Split animation: first draw half, then second half
let currentOffset;
if (progress < 0.5) {
// First half: draw from middle to start
const firstHalfProgress = progress * 2;
currentOffset = middleOffset - (middleOffset - 0) * firstHalfProgress;
} else {
// Second half: draw from middle to end
const secondHalfProgress = (progress - 0.5) * 2;
currentOffset = middleOffset + (length - middleOffset) * secondHalfProgress;
}
animatedElement.style.strokeDashoffset = currentOffset;
} else {
// Regular animation from start
const startOffset = length;
const endOffset = 0;
const currentOffset = startOffset + (endOffset - startOffset) * progress;
animatedElement.style.strokeDashoffset = currentOffset;
}
if (progress < 1 && isAnimated) {
animationRef.current = requestAnimationFrame(animate);
} else if (progress >= 1) {
setIsAnimated(false);
}
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isAnimated, isAnimatingFromMiddle]);
return (
<div>
<div style={{ marginBottom: '20px' }}>
<button onClick={startAnimation} disabled={isAnimated}>
{isAnimatingFromMiddle ? 'Middle Animation' : 'Start Animation'}
</button>
<button onClick={resetAnimation} style={{ marginLeft: '10px' }}>
Reset
</button>
<div style={{ marginTop: '10px' }}>
Progress: {(isProgress * 100).toFixed(1)}%
</div>
</div>
<svg
ref={svgRef}
width="400"
height="300"
viewBox="0 0 400 300"
style={{ border: '1px solid #ccc' }}
>
<rect
id="svg_5"
x="50"
y="50"
width="300"
height="200"
fill="none"
style={{ strokeDasharray: '0', strokeDashoffset: '0' }}
/>
</svg>
</div>
);
};
export default SVGStrokeAnimation;
Alternative CSS Animation Methods
For simpler cases, you can use pure CSS animation, as experts from DEV Community suggest:
.svg-stroke-middle {
stroke-dasharray: 100%;
stroke-dashoffset: 50%;
animation: drawFromMiddle 2s ease-in-out forwards;
}
@keyframes drawFromMiddle {
0% {
stroke-dashoffset: 50%;
}
100% {
stroke-dashoffset: 0%;
}
}
In a React component, this can be used like this:
const SVGStrokeAnimationCSS = () => {
const [isAnimated, setIsAnimated] = useState(false);
const startAnimation = () => {
setIsAnimated(true);
};
return (
<div>
<button onClick={startAnimation} disabled={isAnimated}>
Start CSS Animation
</button>
<svg width="400" height="300" viewBox="0 0 400 300">
<rect
x="50"
y="50"
width="300"
height="200"
fill="none"
className={`svg-stroke-middle ${isAnimated ? 'animated' : ''}`}
style={{
strokeWidth: 2,
stroke: "#00ff7f",
strokeDasharray: '100%',
strokeDashoffset: '50%',
transition: stroke-dashoffset 2s ease-in-out
}}
/>
</svg>
</div>
);
};
Parameter Calculations for Different SVG Types
Calculations may differ for different SVG element types:
-
For rectangles:
javascriptconst length = 2 * (width + height); // Perimeter of rectangle const middleOffset = length / 2; // Middle of perimeter -
For circles:
javascriptconst radius = circleElement.getAttribute('r'); const length = 2 * Math.PI * radius; // Circumference const middleOffset = length / 2; // Middle of circumference -
For complex paths:
javascriptconst length = pathElement.getTotalLength(); const middleOffset = length / 2;
As experts from Team Treehouse explain, stroke-dasharray and stroke-dashoffset work together to create stroke animation effects.
Performance Optimization
For better performance:
-
Use
will-change:css.animated-element { will-change: stroke-dashoffset; } -
Optimize animation:
javascript// Use requestAnimationFrame with proper frequency const animate = (timestamp) => { // ... animation logic animationRef.current = requestAnimationFrame(animate); }; -
Cache values:
javascriptconst cachedLength = animatedElement.getTotalLength(); // Use cachedLength instead of repeated calls -
Use CSS transitions for simple animations:
javascriptanimatedElement.style.transition = 'stroke-dashoffset 2s ease-in-out'; animatedElement.style.strokeDashoffset = '0';
Thus, to make SVG stroke animation start from the middle, you need to correctly calculate stroke-dashoffset, starting from half the path length, and configure the animation to move to the start and end simultaneously or in stages.
Sources
- Animate SVG Lines and Closed Paths with Dash Attributes - Let’s WP
- How SVG Line Animation Works | CSS-Tricks
- Animating stroke-dasharray and stroke-dashoffset (How To) | Team Treehouse
- Simple SVG drawing effect with stroke-dasharray & stroke-dashoffset - DEV Community
- stroke-dashoffset | CSS-Tricks Almanac
- Animating a complex SVG icon with dash-array and dash-offset in CSS | Justin Marsan