Theatre.js Animation Library

repository·main·Indexed 11 days ago

https://github.com/theatre-js/theatre

An animation library for high-fidelity motion graphics on the web, supporting both programmatic and visual workflows. It consists of @theatre/core for runtime animation management and @theatre/studio for a visual choreography editor. The ecosystem also includes @theatre/dataverse for reactive dataflow and @theatre/react for React bindings, featuring abstractions like Atoms, Pointers, and Prisms.

Tokens
48.3K
Snippets
181
Records
244
Agent score
93%

What's inside Theatre.js

  1. Overview of Theatre.js

    main

    Theatre.js is a motion design library for the web designed for high-fidelity animation. It allows you to express detailed, nuanced movement both programmatically and visually.

    Key use cases include:

    • Animating 3D objects (e.g., with THREE.js).
    • Animating HTML/SVG elements (e.g., via React).
    • Designing micro-interactions.
    • Choreographing generative interactive art.
    • Animating any arbitrary JavaScript variable.
  2. What is Theatre.js Core

    main

    Theatre.js is an animation library designed for high-fidelity motion graphics. It allows you to express detailed, nuanced movement both programmatically and visually.

    Key use cases include:

    • Animating 3D objects (e.g., using THREE.js).
    • Animating HTML/SVG elements via React or other libraries.
    • Designing micro-interactions.
    • Choreographing generative interactive art.
    • Animating any arbitrary JavaScript variable.
  3. Use Theatre.js for high-fidelity motion graphics

    main

    Theatre.js allows you to animate various types of content both programmatically and visually. Common use cases include:

    • 3D Objects: Animate objects in THREE.js or other 3D libraries.
    • HTML/SVG: Animate DOM elements via React or other web libraries.
    • Micro-interactions: Design subtle UI feedback and transitions.
    • Generative Art: Choreograph interactive and generative art pieces.
    • Arbitrary JS Variables: Animate any JavaScript variable by connecting it to the Theatre.js timeline.
  4. What is a prism and how does it work?

    main

    A prism is a reactive unit in @theatre/dataverse. It is created by passing a function to prism(). This function automatically tracks any other prisms referenced within it as dependencies. When those dependencies change, the function reruns. This allows you to build complex, reactive dependency graphs where updates propagate efficiently through the system.

    import { prism } from '@theatre/dataverse'
    
    const myPrism = prism(() => {
      // Any prism used here becomes a dependency
      return someOtherPrism.getValue() + 1
    })
  5. What are the core concepts of @theatre/dataverse?

    main

    Dataverse is built around four main abstractions that manage state and computation:

    1. Atoms: State holders that manage either component-level or global application state.
    2. Pointers: Type-safe references to specific properties within an Atom.
    3. Prisms: Functions that derive values from Atoms or other Prisms.
    4. Tickers: Mechanisms to schedule and synchronize computations, especially useful for reacting to changes outside of the React render loop.
  6. Understand Prism states (Hot, Cold, Stale, Fresh)

    main

    Prisms exist in a lifecycle that manages how and when they recompute. This state propagates through the dependency graph.

    Prism States:

    • 🧊 Cold: The prism has no active subscribers and no hot dependents.
    • 🔥 Hot: The prism is being actively used (via useVal, onChange, etc.) or has a hot dependent.
      • 🪵 Stale: The prism is hot, but its dependencies have changed since the last time the prism was read. It needs to recompute.
      • 🌲 Fresh: The prism is hot, and its value is up-to-date with its dependencies. Re-reading will not trigger recomputation.

    Lifecycle Behavior:

    • When a dependency changes, a prism moves from 🌲 Fresh to 🪵 Stale.
    • Reading the value of a 🪵 Stale prism triggers recomputation and moves it to 🌲 Fresh.
    • You can listen for state transitions using prism.onStale(callback).
    const atom = new Atom(0)
    const a = prism(() => val(atom.pointer))
    
    a.onStale(() => {
      console.log('a is stale')
    })
    
    // a goes from 🧊 to 🔥🪵
    console.log(val(a))
    
    // a goes from 🔥🪵 to 🔥🌲
    atom.set(1)
    // a goes from 🔥🌲 to 🔥🪵
  7. How the Ticker class works

    main

    The Ticker class is used to schedule and execute callbacks per tick. It provides a mechanism to decouple the registration of side effects from their execution. Ticks are not automatic; they must be triggered externally (for example, using a requestAnimationFrame loop) by calling the tick() method.

    When you register a callback, you can choose whether it runs immediately (if a tick is already in progress) or waits for the next cycle. This is useful for managing high-frequency updates or ensuring certain logic runs in sync with a rendering loop.

    // Conceptual usage pattern
    const ticker = new Ticker();
    
    // Schedule a callback
    ticker.onNextTick(() => {
      console.log('Running on the next tick');
    });
    
    // Trigger the tick manually (e.g., in a loop)
    function loop(time: number) {
      ticker.tick(time);
      requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
  8. Understand the Theatre.js package structure

    main

    Theatre.js is split into two distinct packages that work together:

    1. @theatre/core: The runtime library used to define animations and manage state in your application.
    2. @theatre/studio: The visual editor (Studio) used to choreograph and tweak animations in real-time.

    To use Theatre.js in a project, you will typically install @theatre/core for your application logic and use @theatre/studio during development to visually design your motion graphics.

  9. Understand the difference between @theatre/core and @theatre/studio

    main

    Theatre.js is split into two distinct packages that serve different roles in your workflow:

    1. @theatre/core: The runtime library. This is what you include in your production application to drive animations and manage state. It is released under the Apache License.

    2. @theatre/studio: The visual editor. This package provides the UI for designing animations, setting up scenes, and choreographing motion. It is intended to be used only during design and development. It is released under the AGPL 3.0 License.

    Important for Production: Your final production bundle should only include @theatre/core. Because @theatre/studio is only used during development, your end-users will not be affected by the AGPL 3.0 license of the studio package.

  10. Understand the Prism<V> interface

    main
    In @theatre/dataverse, a Prism<V> is a common interface representing a reactive value of type V. Prisms allow you to observe and interact with data that can change over time. They support concepts like being "hot" (actively updating/freshening) or "stale" (out of date), and they provide mechanisms to subscribe to value changes via a Ticker to ensure efficient updates.
  11. Use PointerProxy to create switchable pointer-prisms

    main

    The PointerProxy<O> class in @theatre/dataverse allows you to create "pointer-prisms" where the underlying target pointer can be swapped out dynamically. This is useful when you want to maintain a stable reference to a proxy object while changing which part of the data tree it is currently observing or manipulating.

    Key Capabilities

    • Dynamic Switching: Use setPointer(p) to change the underlying Pointer being proxied.
    • Prism Access: Use pointerToPrism(pointer) to obtain a Prism for a specific sub-path relative to the current proxied pointer.
    • Root Access: The pointer property provides a read-only reference to the current root pointer of the proxy.
  12. Understand the keyframe copy/paste algorithm

    main

    Theatre.js uses specific algorithms for copying and pasting keyframes within the Sequence Editor. The behavior depends on whether you are selecting a single track or an aggregate track (a compound property, sheet object, or sheet), and whether the target of the paste is a simple property or a compound one.

    Copying Logic

    When you copy, the system determines a relative PATH based on your selection:

    • Single track selection: The path is relative to the closest common ancestor of the selected tracks.
    • Aggregate track selection: The path is relative to the aggregate track itself (e.g., a sheet, sheetObject, or compoundProp).

    Copy Examples:

    • Selecting obj1.props.transform.position.x results in a path of x.
    • Selecting multiple properties like obj1.props.transform.position.{x, z} and obj1.props.transform.rotation.z results in a structured path: {position: {x, z}, rotation: {z}}.

    Pasting Logic

    When pasting, the system attempts to map the copied data onto the target structure:

    • Simple to Simple: Performs a 1-1 mapping.
    • Simple to Compound: Distributes the single value to all properties within the compound (e.g., pasting 1 into {x, y} results in {x: 1, y: 1}).
    • Compound to Simple: Pastes into the first simple property found within the compound (recursively).
    • Compound to Compound:
      • Perfect match: Full replacement.
      • Partial match: Pastes only the overlapping properties (e.g., {x, y, z} pasted into {x, z} results in {x, z}).
      • No match: If properties do not overlap, nothing happens. However, if the target is an object or sheet (rather than a property), the algorithm performs a forEach on the target's children and attempts to paste onto their .props.