# Animation

animate() is the timeline entry point. animateIn() is the intro. Use generator functions, yield*, all, and tween. Keep fontSize a positive constant.

Custom element animation is a generator function. `yield` pauses until the next frame. `yield*` runs another generator to completion, such as a tween.

```tsx
public *animateIn(duration: number = 0.4): ThreadGenerator {
  this.opacity(0);
  yield* this.opacity(1, duration, easeOutCubic);
}
```

Import `ThreadGenerator`, `all`, `tween`, `waitFor`, and easing from `@vidova/core`.

## `animate` vs `animateIn`

`animate()` is the timeline entry point. The player calls it when the clip is active.

`animateIn()` is the intro. Thumbnails play `animateIn()` before capture. Frame 0 of a reveal-driven element is blank, which is why the thumbnail path needs a separate intro.

Keep them as two methods. `animate()` may call `animateIn()`, then hold or play the rest:

```tsx
public *animateIn(duration: number = 0.4): ThreadGenerator {
  this.opacity(0);
  yield* this.opacity(1, duration, easeOutCubic);
}

public *animate(duration?: number): ThreadGenerator {
  yield* this.animateIn(duration ?? 0.4);
}
```

If `animate()` hides the element at the end, a thumbnail that played only `animate()` would capture an empty frame. That is the reason for the split.

## Tweens and flow

A signal tween takes the next value, a duration in seconds, and an optional timing function:

```tsx
yield* this.opacity(1, 0.4, easeOutCubic);
```

Run tweens together with `all`:

```tsx
yield* all(
  this.opacity(1, 0.4, easeOutCubic),
  this.scale(1, 0.4, easeOutCubic),
);
```

Wait with `waitFor`:

```tsx
yield* waitFor(0.2);
```

`tween` is the low-level helper when you need a callback per frame:

```tsx
yield* tween(0.4, (value) => {
  this.opacity(value);
});
```

Prefer signal tweens when the property is already a signal.

## `fontSize` stays positive

`fontSize` must be a positive number on every frame. Do not drive it from a signal that starts at 0. Keep `fontSize` constant and animate `scale`.

```tsx
public *animateIn(duration: number = 0.4): ThreadGenerator {
  this.scale(0);
  yield* this.scale(1, duration, easeOutCubic);
}
```

A `fontSize` of 0 compiles and then fails at render. The clip shows nothing.

## Refs

Hold a child when a method needs to tween it later:

```tsx
import { createRef } from '@vidova/core';
import { Rect, Txt } from '@vidova/2d';

private readonly title = createRef<Txt>();

public constructor(props?: HelloTitleProps) {
  super({ ...props });
  this.add(
    <Rect>
      <Txt ref={this.title} text={() => this.label()} fontFamily="Inter Variable" />
    </Rect>,
  );
}

public *animateIn(duration: number = 0.4): ThreadGenerator {
  this.title().opacity(0);
  yield* this.title().opacity(1, duration, easeOutCubic);
}
```

Class fields for refs are fine. Class fields for `@signal` props must not collide with `Node` members. See [Layout](/docs/custom-elements/layout).
