Animate SVG frames
Answered
Evanion posted this in #help-forum
EvanionOP
Not strictly a Next.js issue.
I have a series of 16 SVGs that make up one frame each of an animation (A bird flapping it's wings like it's flying).
I tried using the
When I render each path in their separate SVGs and spread them out on the page, there is no frame with this issue.
So, how can I animate between these frames in a better way than using css
I'm considering using lottie, but I would like to avoid including a large library unless I absolutely have to. Not to mention spending the money for Adobe AE.
I have a series of 16 SVGs that make up one frame each of an animation (A bird flapping it's wings like it's flying).
I tried using the
<animate /> [native SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate) element to update the d attribute with each frame path, but I get a weird frame that only renders a part of the path, and moves it .. it kind of looks like it's interpolating between two frame, and morphing the path, but it's only rendering the tip of a birds wing.When I render each path in their separate SVGs and spread them out on the page, there is no frame with this issue.
So, how can I animate between these frames in a better way than using css
@keyframe?I'm considering using lottie, but I would like to avoid including a large library unless I absolutely have to. Not to mention spending the money for Adobe AE.
<svg height="541" viewBox="0 0 441 541" width="441" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" transform="translate(13 171)" d="m220.05 7.35c-2.4-.83333333-...">
<animate dur="16s" repeatCount="indefinite" attributeName="d" values="
// frame 1 path
// frame 2 path
// etc" />
</path>
</svg>Answered by Evanion
I finally solved it using
requestAnimationFrame api.import { FC, useEffect, useRef, useState } from 'react';
import styles from './animatedSvg.module.css';
type Frame = {
d: string;
transform: string
}
type AnimatedSvgProps = {
duration?: number
width?: number;
height?: number;
frozenFrame?: number;
frames?:Frame[]
}
export const AnimatedSvg: FC<AnimatedSvgProps> = ({ duration = 1600, width = 100, height, frozenFrame = 13, frames = defaultFrames }) => {
const [currentFrame, setCurrentFrame] = useState(frozenFrame);
const frameRef = useRef(currentFrame);
const requestRef = useRef<number>(0);
const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
setCurrentFrame(frozenFrame);
return;
}
const totalFrames = frames.length;
const frameDuration = duration / totalFrames;
const animate = () => {
frameRef.current = (frameRef.current + 1) % totalFrames;
setCurrentFrame(frameRef.current);
timeoutRef.current = setTimeout(() => {
requestRef.current = requestAnimationFrame(animate);
}, frameDuration);
};
requestRef.current = requestAnimationFrame(animate);
return () => {
cancelAnimationFrame(requestRef.current);
clearTimeout(timeoutRef.current);
};
}, [duration, frozenFrame]);
const { d, transform } = frames[currentFrame];
return (
<svg width={width} height={height} viewBox="0 0 421 541" xmlns="http://www.w3.org/2000/svg" className={styles['svg']}>
<path d={d} transform={transform} fill="currentColor" />
</svg>
);
};1 Reply
EvanionOP
I finally solved it using
requestAnimationFrame api.import { FC, useEffect, useRef, useState } from 'react';
import styles from './animatedSvg.module.css';
type Frame = {
d: string;
transform: string
}
type AnimatedSvgProps = {
duration?: number
width?: number;
height?: number;
frozenFrame?: number;
frames?:Frame[]
}
export const AnimatedSvg: FC<AnimatedSvgProps> = ({ duration = 1600, width = 100, height, frozenFrame = 13, frames = defaultFrames }) => {
const [currentFrame, setCurrentFrame] = useState(frozenFrame);
const frameRef = useRef(currentFrame);
const requestRef = useRef<number>(0);
const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
setCurrentFrame(frozenFrame);
return;
}
const totalFrames = frames.length;
const frameDuration = duration / totalFrames;
const animate = () => {
frameRef.current = (frameRef.current + 1) % totalFrames;
setCurrentFrame(frameRef.current);
timeoutRef.current = setTimeout(() => {
requestRef.current = requestAnimationFrame(animate);
}, frameDuration);
};
requestRef.current = requestAnimationFrame(animate);
return () => {
cancelAnimationFrame(requestRef.current);
clearTimeout(timeoutRef.current);
};
}, [duration, frozenFrame]);
const { d, transform } = frames[currentFrame];
return (
<svg width={width} height={height} viewBox="0 0 421 541" xmlns="http://www.w3.org/2000/svg" className={styles['svg']}>
<path d={d} transform={transform} fill="currentColor" />
</svg>
);
};Answer