— Tutorial, JavaScript, Frontend — 6 min read
There are a lot of great resources on how to do animations with CSS and there are a lot of great resources on how to do styling in React but there are relatively few resources on how to do them both together. One would think that if I knew one and the other that I’d smush them together to make two. That’s turned out harder than expected and here’s what I learned.
Disclaimer: The following does not necessarily represent best practices or even the recommended approach to styling and/or animating react components. They’re just possibilities of how one can style and animate react components.
Animations have great potential to improve user experience and give it that ‘wow’ factor, but they also have the potential to frustrate and make your app look tacky, so proceed cautiously when animating.
I’m choosing CSS animations over JS animations because CSS animations are often simpler and more performant than most JS libraries. JS animation libraries have different APIs while CSS animations are a standard that have been with us since CSS3. This is not to say that you should always choose one over the other because they can be combined and there are certain animations where CSS just falls flat compared to JS.
As for styled-components, I’m choosing this library for its ease and convenience. It gives me the isolation that css-modules brings, requires no build step, allows for the use of React props and JS variables and expressions to give me that extra flex with CSS, while also letting me write plain CSS (although it does get wrapped in a funky wrapper). It’s also nice that styled-components makes regular React components because you can toss them around as you would with any other React component.
Here’s a quick comparison between CSS solutions in React:
Solutions other than styled-components:
Styled-components:
https://github.com/styled-components/comparison/tree/master/examples/styled-components
Probably the most basic of all the options, we use CSS transitions by specifying the ‘transition’ property onto the element we wish to have transition. Once on, it acts like an event listener and it listens for the one property to change on the element, and once changed the element will transition.
There are many properties that can be transitioned and it generally seems that if the property can be represented by a number then it can be transitioned. But for performance reasons you should generally stick to transitioning the following properties:
https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/
With create-react-app, create a new react application and create a couple folders and files like this:
1create-react-app css-animations-styled-components
1// App/index.js23import React from 'react';4import Box from './styles/Box56export class App extends React.Component {7 render() {8 return (9 <Box />10 );11 }12}1314export default App;
1// App/styles/Box.js23import styled from 'styled-components';45export const Box = styled.div`6 display: inline-block;7 background: pink;8 width: 200px;9 height: 200px;10 transition: transform 300ms ease-in-out;1112 &:hover {13 transform: translate(200px, 150px) rotate(20deg)14 }15`1617export default Box;
Transitions are like event listeners and you can tack them onto the element you want to listen along with the property to listen for. In the ‘App/styles/Box.js’ example above, the ‘Box’ div is listening for changes on the ‘transform’ property so that when ‘transform’ does change it will execute the transition.
We trigger the change when the ‘Box’ div is hovered:
Tipsy Turny
You may notice that pink box will only transition while you’re mouse is directly hovering over it and as the box transforms into a new position your mouse will no longer be hovering it and it will stop transitioning and create this jerky movement. Here’s how to fix that.
1// App/index.js23import React from 'react';4import Box from './styles/Box5import Trigger from './styles/Trigger;67export class App extends React.Component {8 render() {9 return (10 <Trigger>11 <Box />12 </Trigger>13 );14 }15}1617export default App;
1// App/styles/Box.js23import styled from 'styled-components';45export const Box = styled.div`6 display: inline-block;7 background: pink;8 width: 200px;9 height: 200px;10 transition: transform 300ms ease-in-out;11`1213export default Box;
1// App/styles/Trigger.js23import styled from 'styled-components';45import Box from './Box;67export const Trigger = styled.div`8 width: 200px;9 height: 200px;10 border: 20px solid #999;11 background: #ddd;1213 &:hover ${Box} {14 transform: translate(200px, 150px) rotate(20deg);15 }16`1718export default Trigger;
We create a new ‘Trigger’ component (represented as a div) that holds the ‘Box’ component. We still make the ‘Box’ component listen to the transform property but now we change the trigger to happen when the user hovers over the ‘Trigger’ component instead of the ‘Box’ component.
1&:hover ${Box} {2 transform: translate(200px, 150px) rotate(20deg);3}
‘\${Box}’ is how you select other components in styled-components (make sure you import ‘Box’). The above hover line says: “Select all ‘Box’ components within ‘Trigger’ components in the state of hover and apply this transform on said ‘Box’”.
Note that the pink box still transitions as you hover it. If you want the box to only transition when your mouse hovers the ‘Trigger’ box and not the pink ‘Box’, then set ‘pointer-events: none’ on ‘Box’. setting ‘pointer-events’ to none, means ‘:hover’ won’t work on Box, but it will work on ‘Trigger’, which then applies the transform onto ‘Box’.
Keyframes are what the animation does and the animation is how it’s done. (the name, the duration, ease, direction, amount of time…). — Travis Neilson
Normal keyframes do not exist within selectors but they are given a name that will later be called by a selector. In a sense, they’re kind of global as they can be used by any selector as long as the selector calls the correct name of the keyframe. Styled-components has a helper, ‘keyframes’, that generates a unique name for your keyframe so they won’t clash in common namespaces.
Keyframes go hand in hand with the CSS ‘animation’ property. The ‘animation’ property is a short-form property and can be substituted by the following:
https://developer.mozilla.org/en-US/docs/Web/CSS/animation?v=control
And together, keyframes with the animation property can give you finer control over your animations than CSS transitions, although being more verbose.
Keyframes tell your animation what to do by defining hooks or frames at which you want things to happen. For example, imagine the life of your animation is specified by a timeline of keyframes from 0% to 100%, you could specify from 0% to 100% that you want the animation to animate a height of 200px to 600px:
1// App/styles/Box.js23import styled from 'styled-components';45import { keyFrameExampleOne } from './KeyFrames';67export const Box = styled.div`8 display: inline-block;9 background: pink;10 width: 200px;11 height: 200px;12 position: relative;13 animation: ${keyFrameExampleOne} 2s ease-in-out 0s infinite;14`1516export default Box;
1// App/styles/KeyFrames.js23import styled, { keyframes } from 'styled-components';45export const keyFrameExampleOne = keyframes`6 0% {7 height: 200px;8 }9 100% {10 height: 600px;11 background: orange;12 }13`
1// App/index.js23import React from 'react';4import Box from './styles/Box'5import Trigger from './styles/Trigger';67export class App extends React.Component {8 render() {9 return (10 <Trigger>11 <Box />12 </Trigger>13 );14 }15}1617export default App;
If you want even more control over your keyframes, you can hook into more keyframes and properties too. In the following, the box will grow in height from 200px to 600px from 0% to 100% of the keyframe timeline, but it will also grow in width from 0px to 400px from 0% to 30% of the keyframe timeline, and maintain that width until the end of the keyframe timeline:
1// App/styles/KeyFrames.js23import styled, { keyframes } from 'styled-components';45export const keyFrameExampleOne = keyframes`6 0% {7 height: 200px;8 }9 30%, 100% {10 width: 400px;11 }12 100% {13 height: 600px;14 background: orange;15 }16`
Any property you can animate with ‘transition’ is a property you can animate with ‘animation’ and ‘keyframes’, but now you can pick and choose choose what happens on a timeline of 0% to 100%. I hope you can see how much more leverage you can potentially get from keyframes and animations over regular transitions.
Let’s make a reusable React component that’s animated when rendered in. We’ll have a clickable button that renders the React component, called CoolBox.
We make two new folders for our components, Final and CoolBox:
1// src/CoolBox/index.js23import styled, { keyframes } from 'styled-components';45const coolBoxKeyframes = keyframes`6 0% {7 height: 0px;8 background: green;9 }10 100% {11 height: 200px;12 background: blue;13 }14`1516export const CoolBox = styled.div`17 display: inline-block;18 background: green;19 width: 100px;20 position: relative;21 animation-name: ${coolBoxKeyframes};22 animation-duration: 2s;23 animation-timing-function: ease;24 animation-delay: 0s;25 animation-iteration-count: 1;26 animation-direction: normal;27 animation-fill-mode: forwards;28 animation-play-state: running;29`3031export default CoolBox
There are a couple things different here. First, I made a whole new folder just for this styled-component, CoolBox, because it acts just like a component, whereas in previous examples I put the styled-components in their own ‘styles’ folder out of habit.
Use what makes sense to you and your team. In most cases, I put styled-components in their own ‘styles’ folder because I use them exclusively as styles for the corresponding component that the folder sits in, but in this example I use them as stand alone components, so I give them their own folder, separate from any other components.
We’ve also separated the short-hand ‘animation’ property into its constituents. Nothing out of the ordinary, but we do change the animation iteration count to 1 (default is infinite) because we want it to animate only once, and we change the animation-fill-mode to ‘forwards’, which specifies how a CSS animation should apply styles before/after execution. In short, by setting this value to forward we are saying, “When this element finishes its animation, I want it to have the same CSS styles as the final keyframe of its animation”.
1// src/Final/index.js23import React from 'react';45import CoolBox from '../CoolBox';67export class Final extends React.Component {8 constructor(props) {9 super(props);10 this.state = { isCool: false }11 }1213 toggleCoolness = () => {14 this.setState({ isCool: !this.state.isCool })15 }1617 render() {18 const { isCool } = this.state;19 return (20 <div>21 <button onClick={this.toggleCoolness}>Click Me</button>22 {isCool ? (23 <CoolBox />24 ) : (25 <div></div>26 )}27 </div>28 );29 }30}3132export default Final;
Using React’s local state and the double-frowney-face ternary operator, we set a state that’s toggled every time we click a button and we render the CoolBox if that state is toggled on.
1// src/App/index.js23import React from 'react';4// import Box from './styles/Box'5// import Trigger from './styles/Trigger';67import Final from '../Final';89export class App extends React.Component {10 render() {11 return (12 <Final />13 // <Trigger>14 // <Box />15 // </Trigger>16 );17 }18}1920export default App;
And this is what we get:
We went over the basics of using CSS transitions, keyframes, and animations within React and styled-components but there’s a lot more you can do in this realm of animation.
Styled-components, by itself, allows you to interpolate React props or JS variables right into your styles, meaning you can essentially use programming logic in CSS! We’ve seen a bit of this in play in the final example, but you can also now do things like:
Are these features absolutely necessary? Probably not, but it sure does make styling easier.
https://styled-components.com/
Or if CSS animations just aren’t cutting it for you and you need a bit more ‘oomph’ when animating React components you can try something like React-Motion.
https://github.com/chenglou/react-motion
And maybe you want to learn more about native CSS transitions and animations without React or styled-components, then I’d suggest watching DevTips on Youtube because that’s where I got my core examples from:
Hope this helped in learning a thing or two about using CSS animations in React with styled-components.