Nerd For Tech
Published in

Nerd For Tech

May 14: Random CSS animations using styled-components in React

Paper zoopraxiscope by Eadweard Muybridge, 1893

Without animation, the web just isn’t as much fun. It’s just about the easiest way to engage with users because animations mimic the natural rhythms of the real world, making a website more relatable. When it’s done well, it gives an app polished, professional shine that just can’t be easily compensated for elsewhere.

I’ve written on this blog before about React, an open-source JavaScript library for building user interfaces, because its many quirks bewilder even the most seasoned developer, despite its popularity and formidable power. One of these idiosyncrasies is the way React apps relate with CSS, and since CSS is the de facto standard for web animation now that Adobe has finally put a well-deserved stake through Flash’s heart, understanding that relationship is crucial to creating compelling animations when using it. Let’s focus on that relationship in today’s post!

Background

David Ceddia makes an important point I’d like to emphasize before we begin: React doesn’t do as much as you think it does. It draws HTML on a web page — that’s it. So as you’re creating and tweaking your own animated React app, keep in mind that a great many “React issues” are really just JavaScript issues or CSS issues.

For this blog post, I’ll be using as an example a simple dice roller, inspired by life here in New York City, where street dice games are common around this time of year. Upon pressing the “Roll” button, three dice spin to the top, “bounce” off the “wall,” and “land” somewhere random on the screen:

A key term to consider: A keyframe is a snapshot of the start or end point of a smooth transition — a moment frozen in time. It’s called a frame because long, long ago, in a galaxy far, far away, animation was done on actual physical strips of papery stuff called “film”, divided into squares called frames.

Key(frame)s to the kingdom

In pure CSS, we would animate our dice with the @keyframes CSS rule, which allows us to define the start, end, and in-between points of our animation with a lot of flexibility and straightforward detail:

@keyframes dieAnimation {
from {
top: 110%;
left: 50%;
}
to {
top: 0%;
left: 50%;
transform: rotate( 360deg );
}
}

This animation will cause our dice to zoom from off the bottom of the viewport to the top, spinning 360º as it does so, and remaining in the middle of the viewport the whole time. This animation is very simple, as it only has two keyframes: a start, from:, and an end, to:. We can go into much further detail by specifying other keyframes:

@keyframes dieAnimation {
0% {
top: 110%;
left: 50%;
}
50% {
top: 0%;
left: 50%;
transform: rotate( 360deg );
}
100% {
top: 50%;
left: 50%;
transform: rotate( 360deg );
}
}

Now, our spinning die will stop at the top of the viewport halfway through the animation, before coming to rest in the center of the screen.

This is pretty cute, but it doesn’t really do justice to the experience of rolling dice. The die always follows the same path and lands in the same place… but in real life, of course, dice are random and fall in random places. So, how do we add randomness to our dice animation? There are random number generators available in some CSS preprocessors, like SASS:

$randomDuration: random( 5 );.dice { animation-duration: $randomDuration; }

But there’s one huge problem with this: SASS only randomizes at runtime, meaning that as soon as SASS processes your CSS, that number doesn’t change (until SASS runs again). That “random” number won’t be generated whenever the code runs like with Math.random(). To borrow an analogy from Jake Albaugh, picking a random number with random in SASS is like picking a random name by using the name of a random president.

Style police

It’s become clear that we’ll need to bring JavaScript into the picture to randomize our CSS dice animation. But because we’re working with React and not pure JavaScript, we’re going to do this by introducing styled-components, a package that allows us to use CSS and JavaScript together and work with both in harmony — a concept called “CSS-in-JS.”

In previous blog posts I’ve explored the declarative nature of React and the frustration this causes for programmers accustomed to writing procedural code (like pure JavaScript and CSS). JavaScript and CSS generally run in a (relatively) straightforward manner from beginning to end, but React… doesn’t behave like that (kinda the whole point). So, a variety of tools, like styled-components, have been developed to bridge that gap by allowing developers to define and use snippets of CSS as templates or modules, just as React does with JavaScript and HTML. CSS-in-JS is not a single library or package, but rather a collective term for this modular approach and the field of ideas and code packages created to implement it.

To get started, I’ll run yarn add styled-components (or npm install --save styled-components) and import it at the top of our Die component, which is basically just an SVG of the face of a die:

import styled from "styled-components";export default function Die( { number } ) {
return <svg className="die">
...
</svg>;
}

Next, I’ll replace our svg tag with the styled template I just imported, applied to an svg, with the styles I want to apply in backticks:

import styled from "styled-components";const DieSvg = styled.svg`
position: absolute;
top: ${ props => props.randomX }%;
left: ${ props => props.randomY }%;
animation: ${ dieAnimation( props => props.randomY ) } 0.5s linear;
`;
export default function Die( { number } ) {
return <DieSvg className="die">
...
</DieSvg>;
}

Take especial note of two things:

  1. I’m defining DieSvg outside our Die component! If I don’t, React will create a new DieSvg every time our Die re-renders. This won’t break your code, but it’s inefficient and redundant, and styled-components will complain accordingly in your console.
  2. I’m calling on props with a callback function! Trying to call them directly with randomX or even props.randomX will not work.

Finally, I’ll add randomX and randomY as props to both Die and DieSvg:

...
export default function Die( { number, randomX, randomY } ) {
return <DieSvg className="die" randomX={ randomX } randomY={ randomY }>
...
</DieSvg>;
}

Everybody get rannn-dom

Our Die component is now ready to accept a randomX and randomY, so it will appear in a random place on the screen. Now comes the difficult part: animating how they actually get there! Lucky for us, styled-components has a counterpart to the @keyframes rule in pure CSS that we’ll use do to it:

import styled, { keyframes } from "styled-components";const dieAnimation = randomY => keyframes`
0% {
top: 110%;
left: 50%;
}
50% {
top: 0%;
left: ${ randomY / 2 }%;
transform: rotate( 360deg );
}
100% { transform: rotate( 0deg ); }
`;
const DieSvg = styled.svg`
...
`;

Take note once again that the whole dieAnimation gets wrapped in a callback to grab the props it needs! Again: don’t try to call props directly or you’re gonna have a bad time.

Now that we’ve defined the keyframes in our dieAnimation, let’s have our DieSvg actually make use of it:

const DieSvg = styled.svg`
position: absolute;
top: ${ props => props.randomX }%;
left: ${ props => props.randomY }%;
animation: ${ dieAnimation( props => props.randomY ) } 0.5s linear;
`;

We’re invoking dieAnimation() as a function now, passing in props => props.randomY as a callback, and also specifying that it’s a linear animation with a runtime of 0.5s, just as we would in pure CSS.

Now it’s time to bring it all together! I’ll write a new React component, Dice, to display three dice at a time on the screen with the newly-minted animation we’ve moved mountains to write:

import Die from "./Die";export default function Dice( { roll } ) {const randomX = [ Math.floor( ( Math.random() * 25 ) + 5 ), Math.floor( ( Math.random() * 25 ) + 5 ), Math.floor( ( Math.random() * 25 ) + 5 ) ];const randomY = [ Math.floor( ( Math.random() * 25 ) + 5 ), Math.floor( ( Math.random() * 25 ) + 5 ), Math.floor( ( Math.random() * 25 ) + 5 ) ];   return <div>
<Die number={ roll[ 0 ] } randomX={ randomX[ 0 ] } randomY={ randomY[ 0 ] } />
<Die number={ roll[ 1 ] } randomX={ randomX[ 1 ] } randomY={ randomY[ 1 ] } />
<Die number={ roll[ 2 ] } randomX={ randomX[ 2 ] } randomY={ randomY[ 2 ] } />
</div>;
}

This component accepts a roll, an array of three random numbers between one and six, as part of its props. Then it defines two arrays, each with actual real-McCoy Math.random() random numbers that aren’t random presidents, and returns a <div> with three copies of our animated Die inside — each of which end at a different, random place on the screen!

Fork and clone the linked repository, run yarn start or npm start in the terminal, and you’ll see the results.

Conclusion

You may notice in my demonstration that the dice overlap occasionally, which is clearly not the way real dice behave. This hints at one of the difficulties of emulating the randomness of the real world — physics is a lot more complicated and nuanced than computer code. Wrestling with kinematics to give your apps verisimilitude is a hefty challenge, one only you can conquer — but I hope this brief tutorial has given you the confidence to take a chance and roll the dice!

--

--

--

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Recommended from Medium

Making Things Move: Learning JavaScript

Using Custom Cursors with Javascript for a Better User Experience

Introduction to Linting

Improving Vim Workflow With fzf

How to Implement Gatsby with WordPress?

How To Host a Vue.js Static Website With GCP Cloud Storage

React components | Functional vs. class

To React or To Not?

Close up of computer screen with React app code.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Frank

Josh Frank

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…

More from Medium

Implementing a dark theme toggle with react-redux

Front End Development with React— 7 Reasons to Use React

Understanding React Hooks

How does React.memo() work? — React source code walkthrough 14