The composition and the clock
This page covers the composition root and its attributes, the kinetic store that holds the clock, the channels a seek arrives by, and the controls and options that drive playback.
The clock is a store named kinetic, which markup binds as $kinetic.time.
With no runtime on the page, a requestAnimationFrame loop plays the composition and loops at its duration, and Kinetic hides each clip outside its window.
seek(), play(), pause() and config() drive it, and a bad value or an unknown option throws (KN-100).
The composition root
The composition is an element with data-composition-id.
Each scene is a component inside it, timed by its clip attributes.
A timeline here is a GSAP timeline registered on window.__timelines under the composition's id, which a renderer's runtime seeks.
<div data-composition-id="main" data-start="0" data-duration="6"
data-width="1920" data-height="1080" data-fps="30">
<div data-component="title" data-start="1" data-duration="3">
<h1 data-bind-style="style">Launch</h1>
</div>
</div>
wildflower.kinetic.scene('title', {
keyframes: {
style: {
opacity: { at: [0, 0.5, 2.5, 3], to: [0, 1, 1, 0] },
translateY: { at: [0, 0.6], to: [40, 0], ease: 'spring' }
}
}
});
| Attribute | Meaning |
|---|---|
data-composition-id | The composition's id. It is also the default file name of a render, and the key config({ channel: 'timeline' }) registers under. |
data-start="0" | The composition's start, in seconds. |
data-duration | The composition's length, in seconds. A time-driven composition has no timeline to take it from, and development builds warn once the page has loaded without it, or without a composition root at all (KN-110). |
data-width, data-height | The pixel size, which render() uses by default. Kinetic gives the root this width and height before the page shows, wherever the page's CSS leaves them at auto, so the CSS need not repeat them; a size the CSS gives the root stays. |
data-fps | Optional. Frames per second, 30 by default. |
Until readiness, the root holds unbound markup and every clip at once.
Kinetic sets data-kinetic-ready on the root once the page may be shown, to true, or to failed when readiness failed, and one CSS rule, Kinetic's cloak, holds the stage until then.
[data-composition-id]:not([data-kinetic-ready]) { visibility: hidden; }
The rule stays in the page, since Kinetic's script is deferred and the stage would flash before it runs.
It is named after WildflowerJS's [data-cloak], and differs from it in two ways.
- It lifts when Kinetic is ready: the fonts loaded, the media decoded, the effects drawn and the first seek landed. That is later than WildflowerJS's cloak, which lifts when the components initialize.
- It hides with
visibility, which keeps the layout Kinetic measures, where WildflowerJS's cloak usesdisplay: none.
With the runtime on the page, Kinetic sets it once the runtime has published __renderReady, the flag that marks the page as ready to capture.
The clock
The clock is a store named kinetic.
Markup can bind it as $kinetic.time.
| Field | Value |
|---|---|
time | Composition seconds. |
frame | Math.round(time * fps), and in a render with a shutter the number of the frame its samples belong to. |
fps | A render's own fps, else the root's data-fps, else config({ fps }), else 30. |
duration | The root's data-duration in seconds, or the runtime player's duration. 0 when unknown. |
playing | True while the clock is playing. With no runtime on the page that is the standalone clock, and with one it follows the player, checked every 200 ms. |
levels | How many times the level tables have changed, 0 on a page with no audio clip. level() and spectrum() read it, so a computed that calls them runs again when the tables change. |
shutter | The shutter angle, in degrees, of the render with a shutter that is running, and 0 at any other time. smear() reads it, so a computed that calls it runs again when such a render starts and ends. |
mode | 'interactive' or 'video', the mode in force. |
step, stepIndex, stepCount, stepStart, stepLength, waiting, canAdvance | The step fields of interactive mode. |
Channels
A seek sets the clock to one time. A time reaches the store by one of three channels.
hf-seek, the default. The HyperFrames runtime dispatcheshf-seekon every seek, in preview and in a render, with{ time, waitUntil }(Rendering through HyperFrames). Kinetic applies the time and callswaitUntilwith a promise that settles after the framework's flush, every promise a scene'sseek()returned, the CSS-animation catch-up and any Trillium draws, so the frame is captured only once the DOM holds it.- Timeline, the fallback, chosen with
config({ channel: 'timeline' }). Kinetic registers an object atwindow.__timelines[rootId]that the runtime drives through its timeline registry. The root needs adata-composition-idand has to be in the document, or the call throws (KN-100). - Standalone, when no runtime is on the page.
A
requestAnimationFrameloop starts on load, so a scene file previews in a plain tab. At the composition's duration it holds the last frame for 3 seconds, then starts again from 0. The loop stops when a seek arrives from the runtime.config({ loop: { hold: seconds } })sets how long the last frame holds, and with the preview bar up there is no hold.- With
config({ loop: false })it stops on the composition's last frame,duration - 1/fps, and setsplayingto false, andplay()on that frame starts again from 0. play({ from, to })holds the last frame beforetothe same way, also when a slow frame passesto.- With the preview bar down, a click on the composition root pauses the clock, and another click plays it again, keeping the hold of
play({ from, to }). Kinetic makes the root focusable (tabindex="0") unless it has atabindexof its own, and Space or Enter on the focused root does what a click does. A click that ends a drag, or that leaves text selected, does nothing. In an interactive lesson a click pauses a step only while it plays, and the second click plays it on to its hold; at a hold a click does nothing, since the learner's controls take it on from there. While a click has it paused, a large pause mark sits over the center of the stage, beside the composition root and outside it, so no check or render shows it; it appears and goes with no fade, and a click on it plays. A click on a control inside the root does neither:a,button,input,select,textarea,label,summary, an element withcontenteditable,tabindexordata-action, or one whoseroleis a widget such asbuttonorslider.
Clips outside their windows
Kinetic treats the page as having the runtime when it finds window.__player, window.__renderReady or window.__hyperframes.
Without it, on every seek each clip (every element with data-start) is hidden outside its window, by the rule the runtime applies when it is present.
- The window is half-open, a clip that runs to the composition's end keeps the final instant, and a descendant of a hidden clip is hidden with it.
- An SVG filter that makes its own pixels (
feFlood,feImage,feTurbulence) on a clip or its descendant paints throughvisibility: hidden, and so does a descendant with its ownvisibility: visible. - So a hidden clip also carries
data-kinetic-hidden, which a style sheet Kinetic adds clips to nothing withclip-path, and nothing inside it paints. Its layout and its own inline style stay as they are. - Inside its window a clip gets an inline
visibility: visible, which shows it even inside an element the page hides withvisibility: hidden, so a footage clip shows through a parent hidden that way. Hide an element that holds clips withopacity: 0ordisplay: none, or give it adata-startof its own, whose window then hides what is inside it. - With the runtime on the page, the runtime's visibility is the only hiding, so such a filter goes on only inside its clip's window (Still effects with an SVG filter).
Controls
seek(seconds)moves the clock. With the runtime on the page it calls the player'srenderSeek, and otherwise it sets the store and stops the standalone loop. A value that is not a finite number throws (KN-100).play()andpause()drive the runtime's player, or the standalone loop. In interactive mode on a page with steps,play()plays on to the current step's hold, and at or past the hold plays the step again from its start (Interactive mode).play({ from, to })seeks tofrom, plays, and holds on the last frame beforeto, which isto - 1/fpswhentofalls on a frame, withplayingfalse.fromdefaults to the current time andtoto the composition's duration, and atopast the duration holds on the composition's last frame.- A
tothat is not afterfrom, a value that is not a finite number, or notoon a composition with no duration throws (KN-100). - With the runtime on the page, Kinetic calls the player's
renderSeek(from)andplay(), applies the firsthf-seekat or past the held frame as that frame, then pauses the player and seeks it there. The runtime's own clip visibility follows the player's time for that one frame.
- A
config(options)sets options and returns the settings in force. With no argument it only returns them. An option not in the table below throws (KN-100), and nothing is applied.
The config options
| Option | Default | Meaning |
|---|---|---|
fps | 30 | Frames per second when neither a render nor the root's data-fps sets one. A number above 0. |
channel | 'event' | 'event' listens for hf-seek. 'timeline' registers the timeline fallback. |
autoplay | true | Whether the standalone clock starts on load when no runtime is on the page. |
preview | false | The preview bar. true when ?preview or data-preview is present. |
loop | true | Whether the standalone clock starts again from 0 at the end.
true holds the last frame 3 seconds first, { hold: seconds } sets those seconds, and false holds the last frame and stops.
There is no hold while the preview bar is up, and config() reports the option as { hold }, or false.
With the runtime on the page, its player runs playback and this option has no effect. |
fit | as preview | Whether the stage is scaled down to fit the window, following preview until it is set on its own.
The root goes into a plain <div class="kinetic-fit"> (no component) as wide as data-width and no wider than its container, scaled from its top left corner to the wrapper's width, never above 1, so its layout stays data-width by data-height.
It is never applied during a render, where render() draws at the composition's own size. |
sound | true | Whether Kinetic plays the audio clips in the preview, and false plays nothing and takes the bar's speaker button away.
The render and the levels are unaffected. |
shutter | false | The shutter every render() uses when the call gives none, the preview bar's Render button included, as an angle in degrees or { angle, samples }, and 0, false or null turns it off.
config() reports it as { angle, samples }, or false. |
finish | false | The finish every render() uses when the call gives none, the preview bar's Render button included, and the page shows where the browser can carry it (The finish in the page). 0, false or null turns it off, and config() reports it as its fields, each an object of all its named parts such as { grain: { amount, size, fps } }, which config({ finish }) takes back, or false. |
mode | 'auto' | 'auto', 'interactive' or 'video', the mode setting. Under 'auto' the page is in video mode with the preview bar up and in interactive mode otherwise. |
// Play one step of a lesson and hold its last frame, 8.9667 s at 30 fps.
wildflower.kinetic.play({ from: 0, to: 9 });
// Hold the last frame 1 s before starting again.
wildflower.kinetic.config({ loop: { hold: 1 } });
// Hold the composition's last frame and stop.
wildflower.kinetic.config({ loop: false });