In this post I'm going to show you how to create a juicy like animation using React and SVG.
It's heavily inspired by Twitter's like animation but slightly altered and instead of a heart we'll be using a star.
Let's break this animation down – component by component and step by step:
Basic Structure of the Star Button
There are 3 main elements to the star animation. They are layered on top of each other.
Component 1: <StarButton>
The button that makes the whole thing interactive and clickable. It contains both the <StarAnimation>
and the <StarIcon>
.
StarButton.js
function StarButton() {const [starred, setStarred] = useState(false)const toggleStarred = () => {setStarred((starred) => !starred)}return (<button className={`StarButton ${starred ? 'StarButton--starred' : ''}`} type="button" onClick={toggleStarred}><StarAnimation starred={starred} /><StarIcon /></button>)}
Component 2: <StarIcon>
This is just the star icon. This component will remain unchanged and we'll later add some styling via CSS.
StarIcon.js
function StarIcon() {return (<svg className="StarIcon" width={32} height={32} fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 2l3.95 8.563 9.365 1.11-6.924 6.404 1.838 9.25L16 22.72l-8.229 4.606 1.838-9.25-6.924-6.402 9.365-1.11L16 2z" /></svg>)}
Component 3: <StarAnimation>
This is a blank SVG for now that we'll begin filling in in the next step.
StarAnimation.js
function StarAnimation({ starred }) {return (<svg className="StarAnimation" width={96} height={96} fill="none" xmlns="http://www.w3.org/2000/svg">{/* ... */}</svg>)}
Basic Styles
Before we begin filling in our <StarAnimation>
, let's first add some basic styles to position our components.
StarButton.css
.StarButton {line-height: 0;padding: 0;background-color: transparent;border: 0;position: relative;cursor: pointer;}.StarButton .StarAnimation {position: absolute;top: -32px;left: -32px;pointer-events: none;}.StarButton .StarIcon {position: relative;}
With that little bit of boilerplate out of the way, let's get on to the good stuff!
SVG Animation 1: The Pulse Wave
When the button is clicked, you can see a pulse wave appearing from the center.
Let's add this effect to our <StarAnimation>
component.
We're animating an SVG circle with a stroke (border). Using a CSS transition, we will animate
the circle from transform: scale(0); opacity: 1;
to transform: scale(1); opacity: 0;
.
JS
function PulseWave({ starred = false }) {const initialStyle = {transform: 'scale(0)',opacity: 1,}const style = starred? {transform: 'scale(1)',opacity: 0,transformOrigin: 'center center',transition: 'transform 400ms, opacity 400ms',transitionDelay: '0ms, 200ms',}: initialStylereturn (<circle// Position the circle in the center of the 96x96 SVGcx={48}cy={48}// This radius makes the circle go all the way to the edge of the SVG.// We subtract 1px from 48px to account for the strokeWidth of 1px.r={47}strokeWidth={1}stroke="#f7d527"style={style}/>)}
Now let's add this component to our <StarAnimation>
component:
StarAnimation.js
function StarAnimation({ starred }) {return (<svg className="StarAnimation" width={96} height={96} fill="none" xmlns="http://www.w3.org/2000/svg"><PulseWave starred={starred} /></svg>)}
SVG Animation 2: The Particles
Perhaps more prominent than the pulse wave, the particles appear when clicking the star button.
To create these particles, we can follow a few simple steps:
1. Create a number of filled SVG circles in the center of the SVG
JS
function Particles({ starred }) {return Array.from({ length: numParticles }).map((_, i) => {// Position the cirlce at the center of the SVGconst cx = centerXconst cy = centerY// Give the circle a random fillconst yellows = ['#F7D527', '#E0C016', '#C1A40A']const fill = yellows[Math.floor(Math.random() * yellows.length)]// Give the circle a random sizeconst minParticleRadius = 1const maxParticleRadius = 3const radius = minParticleRadius + Math.random() * (maxParticleRadius - minParticleRadius)return <circle key={i} cx={cx} cy={cy} r={radius} fill={fill} />})}
2. Calculate the target position of each circle
JS
function Particles({ starred }) {Array.from({ length: numParticles }).map((_, i) => {// Calculate cx, cy, fill, radius// ...// Determine a random length that the circle will travelconst minParticleDistance = 32const maxParticleDistance = 42const travelDistance =minParticleDistance + Math.random() * (maxParticleDistance - minParticleDistance) - radius// The angle is dependent on the particle's index. We want the// particles to evenly travel outwards in a circle shape.// The angle for the particle is 360deg * (i / numParticles)const travelAngle = 2 * Math.PI * (i / numParticles)// Once we know the travel distance and angle, we can determine the// target position of the particleconst targetCx = centerX + Math.cos(travelAngle) * travelDistanceconst targetCy = centerY + Math.sin(travelAngle) * travelDistancereturn <circle key={i} cx={cx} cy={cy} r={radius} fill={fill} />})}
3. Apply the animation using CSS transforms
JS
function Particles({ starred }) {Array.from({ length: numParticles }).map((_, i) => {// Calculate cx, cy, fill, radius// ...// Calculate targetCx, targetCy// ...// The amount we need to move the particle by is simply// targetPosition - originalPositionconst translateX = targetCx - cxconst translateY = targetCy - cy// Get random durations for movement and opacity// Travel duration will be between 200ms and 500msconst travelDuration = 200 + Math.random() * 300// Opacity duration will be slightly longer than travel durationconst opacityDuration = travelDuration * 1.2// Each particle starts out at the center with opacity 1. It then// animates to its target destination with opacity 0const initialStyle = {transform: `translate(0px, 0px)`,opacity: 1,}const style = starred? {transform: `translate(${translateX}px, ${translateY}px)`,opacity: 0,transition: `transform ${travelDuration}ms, opacity ${opacityDuration}ms`,transitionDelay: `0ms, ${travelDuration * 0.8}ms`,}: initialStylereturn <circle key={i} cx={cx} cy={cy} r={radius} fill={fill} style={style} />})}
As a final step, render the <Particles>
component inside the <StarAnimation>
component.
Animating the Star
Lastly, let's look at how we can animate the star itself to look nice and juicy.
The animation here can be accomplished entirely with CSS. There's a few things going on.
- Give the star a gray
stroke
by default - When the user starts clicking/touching, make the star slightly smaller
- When
starred
, add a yellowfill
to the star and remove thestroke
- When
starred
, make the star larger - When
starred
, rotate the star by 2 spikes
And here are these steps implemented with CSS:
StarButton.css
.StarButton .StarIcon {/* 1. Give the star a gray `stroke` by default */stroke: #ababab;/* We'll be animating the transform of the StarIcon */transition: transform 100ms;}.StarButton:active .StarIcon {/* 2. When the user starts clicking/touching, make the starslightly smaller */transform: scale(0.7);}.StarButton--starred .StarIcon {/* 3. When `starred`, add a yellow `fill` to the star andremove the `stroke` */fill: #f7d527;stroke: none;/* 4. When `starred`, make the star larger *//* 5. When `starred`, rotate the star by 2 spikes */animation: 650ms star-grow-rotate ease-out;}@keyframes star-grow-rotate {0% {transform: scale(1) rotate(0deg);}50% {transform: scale(1.5) rotate(120deg);}100% {/* The star has 5 spikes: 360deg / 5 = 72deg.-> 72deg is a rotation by 1 spike.-> 144deg is a rotation by 2 spikes.*/transform: scale(1) rotate(144deg);}}
That was it! If you followed along up until here, you should now have a working juicy star animation.
You can find an entire working example on CodeSandbox.