renames
21
3-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
3-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
3-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
3-animation/3-js-animation/2-animate-ball-hops/task.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Animate the ball bouncing to the left
|
||||
|
||||
Make the ball bounce to the left. Like this:
|
||||
|
||||
[iframe height=250 src="solution"]
|
||||
|
||||
Write the animation code. The distance to the right is `100px`.
|
||||
|
||||
Take the solution of the previous task <info:task/animate-ball> as the source.
|
460
3-animation/3-js-animation/article.md
Normal file
|
@ -0,0 +1,460 @@
|
|||
# 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.
|
||||
|
||||
[cut]
|
||||
|
||||
## setInterval
|
||||
|
||||
From the HTML/CSS point of view, an animation is a gradual change of the style property. For instance, changing `style.left` from `0px` to `100px` moves the element.
|
||||
|
||||
And if we increase it in `setInterval`, by making 50 small changes 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 fps = 50; // 50 frames per second
|
||||
let timer = setInterval(function() {
|
||||
if (animation complete) clearInterval(timer);
|
||||
else increase style.left
|
||||
}, 1000 / fps)
|
||||
```
|
||||
|
||||
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"]
|
||||
|
||||
## requestAnimationFrame
|
||||
|
||||
Let's imagine we have several simultaneous animations.
|
||||
|
||||
If we run them separately, each one with its own `setInterval(..., 20)`, then the browser would have to repaint much more often than every `20ms`.
|
||||
|
||||
Each `setInterval` triggers once per `20ms`, but they are independent, so we have several independent runs within `20ms`.
|
||||
|
||||
These several independent actions should be grouped together, because it's easier for the browser to redraw things once per `20ms`.
|
||||
|
||||
In other words, this:
|
||||
|
||||
```js
|
||||
setInterval(function() {
|
||||
animate1();
|
||||
animate2();
|
||||
animate3();
|
||||
}, 20)
|
||||
```
|
||||
|
||||
...Is lighter than this:
|
||||
|
||||
```js
|
||||
setInterval(animate1, 20);
|
||||
setInterval(animate2, 20);
|
||||
setInterval(animate3, 20);
|
||||
```
|
||||
|
||||
There's one more thing to keep in mind. Sometimes when CPU is overloaded or for other reasons it may be better to trigger redraws less often. Not 20, but maybe 200ms.
|
||||
|
||||
There's a standard [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`.
|
||||
|
||||
It addresses all those 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 20 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(progress) {
|
||||
return Math.pow(progress, 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
|
||||
|
||||
JavaScript animation 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
3-animation/3-js-animation/back.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
34
3-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
3-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
3-animation/3-js-animation/back@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
3-animation/3-js-animation/bezier-linear.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
3-animation/3-js-animation/bezier-linear@2x.png
Normal file
After Width: | Height: | Size: 7 KiB |
52
3-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
3-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
3-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
3-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
3-animation/3-js-animation/bounce-inout.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
3-animation/3-js-animation/bounce-inout@2x.png
Normal file
After Width: | Height: | Size: 28 KiB |
38
3-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
3-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
3-animation/3-js-animation/circ-ease.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
3-animation/3-js-animation/circ-ease@2x.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
3-animation/3-js-animation/circ.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
34
3-animation/3-js-animation/circ.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 circ(timeFraction) {
|
||||
return 1 - Math.sin(Math.acos(timeFraction))
|
||||
},
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
3-animation/3-js-animation/circ.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
3-animation/3-js-animation/circ@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
3-animation/3-js-animation/elastic.png
Normal file
After Width: | Height: | Size: 11 KiB |
34
3-animation/3-js-animation/elastic.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: 3000,
|
||||
timing: function elastic(x, timeFraction) {
|
||||
return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction)
|
||||
}.bind(null, 1.5),
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
3-animation/3-js-animation/elastic.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
3-animation/3-js-animation/elastic@2x.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
3-animation/3-js-animation/linear.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
3-animation/3-js-animation/linear@2x.png
Normal file
After Width: | Height: | Size: 13 KiB |
52
3-animation/3-js-animation/move-raf.view/index.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
#train {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img id="train" src="https://en.js.cx/clipart/train.gif">
|
||||
|
||||
<script>
|
||||
train.onclick = function() {
|
||||
animate(function(progress) {
|
||||
train.style.left = progress * 400 + 'px';
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
|
||||
function animate(draw, duration) {
|
||||
let start = performance.now();
|
||||
|
||||
requestAnimationFrame(function animate(time) {
|
||||
// how much time passed from the start?
|
||||
let timePassed = time - start;
|
||||
|
||||
if (timePassed > duration) timePassed = duration;
|
||||
|
||||
// progress is from 0 to 1, the fraction of time that passed
|
||||
let progress = duration / timePassed;
|
||||
|
||||
// draw the animation progress
|
||||
draw(progress);
|
||||
|
||||
// if time is not up - schedule one more run
|
||||
if (timePassed < duration) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
36
3-animation/3-js-animation/move.view/index.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
#train {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<img id="train" src="https://js.cx/clipart/train.gif">
|
||||
|
||||
|
||||
<script>
|
||||
train.onclick = function() {
|
||||
let start = Date.now();
|
||||
|
||||
let timer = setInterval(function() {
|
||||
let timePassed = Date.now() - start;
|
||||
|
||||
train.style.left = timePassed / 5 + 'px';
|
||||
|
||||
if (timePassed > 2000) clearInterval(timer);
|
||||
|
||||
}, 20);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
3-animation/3-js-animation/quad.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
34
3-animation/3-js-animation/quad.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(timeFraction) {
|
||||
return Math.pow(timeFraction, 2);
|
||||
},
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
3-animation/3-js-animation/quad.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
3-animation/3-js-animation/quad@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
3-animation/3-js-animation/quint.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
34
3-animation/3-js-animation/quint.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(timeFraction) {
|
||||
return Math.pow(timeFraction, 5);
|
||||
},
|
||||
draw: function(progress) {
|
||||
brick.style.left = progress * 500 + 'px';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
13
3-animation/3-js-animation/quint.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
3-animation/3-js-animation/quint@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
50
3-animation/3-js-animation/text.view/index.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<!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>
|
||||
|
||||
|
||||
<textarea id="textExample" rows="5" cols="60">He took his vorpal sword in hand:
|
||||
Long time the manxome foe he sought—
|
||||
So rested he by the Tumtum tree,
|
||||
And stood awhile in thought.
|
||||
</textarea>
|
||||
|
||||
<button onclick="animateText(textExample)">Run the animated typing!</button>
|
||||
|
||||
<script>
|
||||
function animateText(textArea) {
|
||||
let text = textArea.value;
|
||||
let to = text.length,
|
||||
from = 0;
|
||||
|
||||
animate({
|
||||
duration: 5000,
|
||||
timing: bounce,
|
||||
draw: function(progress) {
|
||||
let result = (to - from) * progress + from;
|
||||
textArea.value = text.substr(0, Math.ceil(result))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
10
3-animation/3-js-animation/text.view/style.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
textarea {
|
||||
display: block;
|
||||
border: 1px solid #BBB;
|
||||
color: #444;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 10px;
|
||||
}
|
18
3-animation/3-js-animation/width.view/animate.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
function animate({duration, draw, timing}) {
|
||||
|
||||
let start = performance.now();
|
||||
|
||||
requestAnimationFrame(function animate(time) {
|
||||
let timeFraction = (time - start) / duration;
|
||||
if (timeFraction > 1) timeFraction = 1;
|
||||
|
||||
let progress = timing(timeFraction)
|
||||
|
||||
draw(progress);
|
||||
|
||||
if (timeFraction < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
36
3-animation/3-js-animation/width.view/index.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
progress {
|
||||
width: 5%;
|
||||
}
|
||||
</style>
|
||||
<script src="animate.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<progress id="elem"></progress>
|
||||
|
||||
<script>
|
||||
elem.onclick = function() {
|
||||
animate({
|
||||
duration: 1000,
|
||||
timing: function(timeFraction) {
|
||||
return timeFraction;
|
||||
},
|
||||
draw: function(progress) {
|
||||
elem.style.width = progress * 100 + '%';
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|