7. Web Animation
I just need to show you how this works :P
Overview / Transition / Keyframes / jQuery animation / Concluding remarks
Overview
This is finally the last section in this tutorial, and you have probably waited for this to come since you're really keen to add more spice to your websites. Here I am going to share some basics with you, which would be enough for some non-commercial websites. These tips only takes up a very small portion of the world of Web animation, which is still expanding rapidly. But I hope they will help build a strong foundation for you to learn some more advanced skills, which is what I'm still doing now! I would once again to highlight the risk of an excessively animated website, which will burden a Web browser especially in an old device.
Transition

We can start with the simplest approach, that is, to use the CSS property transition
to create some Web animation when there's some interaction that changes an HTML element from one state into a different state. What do I mean by this? You probably have had the answer already - for instance, when the mouse cursor hovers over an HTML element, the element is in the :hover
pseudo-class.
In our previous unit, we did try to make changes to the style of hovered elements with the pseudo-class, but all the changes were abrupt. With the CSS property transition
, we will be able to give some life by making gradual styling changes. The transition
property is in fact a family of CSS properties that allows you to fine-tune the changes between different styles:
transition-property
- You can apply a transition to almost all CSS properties, such aswidth
,background-color
, and more. If you don't need to separate the transitions of different CSS properties from each other, you can just use the valueall
. One CSS property that you can't really apply a gradual transition to isdisplay
- What does even a gradual transition fromblock
toinline-block
mean? I know. You're probably thinking about the gradual transition betweennone
andblock
, for instance, but we have other approaches to this transition below.transition-duration
- You can also determine how long the transition should be. If you set the value as1s
, then the whole transition process will take just one second.transition-timing-function
- By default, you might think of somelinear
gradual transition. That is, the speed of a transition remains constant from the beginning to the end. However, CSS3 offers you many different options, such asease-in
(slower at the beginning, faster toward the end) andease-out
(faster at the beginning, slower toward the end). A complex easing function could be generated with a CSS functioncubic-bezier()
, and here's an online tool for you to manipulate your own easing functions very easily.trainsition-delay
- By specifying this property with a time in second, you can delay the transition effect. Most of times we won't be using this property, but it can help create a trailing tail for a moving object and keep the end result of a transition bit longer.
Since all of the above individual properties have a pretty long name, it is indeed more reasonable (if compared to font
) to use the shorthand transition
and specify the four properties in the same order:
transition: property duration timing-function delay;
Let's do a quick demonstration. Create a <div>
in an HTML document and define the element's appearance with the following styles. With these styles, the block element will gradually enlarge when you move your mouse cursor over it, and at the same time the background color will also smoothly change from an orange color to a red color. And the whole process will take one second to complete.
body > div {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* aligned to the center */
width: 100px;
height: 100px;
background-color: orange;
}
body > div:hover {
width: 150px;
height: 150px;
background-color: red;
transition: all 1s ease-in;
}
Currently, we only add the transition that takes place when the target <div>
element is changed into the :hover
pseudo-class, so when you move your mouse cursor away from the element, the style changes back to the default abruptly. To also make a gradual transition back to the element's default style, we also have to add transition
to the default style as well:
body > div {
/* old styles above... */
transition: all 1s ease-out; /* use ease-out here to reverse the ease-in effect */
Maybe you want the transition to be different for background-color
and for width
and height
. Then, you could separate the transition for background-color
from all
transitions with a comma in the transition
property:
body > div:hover {
width: 150px;
height: 150px;
background-color: red;
transition: all 1s ease-in, background-color 10s ease-out; /* apply transitions separately */
}
Catch me if you can
As mentioned earlier, it is not really possible to make a transition between none
and other values for the display
property, supposed that your goal is to make an element appear/disappear gradually. But there are other solutions, too. The first one is to make a gradual transition for the property opacity
, and let's try this with the following HTML content, in which a <div>
element is enclosed inside another <div>
element. Our goal is to create the effect that when the mouse is over the outer <div>
, the inner <div>
appears gradually next to it.
<div>
<div>Hello!</div>
</div>
Below are the default styles for both <div>
elements. Crucially, we can see that the inner <div>
element is set to have position: absolute;
as well, and since its ancestor is also specified with a non-static position, the inner <div>
would be positioned with respect to the edges of the outer <div>
. Thus, when the inner <div>
is set to have left: 100%;
, it means that the inner <div>
is moved from the left edge of its ancestor by the full width of the ancestor (which is equal to 100px
). So, although the inner <div>
is enclosed inside another <div>
, it is moved out by the CSS style in the final presentation.
body > div {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px;
height: 100px;
background-color: green;
}
body > div > div {
position: absolute;
left: 100%;
width: 100px;
height: 100px;
background-color: red;
color: white;
opacity: 0; /* You can't see me, but I'm still here! */
}
Again, to achieve our goal, we have to rely on the pseudo-class :hover
. But to which element should we add this pseudo-class in our CSS file? It's the outer <div>
- because we want the inner <div>
to appear when the mouse cursor hovers over the outer one, that is, to make the outer one belong to the :hover
pseudo-class.
body > div:hover > div {
opacity: 1;
transition: all 0.3s ease-out;
}
This approach is convenient but you have to be sure that the transparent element doesn't overlap with other clickable contents because the transparent element would be an invisible wall.
In the second approach, we do not manipulate the opacity but the width (or the height) of the inner <div>
element in the transitional process. In our example above, we can start by setting the inner <div>
to have a width
of 0
in its default setting, so it is made invisible because it has a zero width! To complete hiding everything in the inner <div>
, we would have to add the overflow
property and set it as hidden
. You will also need to have white-space
set as nowrap
. These properties and settings guarantees that the texts stay in the same line but are hidden when they go beyond the edge of their container.
body > div > div {
position: absolute;
left: 100%;
width: 0; /* I'm still here, but too thin to be seen! */
height: 100px;
overflow: hidden;
white-space: nowrap;
background-color: red;
color: white;
}
Now, it shouldn't be hard to see how to set the transition
property to the style for the hovered outer <div>
, right?
body > div:hover > div {
width: 100px;
transition: all 0.3s ease-out;
}
Keyframes

In the previous section, we rely on the pseudo-class :hover
and the transition
CSS property to create some very simple Web animations when some interactions take place. But in some cases, you want to create Web animations even when there's no interaction at all as if some magician casts a spell on your website to make HTML elements do funny things, just like the texts moving back and forth above. Then you need to learn to use @keyframes
in CSS, which allows you create animation frame by frame. Let's start with the basic syntax below. The keyword @keyframes
tells a Web browser that a new Web animation is created, which is followed by the name of this particular animation, which is bgChange
in this example. With the customized name, you can apply the same animation to different HTML elements, as we will demonstrate below.
@keyframes bgChange {
from { /* The very beginning */
background-color: #000000;
}
to { /* The very end */
background-color: #FFFFFF;
}
}
Inside a @keyframes
scope, we can have the two basic anchoring points - from
defines the styles at the very beginning of the animation, and to
defines the styles at the end of the animation. The difference between the two points create a transition effect which we have seen in the previous section, which gradually changes a black background to a white background.
Now, supposed that we have two <div>
elements inside <body>
, we can apply a @keyframes
animation to both elements in the style with the animation
CSS property:
div {
height: 100px;
width: 100px;
animation: bgChange 1s ease-in; /* animation-name animation-duration animation-timing-function */
}
The above settings of the animation
property are a shorthand of the three properties of the animation
family, including animation-name
, animation-duration
, and animation-timing-function
. All these only give you a one-time effect, so you are certainly not satisfied. Don't worry, you have animation-iteration-counts
and animation-direction
at your disposal. The former decides how many times an animation should repeat, but you can certain let it go forever by setting it as infinite
. The latter determines how individual frames are ordered in each iteration. By default, it is set as forward
, so in each iteration it starts with from
and ends with to
. But you can change it to reverse
to make an animation run backwards in each iteration. Or, you could choose to set it as alternate
or alternate-reverse
, so the animation is reversed when it reaches the end point, which may be to
or from
depending on the value of your choice. All these can be put into just animation
as a shorthand.
div {
height: 100px;
width: 100px;
/* 1-3: animation-name animation-duration animation-timing-function */
/* 4-5: animation-iteration-counts animation-direction */
animation: bgChange 1s ease-in infinite alternate;
}
One last animation
family member that is used frequently is animation-delay
. This property allows you to create an animation with a different start time for individual layers of your animation, thus a trailing shadow effect. Assuming that we have two <div>
elements fixed to the same position. We can set the one in a lower layer to be slightly opaque than the one in an upper layer to serve as the shadow of the box. While the same moving animation is applied to both <div>
element, the lower one is delayed by 0.5s
, and you see a trailing shadow.
div { /* universal style for divs */
position: fixed;
top: 5vh;
left: 5vw;
width: 100px;
height: 100px;
background-color: red;
}
div:first-of-type { /* earlier = lower */
opacity: 0.5;
animation: moving 1s ease-in 0.2s infinite alternate;
}
div:last-of-type { /* later = higher */
animation: moving 1s ease-in infinite alternate; /* no delay */
}
@keyframes moving { /* a moving animation */
left: 10vh;
}
Do you want to know how to create an animation simulating a heartbeat? We'll do this in class, and we will need these CSS properties offset-path
and offset-distance
. And we will need this online tool, too. Do you know which Web browsers do not support these fun CSS properties just yet? Safari. Oh, and Internet Explorer.
Frame-by-frame design
In the above demonstrations, we only created animations based on two anchor points. But you may have noticed that in the text bar that bounces around at the top of this page, the background color gradually changes to a red color toward the midpoint before reverting back to a crimson color on both edges. This is because in a @keyframes
scope, we can certainly insert more anchor points between from
and to
with %
- 0%
is equivalent to from
, 100%
is the same as to
, and 50%
, for instance, stands for the midpoint. So, let's create an animation for a block element with the following steps:
- The block moves from the left edge of an HTML document and reaches a distance of
20vw
from the left edge by the end of the animation. - Starting from 25% of the entire process, the block starts to rotate clockwise, and by 75% of the entire process, the block completes its rotation by 45 degrees.
- Toward the midpoint of the entire process, the background color changes gradually from a red color to an orange color. After the midpoint, the background color gradually reverts back to a red color.
If you need to rotate an element, you should use the rotate()
function for the transform
property. Just google it! Now, do you have the solution? Or do you want to see how I deal with this case (select the section below)?
@keyframes moveRotateBgChang {
0% {
/* Still need to define the initial position if you don't have it in the default style of the element */
left: 0;
}
25% {
/* This is where the block starts to rotate, so set the initial appearance here */
transform: rotate(0deg);
}
50% {
background-color: orange;
animation-timing-function: linear; /* Set the easing function for each step */
}
75% {
transform: rotate(45deg); /* The point where the rotation should ends */
animation-timing-function: linear;
}
100% {
transform: rotate(45deg); /* The end result of rotation should hold */
left: 20vw; /* The end point, and the movement should end here */
animation-timing-function: ease-out;
}
}
jQuery animation
Now, you're very last mission - to learn how to animate HTML elements using jQuery. You need to go through this final stage for sure, because non-interactive animations may not be relevant to the visual representation of your user interface at all, and the pure CSS solution with :hover
is just limited to the mouseover
event. You need to write JS codes to animate your elements beyond the above cases. It may sound scary, but it's in fact not at all with jQuery - remember, it means to make things easier.
We can start with the functions that are extremely straightforward (which are unavailable in the slim build!):
fadeIn()
- Make selected elements opaque by gradually changingopacity
to 1.fadeOut()
- Make selected elements opaque by gradually changingopacity
to 0.fadeTo()
- Gradually changingopacity
to a specific level.fadeToggle()
- Gradually make selected visible elements opaque and then hide them at the end, or make selected elements visible and then gradually make them opaque.hide()
- Gradually shrink and hide selected elements.show()
- Gradually expand and display selected elements.toggle()
- Gradually shrink and hide visible elements or expand and display hidden elements.slideUp()
- Gradually slide up and hide selected elements.slideDown()
- Gradually slide down and show selected elements.slideToggle()
- Gradually slide up and hide selected elements or slide down and display selected elements.
For each jQuery animation function above, the default length of an animation is 400 ms. You can pass over a parameter and control the pace of an animation. You can start by passing a string like 'slow'
or 'fast'
, which corresponds to 200 ms and 600 ms respectively. Alternatively, you could pass over a number in millisecond to specify the length of an animation directly:
$(selector).hide('fast'); // complete hiding in 200 ms
$(selector).hide('slow'); // complete hiding in 600 ms
$(selector).hide(1000); // complete hiding in 1 sec
One rare drawback in jQuery Web animation is that by default there are only two easing functions allowed in an animation function, which are linear
and swing
respectively. The latter is the default and corresponds to the ease-in-out
value for the CSS properties transition-timing-function
and animation-timing-function
as demonstrated below. To have more easing functions at your disposal, you will have to incorporate the jQuery UI plug-ins into your website (with benefits more than just a complete collection of easing functions).
$(selector).hide('fast', 'linear'); // linear hiding animation;
$(selector).hide('fast', 'swing'); // ease-in-out hiding animation
If you want to make something happen after an animation is complete, you can also pass a callback function (remember what it is?) as a parameter to an animation function, which will be executed at the end of the animation. For instance, if you hope to show one element after another, you can add the show()
function in a callback function passed to another show()
function:
$('#firstLine').show(500, 'linear', function() {
$('#secondLine').show(500, 'linear'); // executed after the first animation is done.
});
Alternatively, if you need an element to animate continuously, you could add animation functions one after another without using any callbacks at all, so the next animation applies after the previous is done. Here I use hide()
and slideDown()
to demonstrate how an element is shrinked and hidden in 1000 ms and then gradually appears with a sliding-down effect in 2000 ms:
$('#thirdLine').hide(1000).slideDown(2000);
Also, you could insert a delay()
function between every two animation functions to delay one animation after another animation:
// slideDown() delayed by 1000 ms after hide()
$('#thirdLine').hide(1000).delay(1000).slideDown(2000);
So far, we have learned how to run a series of animations that terminate after the last one is done, but we have seen that with the CSS property animation
, we could repeat the same animations very easily. Is it possible to achieve this with the jQuery animation functions? The answer is of course, and you need to write a recursive function that runs itself. Put differently, inside a function, you attach multiple animation functions to an element, and after the last animation is done, you use a callback function in which calls the main function itself. I've provided some keywords here, so you can try to do it...or just select the following block for my solution.
// Create the function that runs animations
function repeatAnimation () {
$('#target').hide(1000).delay(500).slideDown(2000, ()=>{
// Call the function itself in the callback of the last animation function, so it's recursive
repeatAnimation();
});
}
// Call the function for the first time
repeatAnimation();
animate()
jQuery provides convenient functions to do simple animations, but they are mostly about showing and hiding things. This is not surprising because we constantly need to show or hiding things (such as a navigation pane) in an interactive Web user interface, but we frequently need something else, too, such as re-positioning an element or changing the apearance of an element. In these cases, you use the (almost) mighty animate()
function.
Earlier in this unit, we saw that an element can have a transition between two CSS styles depending on its hovering state. The logic is similar when it comes to the animate()
function - you pass a CSS style as an object into the function and specify the length for an element to gradually make a transition from its style specified in a CSS stylesheet to the CSS style passed to the function. The main difference is that animate()
need not be attached to a mouseenter
event (that is, hovering). It can runs actively when it is attached directly to an element, or it can be enclosed inside an event listener of all different kinds of events, not just hovering:
// Change the opacity to 1 and the distance to the left edge to 50vw in 1000 ms
$(selector).animate({
opacity: 1,
left: '50vw'
}, 1000);
// Do the same thing, but only when the selected element is clicked
$(selector).on('click', function() {
$(this).animate({
opacity: 1,
left: '50vw'
}, 1000);
});
The animate()
is so powerful that it could do almost everything you need to animate an element, and you can always check the official document to master its use. But maybe it's useful to summarize some important points here for you:
- You cannot use it to animate a CSS property that has a non-numeric value, such as
background-color
(it could be made possible with the jQuery Color plug-in, though!) ordisplay
(again, animatingopacity
is a workaround, or see the official document for a slightly more complex solution). - The function cannot be applied to CSS shorthands that require multiple values to be specified, such as
font
andborder
. - When you refer to a CSS property that comes with a dash, such as
font-size
, the name of the key in the object passed toanimate()
should get rid of the dash (e.g.,fontSize
) or be enclosed inside a pair of quotation marks (e.g.,'font-size'
). - Finally, if you directly specify a numeric value without any unit for a CSS property in the object passed to
animate()
, the value will be treated aspixels
. You can provide the unit as well, but with the number the entire value should be enclosed inside a pair of quotation marks as in the above example (e.g.,'50%'
or'50vw'
).
Case study
With these tips in mind, you can explore the function's potential by yourself, but here we can practice doing an animation that has become fairly common in recent years. That is, as you scroll down a Web page, when en element enters the viewport of a Web browser, it slightly slides up and fade in at the same time (check this Codepen.io example). To do this, we need to achieve the following goals using both CSS and jQuery:
- In your CSS file, set the targets of the animation to be fully transparent and slightly lower from it's default position.
- Inside a function in your JS file, obtain three types of information - 1. The height of the viewport, 2. The distance from the top of the viewport to the top of the entire document, and 3. The distance from the top edge of a target element to the top edge of the entire document.
- With the above information, calculate if the sum of the viewport height and the distance between the viewport top edge and the document top edge is higher than the distance from a target element top edge to the document top edge.
- If yes, it means the top edge of a target element enters the viewport, so you can animate the target element back to its original position.
- In your JS file, run the above function right at the beginning, because some target elements might be in the viewport already when the webpage is fully loaded.
- In your JS file, add an event listener to
document
for ascroll
event and execute the above function when the event is fired.
Let's do this bit by bit, supposed that our target elements are a set of div
blocks. To make them slightly lower than their default position, we need the CSS settings position: relative;
and top: 20px;
:
div {
width: 50px;
height: 50px;
background-color: red;
margin: 10px 0; /* To separate blocks from each other */
position: relative;
top: 20px; /* Lower from its default position by 20 px */
opacity: 0; /* Fully transparent by default */
}
Now, let's build our animation function. To get the viewport height of a Web browser in jQuery, we can use $(window).height();
. The distance between the top edge of the browser window and the top edge of the window viewport could be obtained by $(window).scrollTop();
- that is, the distance in pixel by which the current viewport is scrolled down from the top edge of the entire browser window. Finally, the distance between the top edge of each target element and the top edge of entire document could be obtained with offset()
. This function return an object including the distance in pixel from each of the four borders of an element to each of the four edges of the document. So, offset().top
gives us the information we need. Now, with these numbers, we can use the each()
function in jQuery to check if every target element enters the viewport in an if
condition, and animate a target if the condition is true.
function slideUpTarget () {
var divs = $('div'); // Get all target div elements
var vh = $(window).height(); // Get viewport height
var scrollTop = $(window).scrollTop(); // Get the scrolling distance
divs.each(function() { // A function runs for each element in divs
// 'this' means the current element from divs
var divTop = $(this).offset().top; // Get the element distance
// Use a separate function to run the animation because of the limited layout of this course website...
// Pass over necessary data
sliding(this, vh, scrollTop, divTop);
});
}
function sliding (target, vh, scrollTop, divTop) {
/* Run animation only when the current viewport height plus the scrollTop
is heigher than divTop */
if(vh + scrollTop > divTop) {
// Change top back to 0 (default position) and make it fully opaque
target.animate({top: 0, opacity: 1}, 1000);
}
}
The two functions have no effect because we haven't called them. The first time we need to call it is when the webpage is ready, as indicated by the ready()
function in jQuery:
$(document).ready(function() {
slideUpTarget(); // Animate qualified targets when the webpage is ready
});
Then, we can call it in an event listener attached to the entire document for the scroll
event:
$(document).ready(function() {
slideUpTarget(); // Animate qualified targets when the webpage is ready
$(document).on('scroll', function() {
slideUpTarget(); // Run every time you scroll the webpage
});
});
Hmm. Is it easy? Maybe yes, and maybe not. But it can be done in jQuery within twenty short lines of JS codes, so by any computational standard it is by no means complicated :). It would probably take you an hour to do your research and get it done after tries and errors, but even as amateurish as I am, I can do it in just a few minutes after a few years of experience (and also a few minutes of googling). The key is that you need to keep doing Web programming, and you can achieve whatever you plan to much faster than ever.
Now I am going to leave one very last challenge to you. In the above example, once the target elements are animated, they won't animate again even if you scroll up and then scroll back down. So I hope you can find a way to reset the animation, so when you scroll up and scroll back down, the same animation applies to the target elements again. You just need to make a few tweaks to the above JS codes, and I guess by this time you already don't need any hint, do you?