427 lines
14 KiB
Markdown
427 lines
14 KiB
Markdown
# CSS-animations
|
||
|
||
CSS animations make it possible to do simple animations without JavaScript at all.
|
||
|
||
JavaScript can be used to control CSS animations and make them even better, with little code.
|
||
|
||
## CSS transitions [#css-transition]
|
||
|
||
The idea of CSS transitions is simple. We describe a property and how its changes should be animated. When the property changes, the browser paints the animation.
|
||
|
||
That is, all we need is to change the property; and, the fluid transition will be done by the browser.
|
||
|
||
For instance, the CSS below animates changes of `background-color` for 3 seconds:
|
||
|
||
```css
|
||
.animated {
|
||
transition-property: background-color;
|
||
transition-duration: 3s;
|
||
}
|
||
```
|
||
|
||
Now if an element has `.animated` class, any change of `background-color` is animated during 3 seconds.
|
||
|
||
Click the button below to animate the background:
|
||
|
||
```html run autorun height=60
|
||
<button id="color">Click me</button>
|
||
|
||
<style>
|
||
#color {
|
||
transition-property: background-color;
|
||
transition-duration: 3s;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
color.onclick = function() {
|
||
this.style.backgroundColor = 'red';
|
||
};
|
||
</script>
|
||
```
|
||
|
||
There are 4 properties to describe CSS transitions:
|
||
|
||
- `transition-property`
|
||
- `transition-duration`
|
||
- `transition-timing-function`
|
||
- `transition-delay`
|
||
|
||
We'll cover them in a moment, for now let's note that the common `transition` property allows declaring them together in the order: `property duration timing-function delay`, as well as animating multiple properties at once.
|
||
|
||
For instance, this button animates both `color` and `font-size`:
|
||
|
||
```html run height=80 autorun no-beautify
|
||
<button id="growing">Click me</button>
|
||
|
||
<style>
|
||
#growing {
|
||
*!*
|
||
transition: font-size 3s, color 2s;
|
||
*/!*
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
growing.onclick = function() {
|
||
this.style.fontSize = '36px';
|
||
this.style.color = 'red';
|
||
};
|
||
</script>
|
||
```
|
||
|
||
Now, let's cover animation properties one by one.
|
||
|
||
## transition-property
|
||
|
||
In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties".
|
||
|
||
Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties).
|
||
|
||
## transition-duration
|
||
|
||
In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](http://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`.
|
||
|
||
## transition-delay
|
||
|
||
In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is 2s, then the animation starts 1 second after the property change and the total duration will be 2 seconds.
|
||
|
||
Negative values are also possible. Then the animation starts from the middle. For instance, if `transition-duration` is `2s`, and the delay is `-1s`, then the animation takes 1 second and starts from the halfway point.
|
||
|
||
Here the animation shifts numbers from `0` to `9` using CSS `translate` property:
|
||
|
||
[codetabs src="digits"]
|
||
|
||
The `transform` property is animated like this:
|
||
|
||
```css
|
||
#stripe.animate {
|
||
transform: translate(-90%);
|
||
transition-property: transform;
|
||
transition-duration: 9s;
|
||
}
|
||
```
|
||
|
||
In the example above JavaScript adds the class `.animate` to the element -- and the animation starts:
|
||
|
||
```js
|
||
stripe.classList.add('animate');
|
||
```
|
||
|
||
We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`.
|
||
|
||
Here if you click the digit -- it starts the animation from the current second:
|
||
|
||
[codetabs src="digits-negative-delay"]
|
||
|
||
JavaScript does it with an extra line:
|
||
|
||
```js
|
||
stripe.onclick = function() {
|
||
let sec = new Date().getSeconds() % 10;
|
||
*!*
|
||
// for instance, -3s here starts the animation from the 3rd second
|
||
stripe.style.transitionDelay = '-' + sec + 's';
|
||
*/!*
|
||
stripe.classList.add('animate');
|
||
};
|
||
```
|
||
|
||
## transition-timing-function
|
||
|
||
The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa.
|
||
|
||
It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it.
|
||
|
||
That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often.
|
||
|
||
### Bezier curve
|
||
|
||
The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions:
|
||
|
||
1. First control point: `(0,0)`.
|
||
2. Last control point: `(1,1)`.
|
||
3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything.
|
||
|
||
The syntax for a Bezier curve in CSS: `cubic-bezier(x2, y2, x3, y3)`. Here we need to specify only 2nd and 3rd control points, because the 1st one is fixed to `(0,0)` and the 4th one is `(1,1)`.
|
||
|
||
The timing function describes how fast the animation process goes.
|
||
|
||
- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`.
|
||
- The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value.
|
||
|
||
The simplest variant is when the animation goes uniformly, with the same linear speed. That can be specified by the curve `cubic-bezier(0, 0, 1, 1)`.
|
||
|
||
Here's how that curve looks:
|
||
|
||

|
||
|
||
...As we can see, it's just a straight line. As the time (`x`) passes, the completion (`y`) of the animation steadily goes from `0` to `1`.
|
||
|
||
The train in the example below goes from left to right with the permanent speed (click it):
|
||
|
||
[codetabs src="train-linear"]
|
||
|
||
The CSS `transition` is based on that curve:
|
||
|
||
```css
|
||
.train {
|
||
left: 0;
|
||
transition: left 5s cubic-bezier(0, 0, 1, 1);
|
||
/* JavaScript sets left to 450px */
|
||
}
|
||
```
|
||
|
||
...And how can we show a train slowing down?
|
||
|
||
We can use another Bezier curve: `cubic-bezier(0.0, 0.5, 0.5 ,1.0)`.
|
||
|
||
The graph:
|
||
|
||

|
||
|
||
As we can see, the process starts fast: the curve soars up high, and then slower and slower.
|
||
|
||
Here's the timing function in action (click the train):
|
||
|
||
[codetabs src="train"]
|
||
|
||
CSS:
|
||
```css
|
||
.train {
|
||
left: 0;
|
||
transition: left 5s cubic-bezier(0, .5, .5, 1);
|
||
/* JavaScript sets left to 450px */
|
||
}
|
||
```
|
||
|
||
There are several built-in curves: `linear`, `ease`, `ease-in`, `ease-out` and `ease-in-out`.
|
||
|
||
The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above.
|
||
|
||
Other names are shorthands for the following `cubic-bezier`:
|
||
|
||
| <code>ease</code><sup>*</sup> | <code>ease-in</code> | <code>ease-out</code> | <code>ease-in-out</code> |
|
||
|-------------------------------|----------------------|-----------------------|--------------------------|
|
||
| <code>(0.25, 0.1, 0.25, 1.0)</code> | <code>(0.42, 0, 1.0, 1.0)</code> | <code>(0, 0, 0.58, 1.0)</code> | <code>(0.42, 0, 0.58, 1.0)</code> |
|
||
|  |  |  |  |
|
||
|
||
`*` -- by default, if there's no timing function, `ease` is used.
|
||
|
||
So we could use `ease-out` for our slowing down train:
|
||
|
||
|
||
```css
|
||
.train {
|
||
left: 0;
|
||
transition: left 5s ease-out;
|
||
/* transition: left 5s cubic-bezier(0, .5, .5, 1); */
|
||
}
|
||
```
|
||
|
||
But it looks a bit differently.
|
||
|
||
**A Bezier curve can make the animation exceed its range.**
|
||
|
||
The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range.
|
||
|
||
In the example below the animation code is:
|
||
```css
|
||
.train {
|
||
left: 100px;
|
||
transition: left 5s cubic-bezier(.5, -1, .5, 2);
|
||
/* JavaScript sets left to 400px */
|
||
}
|
||
```
|
||
|
||
The property `left` should animate from `100px` to `400px`.
|
||
|
||
But if you click the train, you'll see that:
|
||
|
||
- First, the train goes *back*: `left` becomes less than `100px`.
|
||
- Then it goes forward, a little bit farther than `400px`.
|
||
- And then back again -- to `400px`.
|
||
|
||
[codetabs src="train-over"]
|
||
|
||
Why it happens is pretty obvious if we look at the graph of the given Bezier curve:
|
||
|
||

|
||
|
||
We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`.
|
||
|
||
As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`.
|
||
|
||
That's a "soft" variant for sure. If we put `y` values like `-99` and `99` then the train would jump out of the range much more.
|
||
|
||
But how do we make a Bezier curve for a specific task? There are many tools. For instance, we can do it on the site <http://cubic-bezier.com/>.
|
||
|
||
### Steps
|
||
|
||
The timing function `steps(number of steps[, start/end])` allows splitting an animation into steps.
|
||
|
||
Let's see that in an example with digits.
|
||
|
||
Here's a list of digits, without any animations, just as a source:
|
||
|
||
[codetabs src="step-list"]
|
||
|
||
We'll make the digits appear in a discrete way by making the part of the list outside of the red "window" invisible and shifting the list to the left with each step.
|
||
|
||
There will be 9 steps, a step-move for each digit:
|
||
|
||
```css
|
||
#stripe.animate {
|
||
transform: translate(-90%);
|
||
transition: transform 9s *!*steps(9, start)*/!*;
|
||
}
|
||
```
|
||
|
||
In action:
|
||
|
||
[codetabs src="step"]
|
||
|
||
The first argument of `steps(9, start)` is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so `transition: 9s` gives us 9 seconds for the whole animation – 1 second per digit.
|
||
|
||
The second argument is one of two words: `start` or `end`.
|
||
|
||
The `start` means that in the beginning of animation we need to do make the first step immediately.
|
||
|
||
We can observe that during the animation: when we click on the digit it changes to `1` (the first step) immediately, and then changes in the beginning of the next second.
|
||
|
||
The process is progressing like this:
|
||
|
||
- `0s` -- `-10%` (first change in the beginning of the 1st second, immediately)
|
||
- `1s` -- `-20%`
|
||
- ...
|
||
- `8s` -- `-80%`
|
||
- (the last second shows the final value).
|
||
|
||
The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second.
|
||
|
||
So the process would go like this:
|
||
|
||
- `0s` -- `0`
|
||
- `1s` -- `-10%` (first change at the end of the 1st second)
|
||
- `2s` -- `-20%`
|
||
- ...
|
||
- `9s` -- `-90%`
|
||
|
||
Here's `steps(9, end)` in action (note the pause between the first digit change):
|
||
|
||
[codetabs src="step-end"]
|
||
|
||
There are also shorthand values:
|
||
|
||
- `step-start` -- is the same as `steps(1, start)`. That is, the animation starts immediately and takes 1 step. So it starts and finishes immediately, as if there were no animation.
|
||
- `step-end` -- the same as `steps(1, end)`: make the animation in a single step at the end of `transition-duration`.
|
||
|
||
These values are rarely used, because that's not really animation, but rather a single-step change.
|
||
|
||
## Event transitionend
|
||
|
||
When the CSS animation finishes the `transitionend` event triggers.
|
||
|
||
It is widely used to do an action after the animation is done. Also we can join animations.
|
||
|
||
For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right:
|
||
|
||
[iframe src="boat" height=300 edit link]
|
||
|
||
The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction:
|
||
|
||
```js
|
||
boat.onclick = function() {
|
||
//...
|
||
let times = 1;
|
||
|
||
function go() {
|
||
if (times % 2) {
|
||
// sail to the right
|
||
boat.classList.remove('back');
|
||
boat.style.marginLeft = 100 * times + 200 + 'px';
|
||
} else {
|
||
// sail to the left
|
||
boat.classList.add('back');
|
||
boat.style.marginLeft = 100 * times - 200 + 'px';
|
||
}
|
||
|
||
}
|
||
|
||
go();
|
||
|
||
boat.addEventListener('transitionend', function() {
|
||
times++;
|
||
go();
|
||
});
|
||
};
|
||
```
|
||
|
||
The event object for `transitionend` has a few specific properties:
|
||
|
||
`event.propertyName`
|
||
: The property that has finished animating. Can be good if we animate multiple properties simultaneously.
|
||
|
||
`event.elapsedTime`
|
||
: The time (in seconds) that the animation took, without `transition-delay`.
|
||
|
||
## Keyframes
|
||
|
||
We can join multiple simple animations together using the `@keyframes` CSS rule.
|
||
|
||
It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it.
|
||
|
||
Here's an example with explanations:
|
||
|
||
```html run height=60 autorun="no-epub" no-beautify
|
||
<div class="progress"></div>
|
||
|
||
<style>
|
||
*!*
|
||
@keyframes go-left-right { /* give it a name: "go-left-right" */
|
||
from { left: 0px; } /* animate from left: 0px */
|
||
to { left: calc(100% - 50px); } /* animate to left: 100%-50px */
|
||
}
|
||
*/!*
|
||
|
||
.progress {
|
||
*!*
|
||
animation: go-left-right 3s infinite alternate;
|
||
/* apply the animation "go-left-right" to the element
|
||
duration 3 seconds
|
||
number of times: infinite
|
||
alternate direction every time
|
||
*/
|
||
*/!*
|
||
|
||
position: relative;
|
||
border: 2px solid green;
|
||
width: 50px;
|
||
height: 20px;
|
||
background: lime;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
There are many articles about `@keyframes` and a [detailed specification](https://drafts.csswg.org/css-animations/).
|
||
|
||
You probably won't need `@keyframes` often, unless everything is in constant motion on your sites.
|
||
|
||
## Summary
|
||
|
||
CSS animations allow smoothly (or not) animated changes of one or multiple CSS properties.
|
||
|
||
They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that.
|
||
|
||
Limitations of CSS animations compared to JavaScript animations:
|
||
|
||
```compare plus="CSS animations" minus="JavaScript animations"
|
||
+ Simple things done simply.
|
||
+ Fast and lightweight for CPU.
|
||
- JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element.
|
||
- Not just property changes. We can create new elements in JavaScript as part of the animation.
|
||
```
|
||
|
||
The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code.
|
||
|
||
But in the next chapter we'll do some JavaScript animations to cover more complex cases.
|