Animation

Animate an element from code with animate(), or animate content in and out with <lynx-transition> and <lynx-transition-group>. import { LynxAnimation, LynxTransition, LynxTransitionGroup, type LynxAnimationOptions, type BaseLynxAnimation } from '@blotch/angular-lynx';

Usage

src/app/pulse.ts
import { Component, type ElementRef, viewChild } from '@angular/core';
import { type BaseLynxAnimation, LYNX_ELEMENTS } from '@blotch/angular-lynx';

@Component({
  selector: 'app-pulse',
  imports: [LYNX_ELEMENTS],
  template: `
    <view #box class="box" (bindtap)="pulse()">
      <text>Tap me</text>
    </view>
  `,
})
export class Pulse {
  readonly box = viewChild.required<ElementRef>('box');
  #animation: BaseLynxAnimation | null = null;

  pulse(): void {
    // Start animations outside the native event (see the setTimeout rule).
    setTimeout(() => {
      this.#animation?.cancel();
      this.#animation = this.box().nativeElement.animate(
        [{ opacity: 1 }, { opacity: 0.3 }, { opacity: 1 }],
        { duration: 400, easing: 'ease-in-out' },
      );
    });
  }
}
src/app/panel.ts
import { Component, signal } from '@angular/core';
import { LYNX_ELEMENTS, LynxTransition } from '@blotch/angular-lynx';

@Component({
  selector: 'app-panel',
  imports: [LYNX_ELEMENTS, LynxTransition],
  template: `
    <view (bindtap)="open.set(!open())"><text>Toggle</text></view>
    <lynx-transition [show]="open()" name="fade" [duration]="300">
      <view class="panel"><text>Hello</text></view>
    </lynx-transition>
  `,
  styles: `
    @keyframes fade-enter {
      from {
        opacity: 0;
      }
      to {
        opacity: 1;
      }
    }
    @keyframes fade-leave {
      from {
        opacity: 1;
      }
      to {
        opacity: 0;
      }
    }
    .fade-enter {
      animation: fade-enter 300ms ease both;
    }
    .fade-leave {
      animation: fade-leave 300ms ease both;
    }
  `,
})
export class Panel {
  readonly open = signal(false);
}

API

animate()

// A method of ElementRef.nativeElement
animate(
  keyframes: Record<string, string | number>[],
  options?: number | LynxAnimationOptions,
): BaseLynxAnimation;

A method on every element's nativeElement, which you get from viewChild() or an ElementRef. It starts the animation at once. A number for options is shorthand for { duration }.

  • From an event handler, call it inside setTimeout. See the setTimeout rule.
  • On the background thread, it returns an animation whose methods do nothing.
  • Removing the element cancels its running animations.

LynxAnimation

class LynxAnimation implements BaseLynxAnimation {}

What animate() returns on the main thread. Don't construct it yourself.

LynxAnimation.id

readonly id: string;

A unique ID that AngularLynx generates for the animation.

LynxAnimation.play()

play(): void;

Resumes a paused animation.

LynxAnimation.pause()

pause(): void;

Pauses the animation at its current frame.

LynxAnimation.cancel()

cancel(): void;

Stops the animation and removes its effect.

LynxTransition

@Component({ selector: 'lynx-transition' })
class LynxTransition {}

Mounts its projected content when show turns true, and unmounts it duration milliseconds after show turns false. Meanwhile it adds a class to the <lynx-transition> element itself: {name}-enter on enter and {name}-leave on leave.

  • Use CSS @keyframes, not transition, in those classes. The class is applied in the same render that mounts the content, so there's no earlier frame to transition from.
  • Match duration to your CSS animation's length. Completion is timed, not read from the animation.
  • The first render doesn't animate. Content that starts visible appears without an enter animation.
InputTypeDefaultDescription
showbooleanfalseWhether the content is shown.
namestring'v'Prefix for the classes: {name}-enter and {name}-leave.
durationnumber300Length of the enter and leave animations, in milliseconds.
OutputPayloadEmits
afterEntervoidduration milliseconds after entering starts.
afterLeavevoidRight after the content is removed.
PropertyTypeDescription
shouldRenderWritableSignal<boolean>Whether the content is mounted. Read it; don't set it.

LynxTransitionGroup

@Component({ selector: 'lynx-transition-group' })
class LynxTransitionGroup<T> {}

Renders a list from a child <ng-template let-item> and animates items as they're added and removed. It puts {name}-enter or {name}-leave on each item's root element.

  • The first render doesn't animate. Only later changes to each do.
  • Removed items stay in place while they leave, and the others reorder around them.
  • An item that comes back while leaving keeps its view and loses the leave class.
<lynx-transition-group [each]="todos()" [trackBy]="byId" name="list">
  <ng-template let-todo>
    <view class="row"
      ><text>{{ todo.title }}</text></view
    >
  </ng-template>
</lynx-transition-group>
InputTypeDefaultDescription
eachT[][]The items to render.
trackBy(item: T) => unknownitem => itemReturns each item's identity, like track in @for.
namestring'v'Prefix for the classes: {name}-enter and {name}-leave.
durationnumber300Length of the enter and leave animations, in milliseconds.
OutputPayloadEmits
afterEnterTduration milliseconds after an item enters.
afterLeaveTRight after a leaving item is removed.
PropertyTypeDescription
itemTemplateSignal<TemplateRef<unknown> | undefined>The child <ng-template> it renders.

Types

LynxAnimationOptions

type LynxAnimationOptions = AnimationOptions & {
  iterationCount?: number | string;
  fillMode?: string;
  timingFunction?: string;
};

AnimationOptions comes from @lynx-js/types. Together they accept:

OptionTypeDefaultDescription
durationnumber0Length of one iteration, in milliseconds.
delaynumber0Wait before starting, in milliseconds.
iterationsnumber1How many times to run. Infinity loops forever.
direction'normal' | 'reverse' | 'alternate' | 'alternate-reverse''normal'Playback direction for each iteration.
easingstring'linear'A timing function, such as 'ease-in' or 'cubic-bezier(…)'.
fill'none' | 'forwards' | 'backwards' | 'both''none'Whether the first or last frame applies before or after playing.
namestringGeneratedA name that animation events report. Setting it currently stops play(), pause() and cancel() from reaching the animation.
play-state'running' | 'paused''running'Starts the animation running or paused.
iterationCountnumber | stringCSS-style alias for iterations, such as 'infinite'.
fillModestringCSS-style alias for fill.
timingFunctionstringCSS-style alias for easing.

BaseLynxAnimation

type BaseLynxAnimation = Pick<Animation, 'id' | 'cancel' | 'pause' | 'play'>;

What animate() returns on either thread: an id plus play(), pause() and cancel(). Animation comes from @lynx-js/types.