API Reference
Every option, method and event. New to Zumly? Start with the six steps.
Constructor Options
Required and optional parameters passed to new Zumly({...}).
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
mount | string | HTMLElement | Yes | The canvas: a CSS selector or the element itself (a framework ref works). It gets the zumly-canvas class and fills its nearest positioned ancestor, which needs a size. | |
initialView | string | Yes | Name of the first view. A name that is not in views stops the instance with a "did you mean" message. | |
views | object | Yes | Map of view names to view sources | |
preload | string[] | [] | Views to resolve and cache eagerly during init() | |
deferred | boolean | false | Defer content rendering until after animation completes | |
debug | boolean | false | Enable debug logging to console | |
componentContext | Map | object | new Map() | Shared context passed to function/object view sources | |
gestures | boolean | object | false | Zoom-out that follows the trackpad, a swipe or a pinch. See gestures | |
labels | object | false | English labels | Accessible names of the back button, arrows and dots. See labels | |
flatten | boolean | object | false | Views behind shown as a SnapDOM photo, for smooth zooms with heavy views: { snapdom, stream, minNodes, scale }, or true for the global snapdom. Per view: data-flatten. See Heavy views |
data-deferred attribute.init(). Improves perceived performance for views with async sources or URL fetches. Compare with and without preload to see the loading difference.Transitions
Configure how zoom animations look and feel. All nested under transitions: {...}.
'css', 'waapi', 'none'. Library drivers read a global: 'anime' (Anime.js v4 or v3), 'gsap', 'motion'. With a bundler, pass the library: animeDriver(anime), gsapDriver(gsap), motionDriver({ animate }) from 'zumly'. Or pass a custom (spec, onComplete) => void function. Every driver follows the same curve for a CSS ease, and finishes even when the tab is hidden.'500ms', '1.5s') or a number of milliseconds (600). Per-trigger override via data-with-duration. An invalid value warns and falls back to '1s'.'ease', 'linear', 'cubic-bezier(...)', 'steps(...)', 'linear(...)'. With the anime, gsap or motion driver, also the library's own ease names ('outExpo', 'power3.inOut', 'backOut'). Per-trigger override via data-with-ease. An invalid CSS easing warns and falls back to 'ease-in-out'.'width' scales based on trigger width, 'height' scales based on trigger height. Most visible with non-square triggers.['blur(3px) brightness(0.7)', 'blur(8px) saturate(0)'].effects filter transitions together with the zoom. false applies it at once when the zoom starts and removes it at once on zoom-out. Animating a filter on large background layers is the costliest part of a transition with heavy views, so false makes those zooms noticeably smoother.false = visible, true = visibility:hidden, 'fade' = smooth opacity crossfade.Methods
Navigation promises settle after transitions finish. Await each call to navigate sequentially; calls made during an active load or animation are ignored.
| Method | Returns | Description |
|---|---|---|
init() | Promise | Initialize and render the initial view |
zoomTo(name, opts?) | Promise | Programmatic zoom into a named view |
goTo(name, opts?) | Promise | Navigate: { mode: 'depth' | 'lateral' }. With props, a lateral move picks the sibling with that data: goTo('room', { mode: 'lateral', props: { id: 'bedroom' } }) |
back() | Promise | Undo the last lateral move at this level, or zoom out. Each level keeps its own lateral history, also after zooming in and out again |
canGoBack() | boolean | Whether back() would navigate |
zoomIn(el) | Promise | Zoom into the view indicated by a trigger element |
zoomOut(target?) | Promise | Zoom out one level; zoomOut(2) climbs two, zoomOut({ to: 'home' }) climbs to the nearest ancestor with that name. Several levels run as one transition and clamp at the root. Also takes levels, duration and ease |
zoomLevel() | number | Current zoom depth (1 = root view) |
getZoomLevel() | number | Alias for zoomLevel() |
getCurrentViewName() | string | null | Name of the currently active view |
getViewStack() | array | The views from the root to the current one: [{ name, title, depth }], plus params for a view opened from a trigger with data |
getViewTitle(name) | string | The view's data-title, else the title of its definition, else its name |
on(event, fn) | this | Subscribe to lifecycle event (chainable) |
off(event, fn?) | this | Unsubscribe. Omit fn to remove all for that event |
use(plugin, opts?) | this | Register a plugin (e.g. Zumly.Router); installed during init() |
destroy() | void | Clean up listeners and state, and remove the views and navigation UI the instance put in the canvas. Your own content there stays, and a new instance can start on the same canvas right away |
zoomTo() for depth navigation, goTo() with { mode: 'lateral' } for same-level, back() to go back.// Programmatic navigation
const app = new Zumly({ mount: '#canvas', initialView: 'home', views });
await app.init();
await app.zoomTo('detail'); // zoom in
await app.zoomTo('deep'); // zoom deeper
await app.back(); // zoom out
await app.goTo('page2', { mode: 'lateral' }); // slide to sibling
await app.back(); // back to previous sibling
await app.zoomOut(2); // two levels up, one transition
await app.zoomOut({ to: 'home' }); // up to an ancestor by name
console.log(app.zoomLevel()); // 2
console.log(app.getCurrentViewName()); // 'detail'
Lifecycle Events
Hook into the zoom lifecycle with .on() and .off().
| Event | Payload | Description |
|---|---|---|
viewChanged | { type, viewName, from, zoomLevel, node } | After every navigation and after init(); type is 'init', 'zoomIn', 'zoomOut' or 'lateral' |
viewMounted | { viewName, node } | View inserted into DOM and mounted() hook (if any) complete |
beforeZoomIn | { viewName } | Just before zoom-in animation starts |
afterZoomIn | { viewName, zoomLevel } | After zoom-in animation completes |
beforeZoomOut | { zoomLevel, viewName, from, levels? } | Just before zoom-out starts; viewName is the view returned to, levels when climbing several |
afterZoomOut | { zoomLevel, viewName, from, levels? } | After zoom-out completes |
zoomOutProgress | { phase, progress, duration?, viewName, from, source } | A zoom-out driven by a gesture (gestures.follow): 'start' and 'move' carry the 0–1 progress, 'commit' or 'cancel' fire on release with the duration left, 'end' when the views settle. source is 'wheel', 'swipe' or 'pinch' |
beforeLateral | { viewName, from, isBack } | Before lateral navigation animation |
afterLateral | { viewName, from, isBack } | After lateral navigation animation |
destroy | {} | When destroy() is called |
app.on('viewMounted', ({ viewName, node }) => { /* ... */ });
app.on('beforeZoomIn', ({ viewName }) => { /* ... */ });
app.on('afterZoomIn', ({ viewName, zoomLevel }) => { /* ... */ });
app.on('beforeZoomOut', ({ zoomLevel }) => { /* ... */ });
app.on('afterZoomOut', ({ zoomLevel }) => { /* ... */ });
app.on('beforeLateral', ({ viewName, from, isBack }) => { /* ... */ });
app.on('afterLateral', ({ viewName, from, isBack }) => { /* ... */ });
app.on('zoomOutProgress', ({ phase, progress }) => {
backButton.style.setProperty('--progress', progress); // follow the gesture
});
app.on('destroy', () => { /* ... */ });
View Sources
Views can be defined in many ways. The resolver detects the type automatically.
| Type | Example | Cached |
|---|---|---|
| HTML string | '<div class="z-view">...</div>' | Indefinitely |
| URL | '/views/detail.html' | 5 min TTL |
| Function | (ctx) => '<div>..</div>' | No |
| Async function | async () => await fetch(...) | No |
| Object | { render(ctx) { return '...' } } | No |
| HTMLElement | document.createElement('div') | Cloned |
| Web component | 'my-component' | No |
Function and object views receive a context: target (a fresh div; if the function returns nothing, target is the view: give it your classes and render only the content), props (the trigger's data attributes), trigger, context (componentContext), onCleanup(fn) (runs when the view is discarded) and onActivate(fn) (runs each time the view becomes current again without being rendered anew, for example after zooming back out to it). A template with several root elements is wrapped in one .z-view.
A view can carry a human-readable name for the navigation UI: title in an object view (or a function view's title property), or data-title on the view's root element, which wins. Without one, the view name is used. The back button, the sibling dots, getViewStack() and the view's aria-label show it.
// HTML string
const myView = '<div class="z-view">Hello</div>';
// Function returning markup (called fresh each time)
function myView({ props }) {
return `<div class="z-view">${props.id}</div>`;
}
// Function filling ctx.target, repainting when the user comes back
function room({ target, onActivate }) {
target.className = 'z-view room';
const paint = () => { target.innerHTML = `<h1>${state.temperature}°</h1>`; };
paint();
onActivate(paint);
}
// Async function
async function myView() {
const html = await fetch('/partial.html').then(r => r.text());
return html;
}
// Object with render() and optional mounted()
const myView = {
render(ctx) { return '<div class="z-view">...</div>'; },
mounted(node) { console.log('inserted', node.offsetWidth); }
};
// URL (fetched and cached for 5 minutes)
const myView = '/views/detail.html';
// Human-readable title for the navigation UI
const security = { title: 'Security', render: () => '<div class="z-view">...</div>' };
const log = '<div class="z-view" data-title="Audit log">...</div>';
Per-Trigger Attributes
Override global settings on individual trigger elements using data attributes.
| Attribute | Description |
|---|---|
data-to | Required. Target view name to zoom into |
data-with-duration | Override transition duration (e.g. "2s") |
data-with-ease | Override easing function |
data-with-effects | Override effects (pipe-separated: "blur(5px)|blur(10px)") |
data-with-cover | Override cover dimension: "width" or "height" |
data-with-stagger | Override stagger delay in ms (e.g. "120") |
data-hide-trigger | Hide this trigger during zoom; value "fade" crossfades |
data-deferred | Override global deferred setting for this trigger |
data-* | Any data attribute becomes a prop in ViewContext.props |
<div class="zoom-me"
data-to="detail"
data-with-duration="2s"
data-with-ease="cubic-bezier(.4,0,.2,1)"
data-with-effects="blur(5px)|blur(10px)"
data-deferred
data-id="42"
data-category="settings">
Zoom in
</div>
// In the target view function:
function detail({ props }) {
console.log(props.id); // "42"
console.log(props.category); // "settings"
}
Required Classes
| Class | Element | Purpose |
|---|---|---|
zumly-canvas | Container | Required on the mount element |
z-view | View root | Required on view root elements (auto-added if missing) |
zoom-me | Trigger | Required on clickable zoom-in elements |