DEV Community

Sam Watts
Sam Watts

Posted on

How to Easily Animate Your React Components On Click with CSS Keyframes!πŸŽ†

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

Enter fullscreen mode Exit fullscreen mode

animated-image.module.scss

.image {
  display: block;
  margin: 0 auto;
}

Enter fullscreen mode Exit fullscreen mode

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:
how the component looks on screen
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);
  }
}

Enter fullscreen mode Exit fullscreen mode

Three things to note here:

  1. 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.
  2. 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!
  3. 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..

Wobbling on hover

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

Enter fullscreen mode Exit fullscreen mode

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);
  }
}

Enter fullscreen mode Exit fullscreen mode

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

Wobble on click!

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!😊

Me wobbling!

Top comments (7)

Collapse
 
andreweinhorn profile image
Andrew

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.

Collapse
 
samwatts98 profile image
Sam Watts

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

Collapse
 
andreweinhorn profile image
Andrew

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.

Collapse
 
albertcito profile image
Albert

Beautiful code !

Collapse
 
mariagus profile image
Maria Gusova

Very useful. Thanks!

Collapse
 
deusroyale profile image
DeusRoyale

THANKSSSSSSSSSSS!!!!! :D

Collapse
 
kansalanmol0609 profile image
Anmol Kansal

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?