Introduction
Over the weekend, I've started work on a personal website built through Gatsby. I got started with their fantastic and beginner friendly tutorials, available here.
As I got going, I wanted to bring my pages to life by animating components and elements on my screen.
While there are fantastic plugins for Gatsby and React, as well as other JavaScript libraries, I had no prior experience with CSS animations, and wanted to learn for myself.
I'm pretty happy with the results, and thought it might be a good idea to share the technique I used here!
The Base Component
For this post, we'll use a basic img element as our component:
animated-image.js
import React from 'react'
import styles from './animated-image.module.scss'
const Image = () => (
<img
className={styles.image}
src="https://source.unsplash.com/random/400x200"
alt="randomised!"
/>
)
export default Image
animated-image.module.scss
.image {
display: block;
margin: 0 auto;
}
This component just retrieves a random 400x200 image whenever it's called. I happen to be using SCSS, but it doesn't make a difference, the technique works in CSS too. The SCSS module for this component just centre aligns the image, to look like so:
If you're unfamiliar with Gatsby CSS/SCSS/SASS modules, essentially it's just a way to localise stylesheets to components or web-pages.
Animating Our Component
Lets say, we want our component to 'wobble' when our user interacts with it.
By 'wobble', I mean something basic like the component swinging to the left and right a little bit, and stopping rather quickly.
In our stylesheet, we can utilise @keyframes to describe our wobbling process!
@keyframes allow us to storyboard the animation process.
We can describe the transformations of the target element at any point of the animation cycle.
The syntax for a basic @keyframes, is so:
- @keyframes animation-name { step { transformation } }
In replace of 'step', we can use 0-100% to mark the animation frame, or use 'from' and 'to' to show a transition.
For our wobble example, lets use something like this:
.image {
display: block;
margin: 0 auto;
}
.image:focus, .image:hover {
animation: wobble 1s 1;
}
@keyframes wobble {
25% {
transform: rotate(15deg);
}
50% {
transform: rotate(-30deg);
}
75% {
transform: rotate(5deg);
}
100% {
transform: rotate(0deg);
}
}
Three things to note here:
- We specified the transformations using the 'transform' attribute, along with one of many CSS transformation functions, rotate. This takes a single positive or negative deg value, to represent a clockwise or anti-clockwise angle.
- We added a focus and hover pseudo-state listener to the image, which means when our user hovers over it, we'll see the animation!
- We run our animation by specifying the name of the keyframe animation script, along with how long this animation should take to complete (in our case, one second), and finally how many times it should do so (this can also be infinite, to go on forever. In our case, only run once).
Let's test it out..
Nice!
Animation on Click with React
We've seen the animation working on the pseudo-states of elements, but how can we control animations from HTML/JS events?
We may want animations on click, which isn't an option in CSS.
We can introduce a useState hook to our component to overcome this.
useState is a React Hook that gives us simple getter/setter methods to control a value state in our component. The changes are rendered on the web-page as the values are updated!
animated-image.js
import React from 'react'
import styles from './animated-image.module.scss'
const Image = () => {
const [wobble, setWobble] = React.useState(0)
return (
<img
className={styles.image}
src="https://source.unsplash.com/random/400x200"
alt="randomised!"
onClick={() => setWobble(1)}
onAnimationEnd={() => setWobble(0)}
wobble={wobble}
/>
)
}
export default Image
Here we can see our wobble (getter) and setWobble (setter) methods use a value of 1 to indicate wobbling is in process, and 0 to indicate wobbling has stopped.
While wobble is not a valid HTML element attribute, React will render unknown properties as Element Data Attributes. Data Attributes allow us to store data in elements.
Typically, data attributes shouldn't be used for content specific data, as that is what the Component State is used for. But a scenario like this using it as an animation marker is absolutely fine.
More information on HTML data attributes can be found here.
Now to update our CSS!
We want the animation to run when the wobble attribute is set to 1 only. To do this, we say:
.image {
display: block;
margin: 0 auto;
}
.image[wobble='1'] {
animation: wobble 1s 1;
}
@keyframes wobble {
25% {
transform: rotate(15deg);
}
50% {
transform: rotate(-30deg);
}
75% {
transform: rotate(5deg);
}
100% {
transform: rotate(0deg);
}
}
The square brackets in CSS matches attributes by the name supplied. In this case, the CSS condition is any 'image' class with a wobble attribute value of '1'.
That's it!
The End Result
Perfect! We can see the animation occurs when we click the image, and we can also observe the wobble attribute being updated, controlling when the animation occurs.
Conclusion
I hope this may of been some interest to you, and to demonstrate that CSS animations can be easy and effective at bringing your web applications to life!
This was my first article, go easy on me! If you liked it, or have any other feedback at all, please free to let me know. Thanks!😊
Top comments (8)
Great article, thanks a bunch! I just wondered if it was possible to store the 'wobble' state in a parent component. The reason I am asking is that I'd like to trigger the animation from a sibling component, so need to access the 'wobble' state in the parent. I have tried, but failed, to get this right.
Thanks for your comment! :)
Yeah I don’t see why not! You would just need to define the state and clickHandler in the parent component, and pass the wobble state to the component you want to animate, and the clickHandler to the other child / animated components sibling
Thanks Sam. I've got this setup working on a basic app I built to test this, but on a more complex (existing) app, it is not working! I'm wondering if it is because I am using from 'react-router-dom' and if that is in some way interfering the with way React passes the state change between the siblings. Anyway, probably a question for stackoverflow at this stage. Thanks again for a great article.
Beautiful code !
Very useful. Thanks!
Great article! You can also move the onAnimationEnd and wobble attributes to another component to animate it onClick.
Great article, suppose we want to animate on rendering, can we use Ref to point to the required node and instead of useState, we can add/remove the "wobble" class in useEffect wrt dependency changes?
THANKSSSSSSSSSSS!!!!! :D