I’m working on a React project where I need to create smooth, endless looping animations, particularly for line drawing using D3.js and GSAP. However, I’m encountering an issue where the line drawing isn’t as smooth as I would like it to be.
Here’s what I’ve done so far:
I’m using D3.js to generate dynamic SVG paths for the line drawing.
GSAP is used to animate the line drawing with stroke-dasharray and stroke-dashoffset for a “drawing” effect.
The animation is supposed to loop endlessly and run smoothly, but it seems like there are performance issues that affect the smoothness.
Specifics:
React rendering: I’m using React to manage the DOM and render SVG elements dynamically. The lines are drawn in an SVG element, and the path elements are being updated with GSAP.
Performance issues: The animation works but is not smooth; it seems jittery or laggy, especially with complex paths or when there are many elements to animate.
Smoothness issue: I’ve tried adjusting the duration and easing of the GSAP animations, but the line still doesn’t draw as smoothly as expected.
import gsap from 'gsap';
import * as d3 from 'd3';
// Graph setup
const xAxisLength = 600;
const yAxisHeight = (xAxisLength / 16) * 9;
const paddingRatio = 0.1;
const svgWidth = xAxisLength * (1 + paddingRatio);
const svgHeight = yAxisHeight * (1 + paddingRatio);
const xAxisStartPos = xAxisLength * paddingRatio;
const yAxisStartPos = yAxisHeight * paddingRatio;
// Line data setup
const numPoints = 100;
const xSpacing = (xAxisLength - xAxisStartPos) / (numPoints - 1);
// Generate a new point with random sharp movement
function generateNewPoint(prevY = yAxisHeight / 2) {
const delta = (Math.random() - 0.5) * 20; // Random movement
return Math.max(0, Math.min(yAxisHeight, prevY + delta)); // Clamping within yAxisHeight
}
const HomeDataViz = () => {
const xAxisLineRef = useRef();
const yAxisLineRef = useRef();
const pathRef = useRef();
const groupRef = useRef();
const [axesAnimated, setAxesAnimated] = useState(false);
const [line1Points, setLine1Points] = useState([
{
x: xAxisStartPos,
y: yAxisHeight / 2 + (Math.random() - 0.5) * 20,
},
]);
const [shift, setShift] = useState(0);
useLayoutEffect(() => {
const xAxisLineLength = xAxisLineRef.current.getTotalLength();
const yAxisLineLength = yAxisLineRef.current.getTotalLength();
gsap.set([xAxisLineRef.current, yAxisLineRef.current], {
strokeDasharray: (i) => (i === 0 ? xAxisLineLength : yAxisLineLength),
strokeDashoffset: (i) => (i === 0 ? xAxisLineLength : yAxisLineLength),
});
const tl = gsap.timeline();
tl.to([xAxisLineRef.current, yAxisLineRef.current], {
strokeDashoffset: 0,
duration: 0.75,
ease: 'power1.inOut',
stagger: 0.1,
});
tl.addLabel("axesDone");
tl.call(() => setAxesAnimated(true), null, "axesDone+=0.1");
}, []);
useEffect(() => {
const interval = setInterval(() => {
setLine1Points((prev) => {
const lastX = prev[prev.length - 1].x;
const lastY = prev[prev.length - 1].y;
const newX = lastX + xSpacing;
const newY = generateNewPoint(lastY);
const newPoints = [...prev, { x: newX, y: newY }];
return newPoints.length > numPoints ? newPoints.slice(1) : newPoints;
});
setShift((prevShift) => {
return line1Points.length >= numPoints ? prevShift - xSpacing : prevShift;
});
}, 100);
return () => clearInterval(interval);
}, [line1Points]);
useEffect(() => {
if (line1Points.length > 0) {
if (!axesAnimated) return;
const line = d3
.line()
.x((d) => d.x)
.y((d) => d.y)
.curve(d3.curveCatmullRom.alpha(0.5));
const pathData = line(line1Points);
if (pathRef.current) {
pathRef.current.setAttribute('d', pathData);
const pathLength = pathRef.current.getTotalLength();
pathRef.current.style.strokeDasharray = pathLength;
pathRef.current.style.strokeDashoffset = pathLength;
gsap.to(pathRef.current, {
strokeDashoffset: 0,
duration: 2,
ease: 'power2.out',
});
}
}
gsap.to(groupRef.current, {
x: shift,
duration: 0.1,
ease: 'linear',
});
}, [line1Points, shift]);
return (
<svg width={svgWidth} height={svgHeight} style={{ border: '1px solid white' }}>
<g ref={groupRef}>
<path ref={pathRef} fill="none" stroke="white" strokeWidth={2} />
</g>
<line
ref={xAxisLineRef}
x1={xAxisStartPos}
y1={yAxisHeight}
x2={xAxisLength}
y2={yAxisHeight}
stroke="white"
strokeWidth="2"
/>
<line
ref={yAxisLineRef}
x1={xAxisStartPos}
y1={yAxisHeight}
x2={xAxisStartPos}
y2={yAxisStartPos}
stroke="white"
strokeWidth="2"
/>
</svg>
);
};
export default HomeDataViz;