I'm an avid football (soccer) fan (Wolverhampton Wanderers 🐺 / AFC Bournemouth 🍒) and like nothing better than sitting down to watch the Premier League highlights on BBC's MOTD on a Saturday evening. After a game's highlights, the players and managers are interviewed and while these interviews are taking place, an array of stats and information is displayed to the viewer. This is achieved using fancy animation techniques which draw attention to key information. I've always been impressed with this and it looks super cool.
Feeling inspired, I wanted to recreate some of these animation sequences via CSS, so no JavaScript. This was a good opportunity to practise and learn new skills. The following guide will go through the steps to create the animations.
Initial Project Setup
To make this simple, we'll create a single HTML page called index.html that will reference all the HTML and CSS, resulting in no JavaScript.
In the index.html file we'll create a div element with the class container that will have a child div element with the class motd-card. This div element will contain two div elements, one for the title and the other for the contents of the card.
<div class="container">
<div class="motd-card">
<div class="motd-card-title">Match Stats</div>
<div class="motd-card-body">
</div>
</div>
</div>
The body div with the class motd-card-body will contain a further two div elements, one for the match details with a class of match and another with the class attendance for the number of people who attended the match.
<div class="container">
<div class="motd-card">
<div class="motd-card-title">Match Stats</div>
<div class="motd-card-body">
<div class="match">
<!-- Add match markup here -->
</div>
<div class="attendance">Attendance: 30,544</div>
</div>
</div>
</div>
The div with the class match will contain the following markup to style the teams and score data.
<span class="team">Brighton</span><span class="score">1-0</span><span class="team">West Ham</span>
Note: Sorry West Ham fans, but the example I'm using is when Brighton beat West Ham 1-0 at Amex Stadium on the 5th October 2018.
Next, we need to add the CSS styles so the match stats are styled correctly and looks like that displayed on the TV 📺.
html,
body {
height: 100%;
}
body {
background-color: #333;
font-family: 'Ubuntu', Arial, Helvetica, sans-serif;
margin: 0;
}
.motd-card {
width: 275px;
}
.motd-card-title {
background-color: #FFD130;
color: #2B0400;
font-weight: bold;
padding: 8px 0 6px;
margin: 0;
overflow: hidden;
text-align: center;
text-transform: uppercase;
white-space: nowrap;
}
.motd-card-body {
background-color: #FFF;
color: #2B0400;
overflow: hidden;
}
.match {
font-weight: bold;
padding: 5px 0 0;
text-align: center;
}
.team {
text-transform: uppercase;
}
.score {
background-color: #FFD130;
padding: 2px 6px;
margin: 0 6px;
}
.attendance {
font-size: 0.75em;
padding: 4px;
text-align: center;
text-transform: uppercase;
}
Make sure to add a link element within the head tag of the HTML, which links to the Google Font Ubuntu. This is so the text uses a similar sans-serif font to that used by the BBC's MOTD.
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet">
Completed index.html should look like the one below.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet">
<title>Match Of The Day CSS Animation</title>
<style>
html,
body {
height: 100%;
}
body {
background-color: #333;
font-family: 'Ubuntu', Arial, Helvetica, sans-serif;
margin: 0;
}
.motd-card {
width: 275px;
}
.motd-card-title {
background-color: #FFD130;
color: #2B0400;
font-weight: bold;
padding: 8px 0 6px;
margin: 0;
overflow: hidden;
text-align: center;
text-transform: uppercase;
white-space: nowrap;
}
.motd-card-body {
background-color: #FFF;
color: #2B0400;
overflow: hidden;
}
.match {
font-weight: bold;
padding: 5px 0 0;
text-align: center;
}
.team {
text-transform: uppercase;
}
.score {
background-color: #FFD130;
padding: 2px 6px;
margin: 0 6px;
}
.attendance {
font-size: 0.75em;
padding: 4px;
text-align: center;
text-transform: uppercase;
}
</style>
</head>
<body>
<div class="container">
<div class="motd-card">
<div class="motd-card-title">Match Stats</div>
<div class="motd-card-body">
<div class="match">
<span class="team">Brighton</span><span class="score">1-0</span><span class="team">West Ham</span>
</div>
<div class="attendance">Attendance: 30,544</div>
</div>
</div>
</div>
</body>
</html>
Creating The Animation Sequence
OK, so now the basic HTML and CSS has been written we can now concentrate on the match stats animation sequences. There are several parts to the animation. These can be broken down into three transitions:
- Entry on screen
- Displaying initial data
- Exit off screen
Entry On Screen Animation
Entry on screen is the first transition and is how the animation enters the display. So the first thing that happens is the title for the bar gradually grows from the left until the bar is full width. We need to animate this first, so in CSS we can create animation keyframes using the CSS rule @keyframes.
CSS keyframes are what describe the animation sequence. During animation the CSS styles are changed accordingly as specified in the keyframe selectors. The browser works out the animation sequences that are needed in-between to make the animation smooth and seamless.
You can find out more about @keyframes rules by visiting the MDN web docs - @keyframes.
To declare a CSS keyframes rule I've used the @keyframes along with a name that I can reference later on. For our initial animation we'll call it expand-card-title.
The from and to keyframe selectors can be used to control how the animation will start and end. Initially the title isn't visible, so the from width should be set to 0 pixels wide. When set to 0 then nothing is displayed. The end state needs to be the full width of the title which is 275px wide.
Add the following keyframes rule to the end of the CSS style tag declared within the index.html file.
@keyframes expand-card-title {
from {
width: 0;
}
to {
width: 275px;
}
}
The above keyframes rule will animate the width of the title until it reaches the full width.
Animation CSS Properties
We now need to wire up the keyframes rule to the CSS class rule and this is done via the animation-* properties. The two important properties are the animation-name and animation-delay. The animation-name references the keyframes rule, so in the above case expand-card-title. The animation-duration expects a value defined in seconds (s) or milliseconds (ms) and this is how long the animation cycle should take to complete. For this animation we'll use 0.8s (800 milliseconds) as the value.
You can find out more about animation-* properties and there accepted values by visiting the MDN web docs - animation.
Find the CSS class .motd-card-title and make the following modifications.
.motd-card-title {
animation-duration: 0.8s;
animation-name: expand-card-title;
...
}
Now the title animates as expected, however the body is still visible which isn't the desired outcome.
We'll need to hide the body, so we'll make a slight modification to the CSS class .motd-card-body. Therefore we'll need to add the property max-height and set the value to 0, which will make the body hidden from view.
.motd-card-body {
...
max-height: 0;
...
}
Once the above has been implemented, the title will now animate as desired.
Displaying Initial Data Animation - Part I
The next transition is the initial display of data. This is achieved by moving the title up and revealing the body contents underneath. Firstly, we need to define the transition, which will move the title up. This is achieved by defining the keyframes rule make-room-for-body. Add the new keyframes rule after the expand-card-title rule.
@keyframes make-room-for-body {
from {
top: 28px;
}
to {
top: 8px;
}
}
Again, we've used the from and to keyframe selectors to control the transition. In this case we want the card to move up allowing the contents to be shown.
Next we have to make a few modifications to the .motd-card selector. We need to add both the position and top properties to aid with making the card move up, which will make room for the contents.
.motd-card {
position: absolute;
top: 28px;
...
}
Notice that the top is set to the same value 28px as the top property in the make-room-for-body keyframes rule. This is so the animation uses the same value and doesn't judder around and look unnatural.
Find the .motd-card-title and make the following modifications.
.motd-card {
animation-name: make-room-for-body;
animation-duration: 0.4s;
animation-delay: 1s;
...
}
Animation Delay CSS Property
Here I'll introduced another animation property called animation-delay. This is the time in either seconds (s) or milliseconds (ms) waited before the animation starts.
You can find out more about animation-delay and its accepted values by visiting the MDN web docs - animation-delay.
The animation-delay for the make-room-for-body needs to be higher than the duration of expand-card-title animation sequence (i.e. the expand-card-title transition has to be finished before we start the next transition). So setting the delay to a second means the expand-card-title transition would have completed as the duration for this is 0.8s. This allows for 0.2s delay before the next transition starts (0.8s + 0.2s = 1s).
Before we move on, we need to address the issue that the title is moving up as expected, but then jolts back down to its original position.
It's doing this because the transition finished and the top property is set to 8px, yet the initial motd-card CSS class has the top property defined as 28px. After the transition has finished the default CSS styles are restored and the top property is reset to initial value 28px. This is what is causing the jolt.
Animation Fill Mode CSS Property
The solution for this is animation-fill-mode property. This property can be used with the value forwards, which means the CSS styles that were defined in the final transition will be retained and stops the property values being reverted to their initial values.
You can find out more about animation-fill-mode and its accepted values by visiting the MDN web docs - animation-fill-mode.
.motd-card {
...
animation-fill-mode: forwards;
...
}
Displaying Initial Data Animation - Part II
Now we can move on to the second phase of displaying the initial data transition, which involves showing the card body / content. We'll use the transition to reveal the card body by making the max-height property increase until all the content is completely visible.
For the next step, we need to setup a keyframes rule. This time will call it reveal-card-body. Add this keyframes rule after the other keyframes rules.
@keyframes reveal-card-body {
from {
max-height: 0;
}
to {
max-height: 100%;
}
}
We now have to add the keyframes rule to the .motd-card-body selector using the animation-* properties. So find the .motd-card-body and add the following properties.
.motd-card-body {
animation-delay: 1s;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-name: reveal-card-body;
...
}
We use the animation-fill-mode property, as we don't want the max-height property to revert back to the value 0, which would make the content disappear. Using animation-fill-mode with the value of forwards will retain the max-height value to 100%, thus keeping the content visible after the transition has occurred.
Again we've used an animation delay of a second, as we want the first transition Entry On Screen to finish before this transition is started.
Transition Timing Function CSS Property
However, the animation is still not smooth, so there's another property that we can use to make this transition smoother and this is called transition-timing-function.
By default, the value is set to ease. This is a transition effect that start slowly and then speeds up and the ends slowly. This doesn't give the smooth transition we require, therefore, I suggest we change this and use the ease-in-out value, which will transition with a slow start and end.
You can find out more about transition-timing-function and its accepted values by visiting the MDN web docs - transition-timing-function.
.motd-card-body {
...
transition-timing-function: ease-in-out;
...
}
To make it smoother still we can add a transition-timing-function property to the .motd-card CSS class with the value of ease-out.
.motd-card {
transition-timing-function: ease-out;
}
Refactor
Before we go any further, we need to consider refactoring and making it easier for the next phase, which is the Exit Off Screen.
As with most CSS properties there is also a CSS property shorthand. You've probably seen these before a common shorthand property is the background property, where you can specify multiple CSS properties relating to the background in one handy property. For example:
background: no-repeat url('../../media/examples/star.png');
The animation-* properties also have a shorthand version, which allows us to declare our animations using a short way. This shorthand also allows us to specify the transition-timing-function and multiple animations for the CSS class.
You can find out more about animation shorthand and its accepted values by visiting the MDN web docs - animation.
Let's refactor the .motd-card-title CSS class, remove the animation-* properties and replace with the following:
animation: expand-card-title 0.8s;
That's a lot cleaner and easier to read, now let's refactor the motd-card CSS class by again removing the animation-* properties and the transition-timing-function properties and replace with the following:
animation: make-room-for-body 0.4s ease-out 1s forwards;
Let's quickly explain what each value represents.
Value | Description |
---|---|
make-room-for-body | The name of the keyframes rule |
0.4s | The duration of the animation |
ease-out | The transition-timing-function effect |
1s | The animation delay |
forwards | The animation-fill-mode |
The order of these properties are crucial as the first time value is the duration and the second time value is the delay used for the transition.
We'll now refactor the .motd-card-body CSS class, remove the animation-* and the transition-timing-function properties and replace with the following:
animation: reveal-card-body 1s ease-in-out 1s forwards;
Again the values represent the animation name (reveal-card-body), animation duration (1s), transition timing function effect (ease-in-out), animation delay (1s) and the animation fill mode (forwards).
Exit Off Screen Animation
As mentioned earlier in the refactoring using the animation shorthand CSS property we can now specify multiple animations, which brings us nicely onto the exit transitions.
So we've got the card to appear, but what about removing it and animating this. Well we're in luck and no we don't have to define more keyframes rules, as luckily the CSS animations specification support reversing a transition. How do we do that? Well the animation-direction property is the answer.
Animation Direction CSS Property
The animation-direction can set whether an animation should play forwards, backwards, or alternating back and forth. As we want to reverse the animation, we can use the reverse value.
You can find out more about animation-direction and its accepted values by visiting the MDN web docs - animation-direction.
But didn't we just refactor, so we didn't have to use the animation-* properties and used the animation shorthand CSS property instead. Yes, we did, it was just easier to explain and if you need any reference and support you can easily find the documentation.
We can now start to remove the card from the page. Before we do this, we must consider the timings as we want the content to be readable before it's removed. There's no point in removing it as soon as it's displayed as the user won't get a chance to read it.
We know it takes around a second to finish all animations and we want the content to be displayed for four seconds prior to starting the exit transition. Therefore, a delay of five seconds would be needed before we start the exit transition. With that in mind, we'll need to work backwards and apply this delay to the last transition. So, find the .motd-card-body CSS class and add another animation for the exit transition.
.motd-card-body {
animation:
reveal-card-body: 1s ease-in-out 1s forwards,
reveal-card-body: 1s ease-in-out 5s forwards reverse;
}
Animations are separated via a comma and we've placed them on separate lines, this makes it easier to read and maintain, but they can be placed on the same line. Don't forget to use the semi-colon (;) to mark the end of the animation property.
Regarding the second animation in the above exit transition, we want to keep the property values from the transition (in this case the max-height value of 0). Start at to and end with the from selector within the keyframes rule reveal-card-body.
We'll then need to move the title back down to the original top starting position of 28 pixels. So find the .motdf-card CSS class and add another animation for the exit transition.
.motd-card {
animation:
make-room-for-body 0.4s ease-out 1s forwards,
make-room-for-body 0.4s ease-out 6s forwards reverse;
...
}
Those that are eagle-eyed would have noticed that the delay has been set to six seconds, although before we mentioned that it should be five seconds. We've set it to six seconds as this will allow the reveal-card-body exit transition to complete first. This will give us the desired effect of the title moving back down to its original starting position.
Finally, we need to shrink the title and remove it from view. To do this find the .motd-card-title CSS class and modify accordingly:
.motd-card-title {
animation:
expand-card-title: 0.8s,
expand-card-title: 0.8s 6.4s forwards reverse;
...
}
We're delaying the exit transition by 6.4 seconds so the above exit transition make-room-for-body is completed before we trigger this transition. We've used forward here so the transition property is retained resulting in the card being removed from view.
Now that it's complete, you can play around with the colours, size and speed. As a further step you could also add JavaScript to make the animation dynamic and remove the hard-coded values.
And that's it! You've built a MOTD inspired CSS animation. Below is the final version of the index.html file.
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet">
<title>Match Of The Day CSS Animation</title>
<style>
html,
body {
height: 100%;
}
body {
background-color: #333;
font-family: 'Ubuntu', Arial, Helvetica, sans-serif;
margin: 0;
}
.container {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
margin: 0;
}
.motd-card {
animation:
make-room-for-body 0.4s ease-out 1s forwards,
make-room-for-body 0.4s ease-out 6s forwards reverse;
position: absolute;
top: 28px;
width: 275px;
}
.motd-card-title {
animation:
expand-card-title 0.8s forwards,
expand-card-title 0.8s 6.4s forwards reverse;
background-color: #FFD130;
color: #2B0400;
font-weight: bold;
padding: 8px 0 6px;
margin: 0;
overflow: hidden;
text-align: center;
text-transform: uppercase;
white-space: nowrap;
}
.motd-card-body {
animation:
reveal-card-body 1s ease-in-out 1s forwards,
reveal-card-body 1s ease-in-out 5s forwards reverse;
background-color: #FFF;
color: #2B0400;
max-height: 0;
overflow: hidden;
}
.match {
font-weight: bold;
padding: 5px 0 0;
text-align: center;
}
.team {
text-transform: uppercase;
}
.score {
background-color: #FFD130;
padding: 2px 6px;
margin: 0 6px;
}
.attendance {
font-size: 0.75em;
padding: 4px;
text-align: center;
text-transform: uppercase;
}
@keyframes expand-card-title {
from {
width: 0;
}
to {
width: 275px;
}
}
@keyframes make-room-for-body {
from {
top: 28px;
}
to {
top: 8px;
}
}
@keyframes reveal-card-body {
from {
max-height: 0;
}
to {
max-height: 200px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="motd-card">
<div class="motd-card-title">Match Stats</div>
<div class="motd-card-body">
<div class="match">
<span class="team">Brighton</span><span class="score">1-5</span><span class="team">West Ham</span>
</div>
<div class="attendance">
Attendance: 30,544
</div>
</div>
</div>
</div>
</body>
</html>
Also get the latest source code from GitHub.