Skip to content

Commit

Permalink
docs: add tweening section (motion-canvas#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
aarthificial authored Jan 6, 2023
1 parent 87666f5 commit 369ded5
Show file tree
Hide file tree
Showing 27 changed files with 370 additions and 63 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/player/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ export class Player {

private request() {
this.requestId = requestAnimationFrame(async time => {
if (time - this.renderTime >= 990 / this.state.current.fps) {
if (time - this.renderTime >= 1000 / (this.state.current.fps + 5)) {
this.renderTime = time;
try {
await this.run();
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/guides/getting-started/flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default makeScene2D(function* (view) {
```

Needless to say, it would be extremely cumbersome if we had to write all animations like that.
Fortunately, JavaScript has another keyword for use withing generators - `yield*`.
Fortunately, JavaScript has another keyword for use within generators - `yield*`.
It allows us to delegate the yielding to another generator.

For instance, we could extract the flickering code from the above example to
Expand Down
60 changes: 8 additions & 52 deletions packages/docs/docs/guides/getting-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ sidebar_position: 1
---

import AnimationPlayer from '@site/src/components/AnimationPlayer';
import CodeBlock from '@theme/CodeBlock';
import source from '!!raw-loader!@motion-canvas/examples/src/scenes/quickstart';

# Quickstart

Expand Down Expand Up @@ -69,32 +71,9 @@ going to focus on `src/scenes/example.tsx`, which is where we can add our
animations. Open `example.tsx` in a text editor, and replace all code in
the file with the following snippet.

```tsx title="src/scenes/example.tsx"
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {Circle} from '@motion-canvas/2d/lib/components';
import {useRef} from '@motion-canvas/core/lib/utils';
import {all} from '@motion-canvas/core/lib/flow';

export default makeScene2D(function* (view) {
const myCircle = useRef<Circle>();

view.add(
<Circle
//highlight-start
ref={myCircle}
x={-300}
width={240}
height={240}
fill="#e13238"
/>,
);

yield* all(
myCircle.value.fill('#e6a700', 1).to('#e13238', 1),
myCircle.value.position.x(300, 1).to(-300, 1),
);
});
```
<CodeBlock language="tsx" title="src/scenes/example.tsx">
{source}
</CodeBlock>

Now save the file. Any changes you make are automatically picked up and
reflected in the preview. You should see a gray circle in the preview pane at
Expand Down Expand Up @@ -299,32 +278,9 @@ Now they'll happen at the same time.

This brings us back to our initial example:

```tsx title="src/scenes/example.tsx"
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {Circle} from '@motion-canvas/2d/lib/components';
import {useRef} from '@motion-canvas/core/lib/utils';
import {all} from '@motion-canvas/core/lib/flow';

export default makeScene2D(function* (view) {
const myCircle = useRef<Circle>();

view.add(
<Circle
//highlight-start
ref={myCircle}
x={-300}
width={240}
height={240}
fill="#e13238"
/>,
);

yield* all(
myCircle.value.fill('#e6a700', 1).to('#e13238', 1),
myCircle.value.position.x(300, 1).to(-300, 1),
);
});
```
<CodeBlock language="tsx" title="src/scenes/example.tsx">
{source}
</CodeBlock>

[installation]: /guides/getting-started/installation
[generators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/guides/getting-started/time-events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
sidebar_position: 5
---

# Time Events
# Time Events
2 changes: 1 addition & 1 deletion packages/docs/docs/guides/getting-started/transitions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
sidebar_position: 6
---

# Transitions
# Transitions
179 changes: 178 additions & 1 deletion packages/docs/docs/guides/getting-started/tweening.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,181 @@
sidebar_position: 3
---

# Tweening
import AnimationPlayer from '@site/src/components/AnimationPlayer';
import CodeBlock from '@theme/CodeBlock';
import linearSource from '!!raw-loader!@motion-canvas/examples/src/scenes/tweening-linear';

# Tweening

Tweens are one of the fundamental building blocks of animation.
They are a special type of generators that animate between two values
over given time.

## `tween` function

The simplest way to create a tween is via the
[`tween`](/api/core/tweening#tween) function:

<CodeBlock language="tsx">{linearSource}</CodeBlock>

In the example above, we animate the x coordinate of our circle
from `-300` to `300` over a span of `2` second.

The [`tween`](/api/core/tweening#tween) function takes two parameters.
The first one specifies the tween duration in seconds. The second is
a callback function that will be called each frame the tween takes
place. The `value` parameter it recieves is a number ranging from `0`
to `1`, informing us about the progress of the tween. We can use it to
calculate the values that our tween animates. In the case of our circle,
we use the `map` function to map the `value` range from `[0, 1]` to
`[-300, 300]` and set it as the `x` coordinate:

<AnimationPlayer small name="tweening-linear" />

## Timing functions

At the moment, our animation feels a bit unnatural. The speed with which
the `value` parameter changes is constant, which in turn makes the circle
move with constant speed. In real life, however, objects have inertia -
they take time to speed up and slow down. We can simulate this behavior
with [timing functions](/api/core/tweening).

A timing function takes a number in the range `[0, 1]` and returns another
number in the same range but with a modified rate of change.
Motion Canvas provides all
[the most popular timing functions](https://easings.net/) (sometimes called
easing functions) but since it's a normal JavaScript function you can create
your own.

Let's use the [`easeInOutCubic`](/api/core/tweening#easeInOutCubic) function
to fix our animation:

```ts
yield* tween(2, value => {
circle.value.position.x(map(-300, 300, easeInOutCubic(value)));
});
```

<AnimationPlayer small name="tweening-cubic" />

`easeInOut` means that the object will speed up at the start (`in`) and slow
down at the end (`Out`). `Cubic` denotes the mathematical function used -
in this case it's a cubic equation. Knowing that, a function called
`easeOutQuad` would make the object start with full speed and then slow down
at the end using a quadratic equation.

Because using timing functions to map a range of values is a really common
pattern, it's possible to skip `map` entirely and pass the range to the
timing function itself:

```ts
// This:
map(-300, 300, easeInOutCubic(value));
// Can be simplified to:
easeInOutCubic(value, -300, 300);
```

## Interplation functions

So far, we've only animated a single, numeric value.
The [`map`](/api/core/tweening#map) function can be used to interpolate between
two numbers but to animate more complex types we'll need to use interpolation
functions. Consider the following example:

```ts
yield* tween(2, value => {
circle.value.color(
Color.lerp(
new Color('#e6a700'),
new Color('#e13238'),
easeInOutCubic(value),
),
);
});
```

[`Color.lerp`](/api/core/types/Color#lerp) is a static function that
interpolates between two colors:

<AnimationPlayer small name="tweening-color" />

:::tip

All [complex types](/api/core/types) in Motion Canvas provide a static method
called `lerp` that interpolates between two instances of said type.

:::

Aside from the default linear interpolation, some types offer more advanced
functions such as the [`Vector2.arcLerp`](/api/core/types/Vector2#arcLerp).
It makes the object follow a curved path from point a to b:

```ts
yield* tween(2, value => {
circle.value.position(
// highlight-next-line
Vector2.arcLerp(
new Vector2(-300, 200),
new Vector2(300, -200),
easeInOutCubic(value),
),
);
});
```

<AnimationPlayer small name="tweening-vector" />

## Tweening properties

The [`tween`](/api/core/tweening#tween) function is useful when we need to
orchestrate complex animations. However, there's a better way of tweening
individual properties. You may recall from the
[quickstart](/guides/getting-started/quickstart) section that the following
tween:

```ts
yield* tween(2, value => {
circle.value.color(
Color.lerp(
new Color('#e6a700'),
new Color('#e13238'),
easeInOutCubic(value),
),
);
});
```

Can be written as:

```ts
yield* circle.value.color('#e13238', 2);
```

Here, we use a [`SignalTween`](/api/core/utils/SignalTween) signature that
looks similar to a setter, except it accepts the transition duration as its
second argument. Under the hood, this will also create a tween - one that
starts with the current value and ends with the newly provided one.

By default, it will use `easeInOutCubic` as the timing function.
We can override that by providing a third argument:

```ts
yield* circle.value.color(
'#e13238',
2,
// highlight-next-line
easeOutQuad
);
```

Similarly, we can pass a custom interpolation function as the forth argument:

```ts
yield* circle.value.position(
new Vector2(300, -200),
2,
easeInOutCubic,
// highlight-next-line
Vector2.arcLerp
);
```
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function SignaturePreview({
) : isArrow ? (
''
) : (
<Token type="plain">{reflection.name}</Token>
<Token type="function">{reflection.name}</Token>
)}
{!!reflection.typeParameter?.length && (
<TokenList type={ListType.Angle}>
Expand Down
9 changes: 8 additions & 1 deletion packages/examples/src/scenes/quickstart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ export default makeScene2D(function* (view) {
const myCircle = useRef<Circle>();

view.add(
<Circle x={-300} ref={myCircle} width={240} height={240} fill="#e13238" />,
<Circle
//highlight-start
ref={myCircle}
x={-300}
width={240}
height={240}
fill="#e13238"
/>,
);

yield* all(
Expand Down
3 changes: 3 additions & 0 deletions packages/examples/src/scenes/tweening-color.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": 0
}
30 changes: 30 additions & 0 deletions packages/examples/src/scenes/tweening-color.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {Circle} from '@motion-canvas/2d/lib/components';
import {useRef} from '@motion-canvas/core/lib/utils';
import {tween, easeInOutCubic} from '@motion-canvas/core/lib/tweening';
import {Color} from '@motion-canvas/core/lib/types';

export default makeScene2D(function* (view) {
const circle = useRef<Circle>();

view.add(
<Circle
//highlight-start
ref={circle}
width={240}
height={240}
fill="#e13238"
/>,
);
//highlight-start
yield* tween(2, value => {
circle.value.fill(
Color.lerp(
new Color('#e13238'),
new Color('#e6a700'),
easeInOutCubic(value),
),
);
});
//highlight-end
});
3 changes: 3 additions & 0 deletions packages/examples/src/scenes/tweening-cubic.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": 0
}
24 changes: 24 additions & 0 deletions packages/examples/src/scenes/tweening-cubic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {Circle} from '@motion-canvas/2d/lib/components';
import {useRef} from '@motion-canvas/core/lib/utils';
import {tween, map, easeInOutCubic} from '@motion-canvas/core/lib/tweening';

export default makeScene2D(function* (view) {
const circle = useRef<Circle>();

view.add(
<Circle
//highlight-start
ref={circle}
x={-300}
width={240}
height={240}
fill="#e13238"
/>,
);
//highlight-start
yield* tween(2, value => {
circle.value.position.x(map(-300, 300, easeInOutCubic(value)));
});
//highlight-end
});
3 changes: 3 additions & 0 deletions packages/examples/src/scenes/tweening-linear.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": 0
}
Loading

0 comments on commit 369ded5

Please sign in to comment.