components
201
7-animation/1-bezier-curve/article.md
Normal file
|
@ -0,0 +1,201 @@
|
|||
# Bezier curve
|
||||
|
||||
Bezier curves are used in computer graphics to draw shapes, for CSS animation and in many other places.
|
||||
|
||||
They are actually a very simple thing, worth to study once and then feel comfortable in the world of vector graphics and advanced animations.
|
||||
|
||||
## Control points
|
||||
|
||||
A [bezier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) is defined by control points.
|
||||
|
||||
There may be 2, 3, 4 or more.
|
||||
|
||||
For instance, two points curve:
|
||||
|
||||

|
||||
|
||||
Three points curve:
|
||||
|
||||

|
||||
|
||||
Four points curve:
|
||||
|
||||

|
||||
|
||||
If you look closely at these curves, you can immediately notice:
|
||||
|
||||
1. **Points are not always on curve.** That's perfectly normal, later we'll see how the curve is built.
|
||||
2. **The curve order equals the number of points minus one**.
|
||||
For two points we have a linear curve (that's a straight line), for three points -- quadratic curve (parabolic), for four points -- cubic curve.
|
||||
3. **A curve is always inside the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of control points:**
|
||||
|
||||
 
|
||||
|
||||
Because of that last property, in computer graphics it's possible to optimize intersection tests. If convex hulls do not intersect, then curves do not either. So checking for the convex hulls intersection first can give a very fast "no intersection" result. Checking the intersection or convex hulls is much easier, because they are rectangles, triangles and so on (see the picture above), much simpler figures than the curve.
|
||||
|
||||
**The main value of Bezier curves for drawing -- by moving the points the curve is changing *in intuitively obvious way*.**
|
||||
|
||||
Try to move control points using a mouse in the example below:
|
||||
|
||||
[iframe src="demo.svg?nocpath=1&p=0,0,0.5,0,0.5,1,1,1" height=370]
|
||||
|
||||
**As you can notice, the curve stretches along the tangential lines 1 -> 2 and 3 -> 4.**
|
||||
|
||||
After some practice it becomes obvious how to place points to get the needed curve. And by connecting several curves we can get practically anything.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
  
|
||||
|
||||
## De Casteljau's algorithm
|
||||
|
||||
There's a mathematical formula for Bezier curves, but let's cover it a bit later, because
|
||||
[De Casteljau's algorithm](https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm) it is identical to the mathematical definition and visually shows how it is constructed.
|
||||
|
||||
First let's see the 3-points example.
|
||||
|
||||
Here's the demo, and the explanation follow.
|
||||
|
||||
Control points (1,2 and 3) can be moved by the mouse. Press the "play" button to run it.
|
||||
|
||||
[iframe src="demo.svg?p=0,0,0.5,1,1,0&animate=1" height=370]
|
||||
|
||||
**De Casteljau's algorithm of building the 3-point bezier curve:**
|
||||
|
||||
1. Draw control points. In the demo above they are labeled: `1`, `2`, `3`.
|
||||
2. Build segments between control points 1 -> 2 -> 3. In the demo above they are <span style="color:#825E28">brown</span>.
|
||||
3. The parameter `t` moves from `0` to `1`. In the example above the step `0.05` is used: the loop goes over `0, 0.05, 0.1, 0.15, ... 0.95, 1`.
|
||||
|
||||
For each of these values of `t`:
|
||||
|
||||
- On each <span style="color:#825E28">brown</span> segment we take a point located on the distance proportional to `t` from its beginning. As there are two segments, we have two points.
|
||||
|
||||
For instance, for `t=0` -- both points will be at the beginning of segments, and for `t=0.25` -- on the 25% of segment length from the beginning, for `t=0.5` -- 50%(the middle), for `t=1` -- in the end of segments.
|
||||
|
||||
- Connect the points. On the picture below the connecting segment is painted <span style="color:#167490">blue</span>.
|
||||
|
||||
|
||||
| For `t=0.25` | For `t=0.5` |
|
||||
| ------------------------ | ---------------------- |
|
||||
|  |  |
|
||||
|
||||
4. Now in the <span style="color:#167490">blue</span> segment take a point on the distance proportional to the same value of `t`. That is, for `t=0.25` (the left picture) we have a point at the end of the left quarter of the segment, and for `t=0.5` (the right picture) -- in the middle of the segment. On pictures above that point is <span style="color:red">red</span>.
|
||||
|
||||
5. As `t` runs from `0` to `1`, every value of `t` adds a point to the curve. The set of such points forms the Bezier curve. It's red and parabolic on the pictures above.
|
||||
|
||||
That was a process for 3 points. But the same is for 4 points.
|
||||
|
||||
The demo for 4 points (points can be moved by a mouse):
|
||||
|
||||
[iframe src="demo.svg?p=0,0,0.5,0,0.5,1,1,1&animate=1" height=370]
|
||||
|
||||
The algorithm for 4 points:
|
||||
|
||||
- Connect control points by segments: 1 -> 2, 2 -> 3, 3 -> 4. There will be 3 <span style="color:#825E28">brown</span> segments.
|
||||
- For each `t` in the interval from `0` to `1`:
|
||||
- We take points on these segments on the distance proportional to `t` from the beginning. These points are connected, so that we have two <span style="color:#0A0">green segments</span>.
|
||||
- On these segments we take points proportional to `t`. We get one <span style="color:#167490">blue segment</span>.
|
||||
- On the blue segment we take a point proportional to `t`. On the example above it's <span style="color:red">red</span>.
|
||||
- These points together form the curve.
|
||||
|
||||
The algorithm is recursive and can be generalized for any number of control points.
|
||||
|
||||
Given N of control points:
|
||||
|
||||
1. We connect them to get initially N-1 segments.
|
||||
2. Then for each `t` from `0` to `1`, we take a point on each segment on the distance proportional to `t` and connect them. There will be N-2 segments.
|
||||
3. Repeat step 2 until there is only one point.
|
||||
|
||||
These points make the curve.
|
||||
|
||||
```online
|
||||
**Run and pause examples to clearly see the segments and how the curve is built.**
|
||||
```
|
||||
|
||||
|
||||
A curve that looks like `y=1/t`:
|
||||
|
||||
[iframe src="demo.svg?p=0,0,0,0.75,0.25,1,1,1&animate=1" height=370]
|
||||
|
||||
Zig-zag control points also work fine:
|
||||
|
||||
[iframe src="demo.svg?p=0,0,1,0.5,0,0.5,1,1&animate=1" height=370]
|
||||
|
||||
Making a loop is possible:
|
||||
|
||||
[iframe src="demo.svg?p=0,0,1,0.5,0,1,0.5,0&animate=1" height=370]
|
||||
|
||||
A non-smooth Bezier curve (yeah, that's possible too):
|
||||
|
||||
[iframe src="demo.svg?p=0,0,1,1,0,1,1,0&animate=1" height=370]
|
||||
|
||||
```online
|
||||
If there's anything unclear in the algorithm description, then live examples above show how
|
||||
the curve is built.
|
||||
```
|
||||
|
||||
As the algorithm is recursive, we can build Bezier curves of any order, that is: using 5, 6 or more control points. But in practice many points are less useful. Usually we take 2-3 points, and for complex lines glue several curves together. That's simpler to develop and calculate.
|
||||
|
||||
```smart header="How to draw a curve *through* given points?"
|
||||
We use control points for a Bezier curve. As we can see, they are not on the curve, except the first and the last ones.
|
||||
|
||||
Sometimes we have another task: to draw a curve *through several points*, so that all of them are on a single smooth curve. That task is called [interpolation](https://en.wikipedia.org/wiki/Interpolation), and here we don't cover it.
|
||||
|
||||
There are mathematical formulas for such curves, for instance [Lagrange polynomial](https://en.wikipedia.org/wiki/Lagrange_polynomial). In computer graphics [spline interpolation](https://en.wikipedia.org/wiki/Spline_interpolation) is often used to build smooth curves that connect many points.
|
||||
```
|
||||
|
||||
|
||||
## Maths
|
||||
|
||||
A Bezier curve can be described using a mathematical formula.
|
||||
|
||||
As we saw -- there's actually no need to know it, most people just draw the curve by moving points with a mouse. But if you're into maths -- here it is.
|
||||
|
||||
Given the coordinates of control points <code>P<sub>i</sub></code>: the first control point has coordinates <code>P<sub>1</sub> = (x<sub>1</sub>, y<sub>1</sub>)</code>, the second: <code>P<sub>2</sub> = (x<sub>2</sub>, y<sub>2</sub>)</code>, and so on, the curve coordinates are described by the equation that depends on the parameter `t` from the segment `[0,1]`.
|
||||
|
||||
- The formula for a 2-points curve:
|
||||
|
||||
<code>P = (1-t)P<sub>1</sub> + tP<sub>2</sub></code>
|
||||
- For 3 control points:
|
||||
|
||||
<code>P = (1−t)<sup>2</sup>P<sub>1</sub> + 2(1−t)tP<sub>2</sub> + t<sup>2</sup>P<sub>3</sub></code>
|
||||
- For 4 control points:
|
||||
|
||||
<code>P = (1−t)<sup>3</sup>P<sub>1</sub> + 3(1−t)<sup>2</sup>tP<sub>2</sub> +3(1−t)t<sup>2</sup>P<sub>3</sub> + t<sup>3</sup>P<sub>4</sub></code>
|
||||
|
||||
|
||||
These are vector equations. In other words, we can put `x` and `y` instead of `P` to get corresponding coordinates.
|
||||
|
||||
For instance, the 3-point curve is formed by points `(x,y)` calculated as:
|
||||
|
||||
- <code>x = (1−t)<sup>2</sup>x<sub>1</sub> + 2(1−t)tx<sub>2</sub> + t<sup>2</sup>x<sub>3</sub></code>
|
||||
- <code>y = (1−t)<sup>2</sup>y<sub>1</sub> + 2(1−t)ty<sub>2</sub> + t<sup>2</sup>y<sub>3</sub></code>
|
||||
|
||||
Instead of <code>x<sub>1</sub>, y<sub>1</sub>, x<sub>2</sub>, y<sub>2</sub>, x<sub>3</sub>, y<sub>3</sub></code> we should put coordinates of 3 control points, and then as `t` moves from `0` to `1`, for each value of `t` we'll have `(x,y)` of the curve.
|
||||
|
||||
For instance, if control points are `(0,0)`, `(0.5, 1)` and `(1, 0)`, the equations become:
|
||||
|
||||
- <code>x = (1−t)<sup>2</sup> * 0 + 2(1−t)t * 0.5 + t<sup>2</sup> * 1 = (1-t)t + t<sup>2</sup> = t</code>
|
||||
- <code>y = (1−t)<sup>2</sup> * 0 + 2(1−t)t * 1 + t<sup>2</sup> * 0 = 2(1-t)t = –t<sup>2</sup> + 2t</code>
|
||||
|
||||
Now as `t` runs from `0` to `1`, the set of values `(x,y)` for each `t` forms the curve for such control points.
|
||||
|
||||
## Summary
|
||||
|
||||
Bezier curves are defined by their control points.
|
||||
|
||||
We saw two definitions of Bezier curves:
|
||||
|
||||
1. Using a mathematical formulas.
|
||||
2. Using a drawing process: De Casteljau's algorithm
|
||||
|
||||
Good properties of Bezier curves:
|
||||
|
||||
- We can draw smooth lines with a mouse by moving around control points.
|
||||
- Complex shapes can be made of several Bezier curves.
|
||||
|
||||
Usage:
|
||||
|
||||
- In computer graphics, modeling, vector graphic editors. Fonts are described by Bezier curves.
|
||||
- In web development -- for graphics on Canvas and in the SVG format. By the way, "live" examples above are written in SVG. They are actually a single SVG document that is given different points as parameters. You can open it in a separate window and see the source: [demo.svg](demo.svg?p=0,0,1,0.5,0,0.5,1,1&animate=1).
|
||||
- In CSS animation to describe the path and speed of animation.
|
BIN
7-animation/1-bezier-curve/bezier-car.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
7-animation/1-bezier-curve/bezier-car@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
7-animation/1-bezier-curve/bezier-letter.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
7-animation/1-bezier-curve/bezier-letter@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
7-animation/1-bezier-curve/bezier-vase.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
7-animation/1-bezier-curve/bezier-vase@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
7-animation/1-bezier-curve/bezier2.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
7-animation/1-bezier-curve/bezier2@2x.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
7-animation/1-bezier-curve/bezier3-draw1.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
7-animation/1-bezier-curve/bezier3-draw1@2x.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
7-animation/1-bezier-curve/bezier3-draw2.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
7-animation/1-bezier-curve/bezier3-draw2@2x.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
7-animation/1-bezier-curve/bezier3-e.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
7-animation/1-bezier-curve/bezier3-e@2x.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
7-animation/1-bezier-curve/bezier3.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
7-animation/1-bezier-curve/bezier3@2x.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
7-animation/1-bezier-curve/bezier4-e.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
7-animation/1-bezier-curve/bezier4-e@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
7-animation/1-bezier-curve/bezier4.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
7-animation/1-bezier-curve/bezier4@2x.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
1
7-animation/1-bezier-curve/demo.svg
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
7-animation/1-bezier-curve/pause.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
7-animation/1-bezier-curve/play.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
17
7-animation/2-css-animations/1-animate-logo-css/solution.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
CSS to animate both `width` and `height`:
|
||||
```css
|
||||
/* original class */
|
||||
|
||||
#flyjet {
|
||||
transition: all 3s;
|
||||
}
|
||||
|
||||
/* JS adds .growing */
|
||||
#flyjet.growing {
|
||||
width: 400px;
|
||||
height: 240px;
|
||||
}
|
||||
```
|
||||
|
||||
Please note that `transitionend` triggers two times -- once for every property. So if we don't perform an additional check then the message would show up 2 times.
|
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
img {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
#flyjet {
|
||||
width: 40px;
|
||||
height: 24px;
|
||||
transition: all 3s;
|
||||
}
|
||||
|
||||
#flyjet.growing {
|
||||
width: 400px;
|
||||
height: 240px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img id="flyjet" src="https://en.js.cx/clipart/flyjet.jpg">
|
||||
|
||||
<script>
|
||||
flyjet.onclick = function() {
|
||||
|
||||
let ended = false;
|
||||
|
||||
flyjet.addEventListener('transitionend', function() {
|
||||
if (!ended) {
|
||||
ended = true;
|
||||
alert('Done!');
|
||||
}
|
||||
});
|
||||
|
||||
flyjet.classList.add('growing');
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
img {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
#flyjet {
|
||||
width: 40px;
|
||||
/* -> 400px */
|
||||
|
||||
height: 24px;
|
||||
/* -> 240px */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img id="flyjet" src="https://en.js.cx/clipart/flyjet.jpg">
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
14
7-animation/2-css-animations/1-animate-logo-css/task.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Animate a plane (CSS)
|
||||
|
||||
Show the animation like on the picture below (click the plane):
|
||||
|
||||
[iframe src="solution" height=300]
|
||||
|
||||
- The picture grows on click from `40x24px` to `400x240px` (10 times larger).
|
||||
- The animation takes 3 seconds.
|
||||
- At the end output: "Done!".
|
||||
- During the animation process, there may be more clicks on the plane. They shouldn't "break" anything.
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,7 @@
|
|||
We need to choose the right Bezier curve for that animation. It should have `y>1` somewhere for the plane to "jump out".
|
||||
|
||||
For instance, we can take both control points with `y>1`, like: `cubic-bezier(0.25, 1.5, 0.75, 1.5)`.
|
||||
|
||||
The graph:
|
||||
|
||||

|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
img {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
#flyjet {
|
||||
width: 40px;
|
||||
height: 24px;
|
||||
transition: all 3s cubic-bezier(0.25, 1.5, 0.75, 1.5);
|
||||
}
|
||||
|
||||
#flyjet.growing {
|
||||
width: 400px;
|
||||
height: 240px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img id="flyjet" src="https://en.js.cx/clipart/flyjet.jpg">
|
||||
|
||||
<script>
|
||||
flyjet.onclick = function() {
|
||||
flyjet.classList.add('growing');
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Animate the flying plane (CSS)
|
||||
|
||||
Modify the solution of the previous task <info:task/animate-logo-css> to make the plane grow more than it's original size 400x240px (jump out), and then return to that size.
|
||||
|
||||
Here's how it should look (click on the plane):
|
||||
|
||||
[iframe src="solution" height=350]
|
||||
|
||||
Take the solution of the previous task as the source.
|
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
.circle {
|
||||
transition-property: width, height, margin-left, margin-top;
|
||||
transition-duration: 2s;
|
||||
position: fixed;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
background-color: red;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<button onclick="showCircle(150, 150, 100)">showCircle(150, 150, 100)</button>
|
||||
|
||||
<script>
|
||||
function showCircle(cx, cy, radius) {
|
||||
let div = document.createElement('div');
|
||||
div.style.width = 0;
|
||||
div.style.height = 0;
|
||||
div.style.left = cx + 'px';
|
||||
div.style.top = cy + 'px';
|
||||
div.className = 'circle';
|
||||
document.body.append(div);
|
||||
|
||||
setTimeout(() => {
|
||||
div.style.width = radius * 2 + 'px';
|
||||
div.style.height = radius * 2 + 'px';
|
||||
}, 0);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
.circle {
|
||||
transition-property: width, height, margin-left, margin-top;
|
||||
transition-duration: 2s;
|
||||
position: fixed;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
background-color: red;
|
||||
border-radius: 50%;
|
||||
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: 150px;
|
||||
left: 150px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="circle"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
16
7-animation/2-css-animations/3-animate-circle/task.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Animated circle
|
||||
|
||||
Create a function `showCircle(cx, cy, radius)` that shows an animated growing circle.
|
||||
|
||||
- `cx,cy` are window-relative coordinates of the center of the circle,
|
||||
- `radius` is the radius of the circle.
|
||||
|
||||
Click the button below to see how it should look like:
|
||||
|
||||
[iframe src="solution" height=260]
|
||||
|
||||
The source document has an example of a circle with right styles, so the task is precisely to do the animation right.
|
427
7-animation/2-css-animations/article.md
Normal file
|
@ -0,0 +1,427 @@
|
|||
# CSS-animations
|
||||
|
||||
CSS animations allow to do simple animations without JavaScript at all.
|
||||
|
||||
JavaScript can be used to control CSS animation and make it even better with a little of 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 fluent transition is made 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 to declare them together in the order: `property duration timing-function delay`, and also animate 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 property to animate, for instance: `left`, `margin-left`, `height`, `color`.
|
||||
|
||||
Not all properties can be animated, but [many of them](http://www.w3.org/TR/css3-transitions/#animatable-properties-). The value `all` means "animate all 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: 1s`, then animation starts after 1 second after the change.
|
||||
|
||||
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 half.
|
||||
|
||||
Here's 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 can also start it "from the middle", from the exact number, e.g. corresponding to the current second, using the 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 by 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
|
||||
|
||||
Timing function describes how the animation process is distributed along the time. Will it start slowly and then go fast or vise versa.
|
||||
|
||||
That's the most complicated property from the first sight. 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 from 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 satisfies the conditions:
|
||||
|
||||
1. First control point: `(0,0)`.
|
||||
2. Last control point: `(1,1)`.
|
||||
3. For intermediate points 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 in time.
|
||||
|
||||
- The `x` axis is the time: `0` -- the starting moment, `1` -- the last moment 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, we saw it already.
|
||||
|
||||
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 "jump out" of its range.**
|
||||
|
||||
The control points on the curve can have any `y` coordinates: even negative or huge. Then the Bezier curve would also jump 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 -- 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 put 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 lower than the starting `left` and `y>1` -- over 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 to make the Bezier curve for a specific task? There are many tools. For instance, we can do it on the site <http://cubic-bezier.com/>.
|
||||
|
||||
### Steps
|
||||
|
||||
Timing function `steps(number of steps[, start/end])` allows to split 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 `step(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 swim there and back on click, 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 when the transition finishes and flips the direction:
|
||||
|
||||
```js
|
||||
boat.onclick = function() {
|
||||
//...
|
||||
let times = 1;
|
||||
|
||||
function go() {
|
||||
if (times % 2) {
|
||||
// swim to the right
|
||||
boat.classList.remove('back');
|
||||
boat.style.marginLeft = 100 * times + 200 + 'px';
|
||||
} else {
|
||||
// swim 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 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 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/).
|
||||
|
||||
Probably you won't need `@keyframes` often, unless everything is in the constant move on your sites.
|
||||
|
||||
## Summary
|
||||
|
||||
CSS animations allow to smoothly (or not) animate 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 for purposes of animation.
|
||||
```
|
||||
|
||||
The majority of animations can be implemented using CSS as described in this chapter. And `transitionend` event allows to run JavaScript 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.
|
BIN
7-animation/2-css-animations/bezier-linear.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
7-animation/2-css-animations/bezier-linear@2x.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
7-animation/2-css-animations/bezier-train-over.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
7-animation/2-css-animations/bezier-train-over@2x.png
Normal file
After Width: | Height: | Size: 26 KiB |
43
7-animation/2-css-animations/boat.view/index.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img src="https://js.cx/clipart/boat.png" id="boat">
|
||||
|
||||
<script>
|
||||
boat.onclick = function() {
|
||||
|
||||
this.onclick = null; // only the first click should start the animation
|
||||
|
||||
let times = 1;
|
||||
|
||||
function go() {
|
||||
if (times % 2) {
|
||||
boat.classList.remove('back');
|
||||
boat.style.marginLeft = 100 * times + 200 + 'px';
|
||||
} else {
|
||||
boat.classList.add('back');
|
||||
boat.style.marginLeft = 100 * times - 200 + 'px';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
go();
|
||||
|
||||
boat.addEventListener('transitionend', function() {
|
||||
times++;
|
||||
go();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
11
7-animation/2-css-animations/boat.view/style.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
#boat {
|
||||
margin-left: 0;
|
||||
cursor: pointer;
|
||||
transition: margin-left 3s ease-in-out;
|
||||
}
|
||||
|
||||
/* flipping the picture with CSS */
|
||||
.back {
|
||||
transform: scaleX(-1);
|
||||
filter: fliph;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Click below to animate:
|
||||
<div id="digit"><div id="stripe">0123456789</div></div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
stripe.onclick = function() {
|
||||
let sec = new Date().getSeconds() % 10;
|
||||
stripe.style.transitionDelay = '-' + sec + 's';
|
||||
stripe.classList.add('animate');
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
#digit {
|
||||
width: .5em;
|
||||
overflow: hidden;
|
||||
font: 32px monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#stripe {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
#stripe.animate {
|
||||
transform: translate(-90%);
|
||||
transition-property: transform;
|
||||
transition-duration: 9s;
|
||||
transition-timing-function: linear;
|
||||
}
|
18
7-animation/2-css-animations/digits.view/index.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Click below to animate:
|
||||
|
||||
<div id="digit"><div id="stripe">0123456789</div></div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
3
7-animation/2-css-animations/digits.view/script.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
stripe.onclick = function() {
|
||||
stripe.classList.add('animate');
|
||||
};
|
17
7-animation/2-css-animations/digits.view/style.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
#digit {
|
||||
width: .5em;
|
||||
overflow: hidden;
|
||||
font: 32px monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#stripe {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
#stripe.animate {
|
||||
transform: translate(-90%);
|
||||
transition-property: transform;
|
||||
transition-duration: 9s;
|
||||
transition-timing-function: linear;
|
||||
}
|
BIN
7-animation/2-css-animations/ease-in-out.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
7-animation/2-css-animations/ease-in-out@2x.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
7-animation/2-css-animations/ease-in.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
7-animation/2-css-animations/ease-in@2x.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
7-animation/2-css-animations/ease-out.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
7-animation/2-css-animations/ease-out@2x.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
7-animation/2-css-animations/ease.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
7-animation/2-css-animations/ease@2x.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
24
7-animation/2-css-animations/step-end.view/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Click below to animate:
|
||||
|
||||
<div id="digit"><div id="stripe">0123456789</div></div>
|
||||
|
||||
<script>
|
||||
digit.onclick = function() {
|
||||
stripe.classList.add('animate');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
17
7-animation/2-css-animations/step-end.view/style.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
#digit {
|
||||
width: .5em;
|
||||
overflow: hidden;
|
||||
font: 32px monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#stripe {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
#stripe.animate {
|
||||
transform: translate(-90%);
|
||||
transition-property: transform;
|
||||
transition-duration: 9s;
|
||||
transition-timing-function: steps(9, end);
|
||||
}
|
14
7-animation/2-css-animations/step-list.view/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="digit"><div id="stripe">0123456789</div></div>
|
||||
|
||||
</body>
|
||||
</html>
|
9
7-animation/2-css-animations/step-list.view/style.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
#digit {
|
||||
border: 1px solid red;
|
||||
width: 1.2em;
|
||||
}
|
||||
|
||||
#stripe {
|
||||
display: inline-block;
|
||||
font: 32px monospace;
|
||||
}
|
24
7-animation/2-css-animations/step.view/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Click below to animate:
|
||||
|
||||
<div id="digit"><div id="stripe">0123456789</div></div>
|
||||
|
||||
<script>
|
||||
digit.onclick = function() {
|
||||
stripe.classList.add('animate');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
17
7-animation/2-css-animations/step.view/style.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
#digit {
|
||||
width: .5em;
|
||||
overflow: hidden;
|
||||
font: 32px monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#stripe {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
#stripe.animate {
|
||||
transform: translate(-90%);
|
||||
transition-property: transform;
|
||||
transition-duration: 9s;
|
||||
transition-timing-function: steps(9, start);
|
||||
}
|
BIN
7-animation/2-css-animations/train-curve.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
7-animation/2-css-animations/train-curve@2x.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
15
7-animation/2-css-animations/train-linear.view/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
8
7-animation/2-css-animations/train-linear.view/style.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.train {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 177px;
|
||||
height: 160px;
|
||||
left: 0;
|
||||
transition: left 5s cubic-bezier(0, 0, 1, 1);
|
||||
}
|
15
7-animation/2-css-animations/train-over.view/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='400px'">
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
8
7-animation/2-css-animations/train-over.view/style.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.train {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 177px;
|
||||
height: 160px;
|
||||
left: 100px;
|
||||
transition: left 5s cubic-bezier(.5, -1, .5, 2);
|
||||
}
|
15
7-animation/2-css-animations/train.view/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
8
7-animation/2-css-animations/train.view/style.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.train {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 177px;
|
||||
height: 160px;
|
||||
left: 0px;
|
||||
transition: left 5s cubic-bezier(0.0, 0.5, 0.5, 1.0);
|
||||
}
|
21
7-animation/3-js-animation/1-animate-ball/solution.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
To bounce we can use CSS property `top` and `position:absolute` for the ball inside the field with `position:relative`.
|
||||
|
||||
The bottom coordinate of the field is `field.clientHeight`. But the `top` property gives coordinates for the top of the ball, the edge position is `field.clientHeight - ball.clientHeight`.
|
||||
|
||||
So we animate the `top` from `0` to `field.clientHeight - ball.clientHeight`.
|
||||
|
||||
Now to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode.
|
||||
|
||||
Here's the final code for the animation:
|
||||
|
||||
```js
|
||||
let to = field.clientHeight - ball.clientHeight;
|
||||
|
||||
animate({
|
||||
duration: 2000,
|
||||
timing: makeEaseOut(bounce),
|
||||
draw(progress) {
|
||||
ball.style.top = to * progress + 'px'
|
||||
}
|
||||
});
|
||||
```
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script src="https://js.cx/libs/animate.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="field">
|
||||
<img src="https://js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function makeEaseOut(timing) {
|
||||
return function(timeFraction) {
|
||||
return 1 - timing(1 - timeFraction);
|
||||
}
|
||||
}
|
||||
|
||||
function bounce(timeFraction) {
|
||||
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
|
||||
if (timeFraction >= (7 - 4 * a) / 11) {
|
||||
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ball.onclick = function() {
|
||||
|
||||
let to = field.clientHeight - ball.clientHeight;
|
||||
|
||||
animate({
|
||||
duration: 2000,
|
||||
timing: makeEaseOut(bounce),
|
||||
draw(progress) {
|
||||
ball.style.top = to * progress + 'px'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
#field {
|
||||
height: 200px;
|
||||
border-bottom: 3px black groove;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#ball {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script src="https://en.js.cx/libs/animate.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="field">
|
||||
<img src="https://en.js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
#field {
|
||||
height: 200px;
|
||||
border-bottom: 3px black groove;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#ball {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
9
7-animation/3-js-animation/1-animate-ball/task.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Animate the bouncing ball
|
||||
|
||||
Make a bouncing ball. Click to see how it should look:
|
||||
|
||||
[iframe height=250 src="solution"]
|
32
7-animation/3-js-animation/2-animate-ball-hops/solution.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
In the task <info:task/animate-ball> we had only one property to animate. Now we need one more: `elem.style.left`.
|
||||
|
||||
The horizontal coordinate changes by another law: it does not "bounce", but gradually increases shifting the ball to the right.
|
||||
|
||||
We can write one more `animate` for it.
|
||||
|
||||
As the time function we could use `linear`, but something like `makeEaseOut(quad)` looks much better.
|
||||
|
||||
The code:
|
||||
|
||||
```js
|
||||
let height = field.clientHeight - ball.clientHeight;
|
||||
let width = 100;
|
||||
|
||||
// animate top (bouncing)
|
||||
animate({
|
||||
duration: 2000,
|
||||
timing: makeEaseOut(bounce),
|
||||
draw: function(progress) {
|
||||
ball.style.top = height * progress + 'px'
|
||||
}
|
||||
});
|
||||
|
||||
// animate left (moving to the right)
|
||||
animate({
|
||||
duration: 2000,
|
||||
timing: makeEaseOut(quad),
|
||||
draw: function(progress) {
|
||||
ball.style.left = width * progress + "px"
|
||||
}
|
||||
});
|
||||
```
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script src="https://js.cx/libs/animate.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="field">
|
||||
<img src="https://js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function makeEaseOut(timing) {
|
||||
return function(timeFraction) {
|
||||
return 1 - timing(1 - timeFraction);
|
||||
}
|
||||
}
|
||||
|
||||
function bounce(timeFraction) {
|
||||
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
|
||||
if (timeFraction >= (7 - 4 * a) / 11) {
|
||||
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function quad(timeFraction) {
|
||||
return Math.pow(timeFraction, 2);
|
||||
}
|
||||
|
||||
ball.onclick = function() {
|
||||
|
||||
let height = field.clientHeight - ball.clientHeight;
|
||||
let width = 100;
|
||||
|
||||
animate({
|
||||
duration: 2000,
|
||||
timing: makeEaseOut(bounce),
|
||||
draw: function(progress) {
|
||||
ball.style.top = height * progress + 'px'
|
||||
}
|
||||
});
|
||||
|
||||
animate({
|
||||
duration: 2000,
|
||||
timing: makeEaseOut(quad),
|
||||
draw: function(progress) {
|
||||
ball.style.left = width * progress + "px"
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
#field {
|
||||
height: 200px;
|
||||
border-bottom: 3px black groove;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#ball {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
13
7-animation/3-js-animation/2-animate-ball-hops/task.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Animate the ball bouncing to the right
|
||||
|
||||
Make the ball bounce to the right. Like this:
|
||||
|
||||
[iframe height=250 src="solution"]
|
||||
|
||||
Write the animation code. The distance to the left is `100px`.
|
||||
|
||||
Take the solution of the previous task <info:task/animate-ball> as the source.
|
455
7-animation/3-js-animation/article.md
Normal file
|
@ -0,0 +1,455 @@
|
|||
# JavaScript animations
|
||||
|
||||
JavaScript animations can handle things that CSS can't.
|
||||
|
||||
For instance, moving along a complex path, with a timing function different from Bezier curves, or an animation on a canvas.
|
||||
|
||||
## Using setInterval
|
||||
|
||||
An animation can be implemented as a sequence of frames -- usually small changes to HTML/CSS properties.
|
||||
|
||||
For instance, changing `style.left` from `0px` to `100px` moves the element. And if we increase it in `setInterval`, changing by `2px` with a tiny delay, like 50 times per second, then it looks smooth. That's the same principle as in the cinema: 24 or more frames per second is enough to make it look smooth.
|
||||
|
||||
The pseudo-code can look like this:
|
||||
|
||||
```js
|
||||
let timer = setInterval(function() {
|
||||
if (animation complete) clearInterval(timer);
|
||||
else increase style.left by 2px
|
||||
}, 20); // change by 2px every 20ms, about 50 frames per second
|
||||
```
|
||||
|
||||
More complete example of the animation:
|
||||
|
||||
```js
|
||||
let start = Date.now(); // remember start time
|
||||
|
||||
let timer = setInterval(function() {
|
||||
// how much time passed from the start?
|
||||
let timePassed = Date.now() - start;
|
||||
|
||||
if (timePassed >= 2000) {
|
||||
clearInterval(timer); // finish the animation after 2 seconds
|
||||
return;
|
||||
}
|
||||
|
||||
// draw the animation at the moment timePassed
|
||||
draw(timePassed);
|
||||
|
||||
}, 20);
|
||||
|
||||
// as timePassed goes from 0 to 2000
|
||||
// left gets values from 0px to 400px
|
||||
function draw(timePassed) {
|
||||
train.style.left = timePassed / 5 + 'px';
|
||||
}
|
||||
```
|
||||
|
||||
Click for the demo:
|
||||
|
||||
[codetabs height=200 src="move"]
|
||||
|
||||
## Using requestAnimationFrame
|
||||
|
||||
Let's imagine we have several animations running simultaneously.
|
||||
|
||||
If we run them separately, then even though each one has `setInterval(..., 20)`, then the browser would have to repaint much more often than every `20ms`.
|
||||
|
||||
That's because they have different starting time, so "every 20ms" differs between different animations. The intervals are not alignned. So we'll have several independent runs within `20ms`.
|
||||
|
||||
In other words, this:
|
||||
|
||||
```js
|
||||
setInterval(function() {
|
||||
animate1();
|
||||
animate2();
|
||||
animate3();
|
||||
}, 20)
|
||||
```
|
||||
|
||||
...Is lighter than three independent calls:
|
||||
|
||||
```js
|
||||
setInterval(animate1, 20); // independent animations
|
||||
setInterval(animate2, 20); // in different places of the script
|
||||
setInterval(animate3, 20);
|
||||
```
|
||||
|
||||
These several independent redraws should be grouped together, to make the redraw easier for the browser (and hence smoother for people).
|
||||
|
||||
There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`.
|
||||
|
||||
But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more.
|
||||
|
||||
The syntax:
|
||||
```js
|
||||
let requestId = requestAnimationFrame(callback)
|
||||
```
|
||||
|
||||
That schedules the `callback` function to run in the closest time when the browser wants to do animation.
|
||||
|
||||
If we do changes in elements in `callback` then they will be grouped together with other `requestAnimationFrame` callbacks and with CSS animations. So there will be one geometry recalculation and repaint instead of many.
|
||||
|
||||
The returned value `requestId` can be used to cancel the call:
|
||||
```js
|
||||
// cancel the scheduled execution of callback
|
||||
cancelAnimationFrame(requestId);
|
||||
```
|
||||
|
||||
The `callback` gets one argument -- the time passed from the beginning of the page load in microseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now).
|
||||
|
||||
Usually `callback` runs very soon, unless the CPU is overloaded or the laptop battery is almost discharged, or there's another reason.
|
||||
|
||||
The code below shows the time between first 10 runs for `requestAnimationFrame`. Usually it's 10-20ms:
|
||||
|
||||
```html run height=40 refresh
|
||||
<script>
|
||||
let prev = performance.now();
|
||||
let times = 0;
|
||||
|
||||
requestAnimationFrame(function measure(time) {
|
||||
document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " ");
|
||||
prev = time;
|
||||
|
||||
if (times++ < 10) requestAnimationFrame(measure);
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Structured animation
|
||||
|
||||
Now we can make a more universal animation function based on `requestAnimationFrame`:
|
||||
|
||||
```js
|
||||
function animate({timing, draw, duration}) {
|
||||
|
||||
let start = performance.now();
|
||||
|
||||
requestAnimationFrame(function animate(time) {
|
||||
// timeFraction goes from 0 to 1
|
||||
let timeFraction = (time - start) / duration;
|
||||
if (timeFraction > 1) timeFraction = 1;
|
||||
|
||||
// calculate the current animation state
|
||||
let progress = timing(timeFraction)
|
||||
|
||||
draw(progress); // draw it
|
||||
|
||||
if (timeFraction < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Function `animate` accepts 3 parameters that essentially describes the animation:
|
||||
|
||||
`duration`
|
||||
: Total time of animation. Like, `1000`.
|
||||
|
||||
`timing(timeFraction)`
|
||||
: Timing function, like CSS-property `transition-timing-function` that gets the fraction of time that passed (`0` at start, `1` at the end) and returns the animation completion (like `y` on the Bezier curve).
|
||||
|
||||
For instance, a linear function means that the animation goes on uniformly with the same speed:
|
||||
|
||||
```js
|
||||
function linear(timeFraction) {
|
||||
return timeFraction;
|
||||
}
|
||||
```
|
||||
|
||||
It's graph:
|
||||

|
||||
|
||||
That's just like `transition-timing-function: linear`. There are more interesting variants shown below.
|
||||
|
||||
`draw(progress)`
|
||||
: The function that takes the animation completion state and draws it. The value `progress=0` denotes the beginning animation state, and `progress=1` -- the end state.
|
||||
|
||||
This is that function that actually draws out the animation.
|
||||
|
||||
It can move the element:
|
||||
```js
|
||||
function draw(progress) {
|
||||
train.style.left = progress + 'px';
|
||||
}
|
||||
```
|
||||
|
||||
...Or do anything else, we can animate anything, in any way.
|
||||
|
||||
|
||||
Let's animate the element `width` from `0` to `100%` using our function.
|
||||
|
||||
Click on the element for the demo:
|
||||
|
||||
[codetabs height=60 src="width"]
|
||||
|
||||
The code for it:
|
||||
|
||||
```js
|
||||
animate({
|
||||
duration: 1000,
|
||||
timing(timeFraction) {
|
||||
return timeFraction;
|
||||
},
|
||||
draw(progress) {
|
||||
elem.style.width = progress * 100 + '%';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Unlike CSS animation, we can make any timing function and any drawing function here. The timing function is not limited by Bezier curves. And `draw` can go beyond properties, create new elements for like fireworks animation or something.
|
||||
|
||||
## Timing functions
|
||||
|
||||
We saw the simplest, linear timing function above.
|
||||
|
||||
Let's see more of them. We'll try movement animations with different timing functions to see how they work.
|
||||
|
||||
### Power of n
|
||||
|
||||
If we want to speed up the animation, we can use `progress` in the power `n`.
|
||||
|
||||
For instance, a parabolic curve:
|
||||
|
||||
```js
|
||||
function quad(timeFraction) {
|
||||
return Math.pow(timeFraction, 2)
|
||||
}
|
||||
```
|
||||
|
||||
The graph:
|
||||
|
||||

|
||||
|
||||
See in action (click to activate):
|
||||
|
||||
[iframe height=40 src="quad" link]
|
||||
|
||||
...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster.
|
||||
|
||||
Here's the graph for `progress` in the power `5`:
|
||||
|
||||

|
||||
|
||||
In action:
|
||||
|
||||
[iframe height=40 src="quint" link]
|
||||
|
||||
### The arc
|
||||
|
||||
Function:
|
||||
|
||||
```js
|
||||
function circ(timeFraction) {
|
||||
return 1 - Math.sin(Math.acos(timeFraction));
|
||||
}
|
||||
```
|
||||
|
||||
The graph:
|
||||
|
||||

|
||||
|
||||
[iframe height=40 src="circ" link]
|
||||
|
||||
### Back: bow shooting
|
||||
|
||||
This function does the "bow shooting". First we "pull the bowstring", and then "shoot".
|
||||
|
||||
Unlike previous functions, it depends on an additional parameter `x`, the "elasticity coefficient". The distance of "bowstring pulling" is defined by it.
|
||||
|
||||
The code:
|
||||
|
||||
```js
|
||||
function back(x, timeFraction) {
|
||||
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
|
||||
}
|
||||
```
|
||||
|
||||
**The graph for `x = 1.5`:**
|
||||
|
||||

|
||||
|
||||
For animation we use it with a specific value of `x`. Example for `x = 1.5`:
|
||||
|
||||
[iframe height=40 src="back" link]
|
||||
|
||||
### Bounce
|
||||
|
||||
Imagine we are dropping a ball. It falls down, then bounces back a few times and stops.
|
||||
|
||||
The `bounce` function does the same, but in the reverse order: "bouncing" starts immediately. It uses few special coefficients for that:
|
||||
|
||||
```js
|
||||
function bounce(timeFraction) {
|
||||
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
|
||||
if (timeFraction >= (7 - 4 * a) / 11) {
|
||||
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In action:
|
||||
|
||||
[iframe height=40 src="bounce" link]
|
||||
|
||||
### Elastic animation
|
||||
|
||||
One more "elastic" function that accepts an additional parameter `x` for the "initial range".
|
||||
|
||||
```js
|
||||
function elastic(x, timeFraction) {
|
||||
return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction)
|
||||
}
|
||||
```
|
||||
|
||||
**The graph for `x=1.5`:**
|
||||

|
||||
|
||||
In action for `x=1.5`:
|
||||
|
||||
[iframe height=40 src="elastic" link]
|
||||
|
||||
## Reversal: ease*
|
||||
|
||||
So we have a collection of timing functions. Their direct application is called "easeIn".
|
||||
|
||||
Sometimes we need to show the animation in the reverse order. That's done with the "easeOut" transform.
|
||||
|
||||
### easeOut
|
||||
|
||||
In the "easeOut" mode the `timing` function is put into a wrapper `timingEaseOut`:
|
||||
|
||||
```js
|
||||
timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction)
|
||||
```
|
||||
|
||||
In other words, we have a "transform" function `makeEaseOut` that takes a "regular" timing function and returns the wrapper around it:
|
||||
|
||||
```js
|
||||
// accepts a timing function, returns the transformed variant
|
||||
function makeEaseOut(timing) {
|
||||
return function(timeFraction) {
|
||||
return 1 - timing(1 - timeFraction);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For instance, we can take the `bounce` function described above and apply it:
|
||||
|
||||
```js
|
||||
let bounceEaseOut = makeEaseOut(bounce);
|
||||
```
|
||||
|
||||
Then the bounce will be not in the beginning, but at the end of the animation. Looks even better:
|
||||
|
||||
[codetabs src="bounce-easeout"]
|
||||
|
||||
Here we can see how the transform changes the behavior of the function:
|
||||
|
||||

|
||||
|
||||
If there's an animation effect in the beginning, like bouncing -- it will be shown at the end.
|
||||
|
||||
In the graph above the <span style="color:#EE6B47">regular bounce</span> has the red color, and the <span style="color:#62C0DC">easeOut bounce</span> is blue.
|
||||
|
||||
- Regular bounce -- the object bounces at the bottom, then at the end sharply jumps to the top.
|
||||
- After `easeOut` -- it first jumps to the top, then bounces there.
|
||||
|
||||
### easeInOut
|
||||
|
||||
We also can show the effect both in the beginning and the end of the animation. The transform is called "easeInOut".
|
||||
|
||||
Given the timing function, we calculate the animation state like this:
|
||||
|
||||
```js
|
||||
if (timeFraction <= 0.5) { // first half of the animation
|
||||
return timing(2 * timeFraction) / 2;
|
||||
} else { // second half of the animation
|
||||
return (2 - timing(2 * (1 - timeFraction))) / 2;
|
||||
}
|
||||
```
|
||||
|
||||
The wrapper code:
|
||||
|
||||
```js
|
||||
function makeEaseInOut(timing) {
|
||||
return function(timeFraction) {
|
||||
if (timeFraction < .5)
|
||||
return timing(2 * timeFraction) / 2;
|
||||
else
|
||||
return (2 - timing(2 * (1 - timeFraction))) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
bounceEaseInOut = makeEaseInOut(bounce);
|
||||
```
|
||||
|
||||
In action, `bounceEaseInOut`:
|
||||
|
||||
[codetabs src="bounce-easeinout"]
|
||||
|
||||
The "easeInOut" transform joins two graphs into one: `easeIn` (regular) for the first half of the animation and `easeOut` (reversed) -- for the second part.
|
||||
|
||||
The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and `easeInOut` of the `circ` timing function:
|
||||
|
||||

|
||||
|
||||
- <span style="color:#EE6B47">Red</span> is the regular variantof `circ` (`easeIn`).
|
||||
- <span style="color:#8DB173">Green</span> -- `easeOut`.
|
||||
- <span style="color:#62C0DC">Blue</span> -- `easeInOut`.
|
||||
|
||||
As we can see, the graph of the first half of the animation is the scaled down `easeIn`, and the second half is the scaled down `easeOut`. As a result, the animation starts and finishes with the same effect.
|
||||
|
||||
## More interesting "draw"
|
||||
|
||||
Instead of moving the element we can do something else. All we need is to write the write the proper `draw`.
|
||||
|
||||
Here's the animated "bouncing" text typing:
|
||||
|
||||
[codetabs src="text"]
|
||||
|
||||
## Summary
|
||||
|
||||
For animations that CSS can't handle well, or those that need tight control, JavaScript can help. JavaScript animations should be implemented via `requestAnimationFrame`. That built-in method allows to setup a callback function to run when the browser will be preparing a repaint. Usually that's very soon, but the exact time depends on the browser.
|
||||
|
||||
When a page is in the background, there are no repaints at all, so the callback won't run: the animation will be suspended and won't consume resources. That's great.
|
||||
|
||||
Here's the helper `animate` function to setup most animations:
|
||||
|
||||
```js
|
||||
function animate({timing, draw, duration}) {
|
||||
|
||||
let start = performance.now();
|
||||
|
||||
requestAnimationFrame(function animate(time) {
|
||||
// timeFraction goes from 0 to 1
|
||||
let timeFraction = (time - start) / duration;
|
||||
if (timeFraction > 1) timeFraction = 1;
|
||||
|
||||
// calculate the current animation state
|
||||
let progress = timing(timeFraction);
|
||||
|
||||
draw(progress); // draw it
|
||||
|
||||
if (timeFraction < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `duration` -- the total animation time in ms.
|
||||
- `timing` -- the function to calculate animation progress. Gets a time fraction from 0 to 1, returns the animation progress, usually from 0 to 1.
|
||||
- `draw` -- the function to draw the animation.
|
||||
|
||||
Surely we could improve it, add more bells and whistles, but JavaScript animations are not applied on a daily basis. They are used to do something interesting and non-standard. So you'd want to add the features that you need when you need them.
|
||||
|
||||
JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here.
|
||||
|
||||
The same is about `draw`: we can animate anything, not just CSS properties.
|
BIN
7-animation/3-js-animation/back.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
34
7-animation/3-js-animation/back.view/index.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://js.cx/libs/animate.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="path">
|
||||
<div id="brick"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
brick.onclick = function() {
|
||||
animate({
|
||||
duration: 1000,
|
||||
timing: function back(x, timeFraction) {
|
||||
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
|
||||
}.bind(null, 1.5),
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
7-animation/3-js-animation/back.view/style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
#brick {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: #EE6B47;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#path {
|
||||
outline: 1px solid #E8C48E;
|
||||
width: 540px;
|
||||
height: 20px;
|
||||
}
|
BIN
7-animation/3-js-animation/back@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
7-animation/3-js-animation/bezier-linear.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
7-animation/3-js-animation/bezier-linear@2x.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
52
7-animation/3-js-animation/bounce-easeinout.view/index.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://js.cx/libs/animate.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="path">
|
||||
<div id="brick"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function makeEaseInOut(timing) {
|
||||
return function(timeFraction) {
|
||||
if (timeFraction < .5)
|
||||
return timing(2 * timeFraction) / 2;
|
||||
else
|
||||
return (2 - timing(2 * (1 - timeFraction))) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function bounce(timeFraction) {
|
||||
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
|
||||
if (timeFraction >= (7 - 4 * a) / 11) {
|
||||
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bounceEaseInOut = makeEaseInOut(bounce);
|
||||
|
||||
brick.onclick = function() {
|
||||
animate({
|
||||
duration: 3000,
|
||||
timing: bounceEaseInOut,
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
7-animation/3-js-animation/bounce-easeinout.view/style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
#brick {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: #EE6B47;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#path {
|
||||
outline: 1px solid #E8C48E;
|
||||
width: 540px;
|
||||
height: 20px;
|
||||
}
|
48
7-animation/3-js-animation/bounce-easeout.view/index.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://js.cx/libs/animate.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="path">
|
||||
<div id="brick"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function makeEaseOut(timing) {
|
||||
return function(timeFraction) {
|
||||
return 1 - timing(1 - timeFraction);
|
||||
}
|
||||
}
|
||||
|
||||
function bounce(timeFraction) {
|
||||
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
|
||||
if (timeFraction >= (7 - 4 * a) / 11) {
|
||||
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bounceEaseOut = makeEaseOut(bounce);
|
||||
|
||||
brick.onclick = function() {
|
||||
animate({
|
||||
duration: 3000,
|
||||
timing: bounceEaseOut,
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
7-animation/3-js-animation/bounce-easeout.view/style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
#brick {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: #EE6B47;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#path {
|
||||
outline: 1px solid #E8C48E;
|
||||
width: 540px;
|
||||
height: 20px;
|
||||
}
|
BIN
7-animation/3-js-animation/bounce-inout.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
7-animation/3-js-animation/bounce-inout@2x.png
Normal file
After Width: | Height: | Size: 28 KiB |
38
7-animation/3-js-animation/bounce.view/index.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://js.cx/libs/animate.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div id="path">
|
||||
<div id="brick"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
brick.onclick = function() {
|
||||
animate({
|
||||
duration: 3000,
|
||||
timing: function bounce(timeFraction) {
|
||||
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
|
||||
if (timeFraction >= (7 - 4 * a) / 11) {
|
||||
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
|
||||
}
|
||||
}
|
||||
},
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
7-animation/3-js-animation/bounce.view/style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
#brick {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: #EE6B47;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#path {
|
||||
outline: 1px solid #E8C48E;
|
||||
width: 540px;
|
||||
height: 20px;
|
||||
}
|
BIN
7-animation/3-js-animation/circ-ease.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
7-animation/3-js-animation/circ-ease@2x.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
7-animation/3-js-animation/circ.png
Normal file
After Width: | Height: | Size: 6.6 KiB |