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({...}).

OptionTypeRequiredDefaultDescription
mountstring | HTMLElementYesThe 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.
initialViewstringYesName of the first view. A name that is not in views stops the instance with a "did you mean" message.
viewsobjectYesMap of view names to view sources
preloadstring[][]Views to resolve and cache eagerly during init()
deferredbooleanfalseDefer content rendering until after animation completes
debugbooleanfalseEnable debug logging to console
componentContextMap | objectnew Map()Shared context passed to function/object view sources
gesturesboolean | objectfalseZoom-out that follows the trackpad, a swipe or a pinch. See gestures
labelsobject | falseEnglish labelsAccessible names of the back button, arrows and dots. See labels
flattenboolean | objectfalseViews 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
deferred boolean default: false
When enabled, view content is inserted after the zoom animation completes instead of before. Useful for heavy views that might cause jank during animation. Per-trigger override via data-deferred attribute.
preload string[] default: []
Resolves and caches listed views during 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: {...}.

driver string | function default: 'css'
The animation engine used for zoom transitions. Built-in: '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.
duration string | number default: '1s'
CSS time ('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 string default: 'ease-in-out'
CSS easing function: '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'.
cover 'width' | 'height' default: 'width'
Which dimension of the trigger element determines the zoom scale. 'width' scales based on trigger width, 'height' scales based on trigger height. Most visible with non-square triggers.
effects [string, string] default: ['none', 'none']
CSS filter strings applied to background views during zoom. First value for the previous view, second for the last (deepest background) view. Example: ['blur(3px) brightness(0.7)', 'blur(8px) saturate(0)'].
animateEffects boolean default: true
Whether the 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.
stagger number default: 0
Progressive delay (ms) between view layers during transitions. Current view starts immediately, previous after 1×stagger, last after 2×stagger. Creates a cascading animation effect.
hideTrigger boolean | 'fade' default: false
How to hide the clicked trigger element during zoom-in. 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.

MethodReturnsDescription
init()PromiseInitialize and render the initial view
zoomTo(name, opts?)PromiseProgrammatic zoom into a named view
goTo(name, opts?)PromiseNavigate: { mode: 'depth' | 'lateral' }. With props, a lateral move picks the sibling with that data: goTo('room', { mode: 'lateral', props: { id: 'bedroom' } })
back()PromiseUndo 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()booleanWhether back() would navigate
zoomIn(el)PromiseZoom into the view indicated by a trigger element
zoomOut(target?)PromiseZoom 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()numberCurrent zoom depth (1 = root view)
getZoomLevel()numberAlias for zoomLevel()
getCurrentViewName()string | nullName of the currently active view
getViewStack()arrayThe views from the root to the current one: [{ name, title, depth }], plus params for a view opened from a trigger with data
getViewTitle(name)stringThe view's data-title, else the title of its definition, else its name
on(event, fn)thisSubscribe to lifecycle event (chainable)
off(event, fn?)thisUnsubscribe. Omit fn to remove all for that event
use(plugin, opts?)thisRegister a plugin (e.g. Zumly.Router); installed during init()
destroy()voidClean 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() / goTo() / back() Promise
Navigate programmatically without user clicks. 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().

EventPayloadDescription
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
Lifecycle Events .on() / .off()
Interact with the demo and watch events fire in real time in the log panel.
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.

TypeExampleCached
HTML string'<div class="z-view">...</div>'Indefinitely
URL'/views/detail.html'5 min TTL
Function(ctx) => '<div>..</div>'No
Async functionasync () => await fetch(...)No
Object{ render(ctx) { return '...' } }No
HTMLElementdocument.createElement('div')Cloned
Web component'my-component'No
View Sources Multiple formats
Each card zooms into a view created from a different source type. Compare HTML strings, functions, objects with render(), and async sources.

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.

AttributeDescription
data-toRequired. Target view name to zoom into
data-with-durationOverride transition duration (e.g. "2s")
data-with-easeOverride easing function
data-with-effectsOverride effects (pipe-separated: "blur(5px)|blur(10px)")
data-with-coverOverride cover dimension: "width" or "height"
data-with-staggerOverride stagger delay in ms (e.g. "120")
data-hide-triggerHide this trigger during zoom; value "fade" crossfades
data-deferredOverride 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

ClassElementPurpose
zumly-canvasContainerRequired on the mount element
z-viewView rootRequired on view root elements (auto-added if missing)
zoom-meTriggerRequired on clickable zoom-in elements