Ship

Exports

An animated download from Stew Factory is its artwork, its scene data and one call that plays them: `mount`. This page shows that call and the handle it gives back.

What a download is

Every animated download carries the same three things, in React, Vue, Angular or as a Web Component. Only the code around them changes.

mount comes from the runtime: Stew Engine, plus the code that wires a scene together in Stew Factory's previews (its start, its clips, its wires), in one file that imports nothing. So a download moves as the preview does. Its bundle test holds it to 45 KB gzipped.

A design with no animation yet downloads as plain markup, with no runtime at all.

<svg>
The artwork. It keeps the data-stew-* attributes the runtime reads: which element is which (data-stew-uid), its anchor, its layers.
SCENE
Plain JSON: the scene's clip, the clips that events and data play, and the wires between them.
mount(svg, SCENE)
Plays the scene on the artwork, and hands back its player, set and destroy.

Mount a scene

This SCENE is written by hand in the shape an export writes, and mounted on the artwork with the runtime's own mount, as a download does it. Each button calls the handle mount returns. stew.destroy() puts the artwork back as it was drawn, and the same button then mounts it again.

The scene's clip repeats, so reverse() winds back through every lap it has played. The demo hands the data clip false as it mounts, so false does nothing until true has played: the same value twice is no change.

The scene's clip
Data
The artwork
{
  "v": 1,
  "doc": {
    "duration": 1.4,
    "tracks": [
      {
        "targetUid": "friend",
        "property": "y",
        "keyframes": [
          { "time": 0, "value": 0, "easing": "power2.out" },
          { "time": 0.45, "value": -64, "easing": "power2.in" },
          { "time": 0.9, "value": 0 }
        ]
      },
      {
        "targetUid": "friend",
        "property": "scaleX",
        "keyframes": [
          { "time": 0.9, "value": 1, "easing": "power2.out" },
          { "time": 1, "value": 1.16, "easing": "back.out(3)" },
          { "time": 1.4, "value": 1 }
        ]
      },
      {
        "targetUid": "friend",
        "property": "scaleY",
        "keyframes": [
          { "time": 0.9, "value": 1, "easing": "power2.out" },
          { "time": 1, "value": 0.82, "easing": "back.out(3)" },
          { "time": 1.4, "value": 1 }
        ]
      },
      {
        "targetUid": "shadow",
        "property": "scale",
        "keyframes": [
          { "time": 0, "value": 1, "easing": "power2.out" },
          { "time": 0.45, "value": 0.55, "easing": "power2.in" },
          { "time": 0.9, "value": 1 }
        ]
      }
    ],
    "trigger": "load",
    "loop": true
  },
  "interactions": [
    {
      "id": "happy",
      "name": "Happy",
      "binding": { "kind": "boolean", "name": "happy" },
      "doc": {
        "duration": 0.5,
        "tracks": [
          {
            "targetUid": "heart",
            "property": "opacity",
            "keyframes": [
              { "time": 0, "value": 0 },
              { "time": 0.12, "value": 1 }
            ]
          },
          {
            "targetUid": "heart",
            "property": "scale",
            "keyframes": [
              { "time": 0, "value": 0.3, "easing": "back.out(2.5)" },
              { "time": 0.5, "value": 1 }
            ]
          },
          {
            "targetUid": "cheeks",
            "property": "opacity",
            "keyframes": [
              { "time": 0, "value": 0 },
              { "time": 0.5, "value": 1 }
            ]
          }
        ]
      }
    }
  ]
}

The scene data

Stew Factory writes SCENE on every animated export, as JSON on one line next to the markup. It is generated data for the runtime, not for editing by hand: the next export writes it again.

Its doc is the scene's clip in the format Stew Factory saves, which is the format Stew Engine reads, plus what starts it and whether it repeats.

StewScene
vnumberrequired
The data version, 1 today. mount throws on any other, so data from a newer Stew Factory asks for a newer runtime.
docobjectrequired
The scene's clip, with its trigger and loop, and the shapes' own actions (behaviors).
interactionsreadonly object[]optional
The clips an event, a wire or the app's data plays, each a clip of its own.
chainobjectoptional
The wires between those clips, and when each scene clip starts and ends.
doc: how the scene starts
trigger"load" | "click" | "hover" | "inview" | "scrollscrub"default "load"
load plays at once. click replays it on every click on the host. hover plays while the pointer is over the host and winds back when it leaves. inview plays once 30% of the host is in view. scrollscrub follows the scroll.
loopbooleandefault true
Repeat when it ends. A scroll scrub never repeats.

The call and its handle

mount draws the scene's first frame at once (a scroll scrub: where the scroll has it), then its trigger starts it. The shapes' own actions play from the start, whatever starts the scene.

The handle's player is a Stew Engine player, so the calls on the player page work on it. mount sets its repeat from the scene's loop: -1 when it repeats, 0 when it doesn't. Take the scene down with stew.destroy(), not player.destroy(): that stops only the scene's clip.

mount(svg, scene, options?)
svgSVGSVGElementrequired
The artwork the scene was exported with. Each target is found by its data-stew-uid.
sceneStewScenerequired
The SCENE exported with it.
options.hostElementdefault the svg
What a click, hover or in-view start listens on.
options.scrollerElement | nulldefault the page
What scrolls the host, for an in-view start or a scroll scrub.
options.controlbooleanoptional
With host: the host is a control wrapping the artwork (a button, a link, the label of a checkbox or text field). The whole component's clips listen on it, and while it is disabled the pointer starts nothing and its clicks are eaten, as with a disabled <button>. Clips the app's data plays still play.
The handle mount returns (Stew)
playerPlayer
The scene's clip: play(), pause(), reverse(), restart(), seek(), timeScale and the rest.
set(kind, name, value)
Feeds a data clip what the app passes, by its name.
destroy()
Stops everything and puts the artwork back exactly as it was.

Feeding it data

A clip can be driven by the app's data instead of an event. Each framework's file turns those clips into props (inputs in Angular, attributes or properties on the Web Component) and hands every change to set. A framework file keeps set to itself and takes props. When you call mount yourself, call set directly.

The same value twice is no change, as with a prop set to what it was. A name no clip answers to does nothing.

InputKind: what set takes
"state"string
The value names the state, and its clip plays from the start. set does not read the name for a state (the framework files pass "state"): set("state", "state", "error").
"value"number
Glides the clip's playhead to that share of it, 0 to 1, over 0.35 seconds, easing out. A newer number takes over. Outside 0 to 1 it stops at the ends.
"boolean"boolean
The first value snaps into place without playing, so a component mounted already on doesn't animate itself on. After that, true plays the clip forward and false plays it back.

In each framework

Each file mounts the scene once its artwork is in the page and destroys it when it leaves. Each hands you the handle's player, which is null until the scene is mounted.

A Button, Link, Checkbox or Input download wraps the artwork in that control (a button, a link, or a label holding a native checkbox or text field), and mounts with that wrapper as host and control: true.

The files import mount from @stew-engine/player. That runtime is not on npm yet, so for now there is nothing to install.

React
Mounts in a layout effect, before the first paint, and destroys on unmount. With ref={anim}: anim.current?.player.
Vue 3
Mounts in onMounted and destroys in onBeforeUnmount. With ref="anim": anim.value.player.
Angular
Mounts in ngAfterViewInit, in the browser only and outside Angular's zone, and destroys in ngOnDestroy. With @ViewChild: anim.player.
Web Component
Mounts in connectedCallback and destroys in disconnectedCallback. On the element: el.player.
See it all togetherExamples