components
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 |
34
7-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
7-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
7-animation/3-js-animation/circ@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
7-animation/3-js-animation/elastic.png
Normal file
After Width: | Height: | Size: 11 KiB |
34
7-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
7-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
7-animation/3-js-animation/elastic@2x.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
7-animation/3-js-animation/linear.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
7-animation/3-js-animation/linear@2x.png
Normal file
After Width: | Height: | Size: 13 KiB |
52
7-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
7-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
7-animation/3-js-animation/quad.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
34
7-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
7-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
7-animation/3-js-animation/quad@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
7-animation/3-js-animation/quint.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
34
7-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
7-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
7-animation/3-js-animation/quint@2x.png
Normal file
After Width: | Height: | Size: 14 KiB |
50
7-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
7-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
7-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
7-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>
|