Top 3 SVG Animation Techniques. A brief guide to these fantastic… | by Vladimir Topolev | Nov, 2022

A brief guide to these cool features

image by author

We know that SVG has Element that allows difficult shapes to be depicted. And one of the interesting techniques is the use of a library that can create interpolations for smooth transitions from one path to another.

First, let’s create some arbitrary objects in Figma. It might look like this (note that if there are multiple shapes in one object – you should merge them in a way that Figma turns it into a path):

After that, we need to convert each shape into an SVG path – right-click under the selected shape and choose the option “Copy / Paste -> Copy as SVG”:

The following SVG is copied to the clipboard:



we are interested in the string, which is in d Speciality As we do these steps for all the shapes, we get the following set of paths:

const circlePath = 'M50 25C50 38.8071 38.8071 50 25 50C11.1929 50 0 38.8071 0 25C0 11.1929 11.1929 0 25 0C38.8071 0 50 11.1929 50 25Z';;
const moonPath = 'M18.5 25C18.5 38.8071 25 50 25 50C11.1929 50 0 38.8071 0 25C0 11.1929 11.1929 0 25 0C25 0 18.5 11.1929 18.5 25Z';;
const bowPath = 'M32.5634 4.97243C34.8622 4.36552 37.0171 3.59038 38.9747 2.88622C45.6264 0.493523 50 -1.07968 50 7.55108C50 16.1818 45.6264 14.6086 38.9747 12.2159C37.0075 11.5083 34.841 10.729 32.5294 10.1207C31.4665 12.9705 28.7202 15 25.5 15C22.1937 15 19.387 12.8605 18.389 9.89044C15.7317 10.523 13.251 11.4153 11.0253 12.2159C4.37355 14.6086 0 16.1818 0 7.55108C0 -1.07968 4.37355 0.493523 11.0253 2.88622C13.2423 3.68369 15.7123 4.57219 18.3579 5.20427C19.3276 2.18508 22.1586 0 25.5 0C28.7552 0 31.5261 2.07386 32.5634 4.97243Z';;
const crossPath = 'M20.3352 39.5258C17.9425 46.1775 16.3692 50.5511 25 50.5511C33.6308 50.5511 32.0576 46.1775 29.6649 39.5258C28.3742 35.9377 26.845 31.6866 26.5505 27.1015C31.1355 27.3961 35.3866 28.9252 38.9747 30.2159C45.6265 32.6086 50 34.1818 50 25.5511C50 16.9203 45.6265 18.4935 38.9747 20.8862C35.3866 22.1769 31.1355 23.7061 26.5505 24.0006C26.845 19.4156 28.3742 15.1645 29.6649 11.5764C32.0576 4.92464 33.6308 0.551086 25 0.551086C16.3692 0.551086 17.9425 4.92464 20.3352 11.5764C21.6258 15.1645 23.155 19.4156 23.4496 24.0006C18.8645 23.7061 14.6134 22.1769 11.0253 20.8862C4.37354 18.4935 0 16.9203 0 25.5511C0 34.1818 4.37354 32.6086 11.0253 30.2159C14.6134 28.9252 18.8645 27.3961 23.4496 27.1015C23.155 31.6866 21.6258 35.9377 20.3352 39.5258Z';;
const starPath = 'M26 0L32.0619 18.6565H51.6785L35.8083 30.1869L41.8702 48.8435L26 37.3131L10.1298 48.8435L16.1917 30.1869L0.321474 18.6565H19.9381L26 0Z';;

Library flutter Helps us create a smooth transition between two shapes.

yarn add flubber

The API is quite simple. We need to create an interpolator going through the two paths, and this returns a function. Initializing this function with values ​​from 0 to 1 gives a new intermediate path for a smooth transition. Here’s the code:

const interpolator = flubber.interpolate(crossPath, starPath);

interpolator(0); // returns an SVG cross path string
interpolator(0.5); // returns something halfway between the cross and the star
interpolator(1); // returns an SVG star path string

it looks like this:

We need to integrate it with React. To simplify our task, I’m going to use two dependencies:
, usehooks-ts : A library with a bunch of useful hooks. we are going to use useInterval hook (implementation Here,
, framer-motion :library for animation and we are just going to use animate – a function that animates any value (more details Here,

let’s make ours MorphPath Component. As we can expect there should be two way as props: fromPath And toPath, Additionally, we can expose parameters that allow us to more specifically set the animation behavior:

import  interpolate  from 'flubber';
import animate, Spring, Tween from 'framer-motion';
import useEffect, useRef, useState from 'react';

type MorphPathProps =
fromPath: string;
toPath: string;
animation?: (Tween ;

const MorphPath = ( fromPath, toPath, animation : MorphPathProps) =>
// create interpolator for smooth transition paths from fromPath to toPath
const interpolatorRef = useRef();
useEffect(() =>
interpolatorRef.current = interpolate(fromPath, toPath,
maxSegmentLength: 0.1,
);
, [fromPath, toPath]);

// animate value from 0 to 1
const [progress, setProgress] = useState(0);
useEffect(() =>
animate(0, 1,
...animation,
onUpdate: (val) =>
setProgress(val);
,
);
, [fromPath, toPath, animation]);

// for each new animated progress value invoking
// interpolator and set a new inperpolated path into React state
const [path, setPath] = useState

We need to use this component, and every two seconds we create a new sequence fromPath And toPath,

const circlePath = '...';
const moonPath = '...';
const bowPath = '...';
const crossPath = '...';
const starPath = '';

const paths = [circlePath, moonPath, bowPath, crossPath, starPath];

const MorphMoonToSun = () =>
const [index, setIndex] = useState(0);

useInterval(() =>
setIndex((prev) => prev + 1);
, 2000);

return (



fromPath=paths[index % paths.length]
toPath=paths[(index + 1) % paths.length]
animation= duration: 1
/>


);
;

Brilliant. It is not that difficult.

Another commonly used animation. This is an animation of the stroke properties, and we can see how the path is being drawn on the fly like this:

The best part here is that it is very easy to implement. Animation based on only two css properties — stroke-dasharray And stroke-dashofset, Those properties are responsible for defining the pattern of dashes and intervals used to depict the outline of the figure:

So, if we know the length of the path (we can extract it via JS API), we can create a pattern where the length of spaces and dashes is equal to the length of the path. By manipulating the value of offset, we can create an effect as we begin to draw the stroke, but literally, this means we move a dash along the stroke path:

You can experiment with this sandbox to understand more deeply what is happening (change the scroller and see how the path is changing):

Let’s summarize what we are going to implement:

  • Define path length (JS provides a special API for this path.getTotalLength()and set this value to stroke-dasharray, This animation will be frozen in time
  • animate stroke-dashofset property from -totalLengthPath To 0

This component may look like this:

import  animate, Spring, Tween  from "framer-motion";
import useEffect, useRef, useState from "react";

type AnimatedPathProps =
path: string;
animation?: (Tween ;

const AnimatedPath = ( path, animation : AnimatedPathProps) =>
const pathRef = useRef(null);
const [totalPathLength, setTotalPathLength] = useState(0);
const [animatedOffset, setAnimatedOffset] = useState(-totalPathLength);

// via JS API calculate length of path
// and defining stroke-dasharray propert with pattern
// where length of dash and gap equal total length of path
useEffect(() =>
if (pathRef.current)
const pathLength = pathRef.current.getTotalLength();
setTotalPathLength(pathLength);
setAnimatedOffset(-pathLength);

, []);

// animate offset of dashes and change
// value in a range [-totalPath:ength, 0]
useEffect(() =>
animate(-totalPathLength, 0,
...animation,
onUpdate: (val) =>
setAnimatedOffset(() => val);

);
, [totalPathLength, animation]);

return (
d=path
ref=pathRef
stroke="red"
fill="none"
strokeWidth=10
strokeDasharray=totalPathLength
strokeDashoffset=animatedOffset
/>
);
;

export default AnimatedPath;

And here’s how we can use this component:

import AnimatedPath from "./components/AnimatedPath";
import useInterval from "usehooks-ts";
import "./styles.css";
import useState from "react";

const defaultPath = [
path: "...path for letter 1" ,
path: "...path for letter 2" ,
// ... other path letters
];

const animationDuration = 0.8;

export default function App()
return (


viewBox="0 0 2600 1350"
width="500"
style=styles.svg
>
defaultPath.map((path, i) => (
path=path.path
key=i
animation=
duration: animationDuration,
delay: i * animationDuration * 0.5

/>
))


);

For any other details, you can take a look at the code sandbox attached above.

should pay attention to Element that defines an alpha mask for blending the current object to the background. When you apply a mask to a particular element – everything under the white pixel will be visible, and under the black – invisible.

Let’s say we have two amazing photos of dishes offered by a catering service in Poland, and let’s put them in SVG one below the other:

In Figma, let’s also draw an arbitrary shape filled with white (remember, everything below the white pixel will appear in a mask). It may look like this:

The last step is to use the obtained path as a mask in the SVG:

The final SVG looks like this:







here we found a way out element with id="transition" and assign a new property mask For the second image and define which mask is going to be used: mask='url(#transition)',

Finally, we get the following:

But it’s pretty boring. Let’s add some animations here depending on the component MorphPath in which we developed Chapter 1, But before that, let’s show some intermediate paths in Figma (let’s say 0%, 25%, 50%, 75% and 100%). This helps interpolate path transitions in a more manageable way:

As always, we need to remove the strings d Define attributes and constants for each path:

const path00 =
"M-192 -1H-43C-43 -1 -129 87 -135 164C-141 241 -67.2419 331.14 -43 405C3 545.151 34.5 685 -14 815C-62.5 945 -91.5 928 -25.5 1128.5C40.5 1329 -94.0519 1351 -25.5 1504C9 1581 -41.1948 1633.94 -43 1623H-192V-1Z";
const path25 =
"M-206 0H-57C-57 0 102.5 27.5 179 151.5C255.5 275.5 179 275.5 179 467C179 658.5 395.5 699.5 395.5 824C395.5 948.5 90 980 90 1136C90 1292 373.5 1155 303.5 1496C276.832 1625.91 -55.1948 1634.94 -57 1624H-206V0Z";
const path50 =
"M-206 4.29153e-05H-57C-57 4.29153e-05 458.5 -84.5 406.5 136.5C354.5 357.5 568.5 257 611 438.5C653.5 620 495 694.5 495 819C495 943.5 644 986.5 644 1142.5C644 1298.5 383.74 1148.09 534.5 1467C557.9 1516.5 687.5 1509 637.5 1575C587.5 1641 -55.7292 1631.7 -57 1624H-206V4.29153e-05Z";

const path75 =
"M-206 8.7738e-05H-57C-57 8.7738e-05 831.5 -107.5 944.5 146C1057.5 399.5 798 289 840.5 470.5C883 652 1048.5 696.5 1048.5 821C1048.5 945.5 743.5 960 805.5 1119.5C867.5 1279 874.063 1218.5 944.5 1367.5C967.9 1417 1003.5 1573 910 1622.5C816.5 1672 -55.7292 1631.7 -57 1624H-206V8.7738e-05Z";
const path100 =
"M-206 7.62939e-05H-57C-57 7.62939e-05 975.5 -291.5 1088.5 -38C1201.5 215.5 1118 274 1160.5 455.5C1203 637 1048.5 696.5 1048.5 821C1048.5 945.5 1081 1022 1143 1181.5C1205 1341 1115.06 1391 1185.5 1540C1208.9 1589.5 1094 1622 1000.5 1671.5C907 1721 -55.7292 1631.7 -57 1624H-206V7.62939e-05Z";

const paths = [path00, path25, path50, path75, path100];

Since we have already implemented MorphPath component, the final code will look like this:

// some code skipped for brevity, the full version
// see in codesanbox attech below
import MorphPath from "./components/MorphPath";

const path00 = '...';
const path25 = '...';
const path50 = '...';
const path75 = '...';
const path100 = '...';

const paths = [path00, path25, path50, path75, path100];

export default function App()
const [pathIndex, setPathIndex] = useState(0);

useInterval(() =>
setPathIndex((prev) => prev + 1);
, 1000);

return (




fromPath=paths[pathIndex % paths.length]
toPath=paths[(pathIndex + 1) % paths.length]
fill="white"
animation= duration: 1
/>





);

The end result in action looks like this:

I hope you enjoy reading this article. I’m glad to see any comments about items that should have been covered, and I appreciate any help in improving the article.

Thanks.

Leave a Reply