Zumly with Vue
A view can be a Vue component. Zumly hands you an empty element; you mount an app in it and unmount it when the view goes away.
Each planet and the list are Vue components in their own views. Like a few times, open a planet, then come back: the count is still there.
The pattern
Make the view a function. It receives a context; mount an app in target.
import Zumly from 'zumly'
import 'zumly/style.css'
import { createApp } from 'vue'
import Detail from './Detail.vue'
const app = new Zumly({
mount: '.canvas',
initialView: 'home',
views: {
home: '<div class="z-view"><div class="zoom-me" data-to="detail" data-id="42">Open</div></div>',
detail: ({ target, props, onCleanup }) => {
const view = createApp(Detail, { id: props.id })
view.mount(target)
onCleanup(() => view.unmount())
}
}
})
app.init()
targetis a fresh element that becomes the view. Zumly gives it thez-viewclass; add your own classes or a size to it if the view should not fill the canvas.propsholds the trigger'sdata-*attributes.data-id="42"arrives asprops.id, a string.onCleanupruns once, when the view is discarded. Unmount there; removing the DOM alone does not.
A list that opens a detail
Zumly is created once, in the component that owns the canvas, and destroyed with it. The list renders the triggers; any element with zoom-me and data-to works, whoever rendered it.
App.vue
<script setup>
import { ref, onMounted, onBeforeUnmount, createApp } from 'vue'
import Zumly from 'zumly'
import 'zumly/style.css'
import List from './List.vue'
import Detail from './Detail.vue'
// Each view gets its own Vue app
const mountView = (component, toProps = () => ({})) => ({ target, props, onCleanup }) => {
const view = createApp(component, toProps(props))
view.mount(target)
onCleanup(() => view.unmount())
}
const canvas = ref(null)
let app
onMounted(() => {
app = new Zumly({
mount: canvas.value,
initialView: 'list',
views: {
list: mountView(List),
detail: mountView(Detail, props => ({ id: props.id }))
}
})
app.init()
})
onBeforeUnmount(() => app?.destroy()) // removes its views too
</script>
<template>
<!-- The canvas fills its nearest positioned ancestor: give that a height -->
<div style="position: relative; height: 100vh">
<div ref="canvas" class="zumly-canvas"></div>
</div>
</template>
List.vue
<script setup>
const items = [
{ id: '1', name: 'Engine' },
{ id: '2', name: 'Battery' },
{ id: '3', name: 'Brakes' }
]
</script>
<template>
<div class="list">
<h1>Subsystems</h1>
<div v-for="item in items" :key="item.id" class="zoom-me" data-to="detail" :data-id="item.id">
{{ item.name }}
</div>
</div>
</template>
When a view comes back
- The views behind the current one stay mounted, so their component state survives a zoom in and out. Nothing renders again.
- If something changed meanwhile (a store, a fetch), repaint in
onActivate(fn), which runs each time the view becomes current again. - Siblings you leave are discarded, unless
lateralNav: { keepAlive: true }keeps them while you stay at that level.
Sharing services and state
Each view is its own Vue app, so install Pinia, the router or any plugin on it. Pass the shared instances once in componentContext; every view receives them as context.
const app = new Zumly({
...,
componentContext: { pinia },
views: {
detail: ({ target, props, context, onCleanup }) => {
const view = createApp(Detail, { id: props.id }).use(context.pinia)
view.mount(target)
onCleanup(() => view.unmount())
}
}
})
Things to know
destroy()takes its views with it, so the component that creates Zumly can mount again (hot reload, av-if) on a clean canvas.- Do not render another
z-viewinsidetarget:targetalready is the view. Zumly warns in the console if you do. - Views are plain DOM, not Shadow DOM: your global styles apply inside them.
- Zumly ships its own types;
ViewFunctionandViewContexttype the view context.
import { createApp } from 'vue'
import type { ViewFunction } from 'zumly'
const detail: ViewFunction = ({ target, props, onCleanup }) => {
const view = createApp(Detail, { id: String(props.id) })
view.mount(target)
onCleanup(() => view.unmount())
}
Next
Where views come from, in step 4. The same pattern for React · Svelte · Angular, and for radial layouts with Orbit.
Open this example ↗ Learn in six steps