Skip to content

Documentation / @ripl/vue

@ripl/vue ​

npmlicensesize

Declarative Vue 3 components for Ripl. Describe a scene graph as a template, bind props to element state, and let v-if and v-for drive the graph.

Features ​

  • Every built-in element as a component — <ripl-arc>, <ripl-circle>, <ripl-ellipse>, <ripl-image>, <ripl-line>, <ripl-path>, <ripl-polygon>, <ripl-polyline>, <ripl-rect>, <ripl-text> and <ripl-group>, each typed with its own state properties.
  • Three levels of engine, all optional — a context alone paints; add <ripl-scene> for a hoisted graph and z-ordering; add <ripl-renderer> for an animation loop and transitions.
  • <ripl-transition> — enter, update and leave phases with per-element staggering, following Vue's own enter-from / leave-to model.
  • Pointer events as Vue listeners — @click, @mouseenter, @drag and the rest, subscribed only when you bind them so hit testing stays accurate.
  • Compositions for the imperative escape hatch — useRiplContext, useRiplScene, useRiplRenderer and useRiplElement.
  • Strict TypeScript, tree-shakable, SSR-safe.

Installation ​

bash
# npm
npm install @ripl/vue

# yarn
yarn add @ripl/vue

# pnpm
pnpm add @ripl/vue

vue (3.5 or later) is a peer dependency you already have. @ripl/core, @ripl/web, @ripl/dom and @ripl/utilities arrive as dependencies of this package; you never install them yourself.

This package targets @ripl/web, i.e. Canvas 2D. To render through another backend, build the context yourself and pass it in via the context prop on <ripl-context>.

Quick start ​

Register the components globally:

typescript
import {
    createRipl,
} from '@ripl/vue';

import {
    createApp,
} from 'vue';

import App from './app.vue';

createApp(App).use(createRipl()).mount('#app');

Then describe a scene. Give <ripl-context> a size, since the canvas fills it:

vue
<template>
    <ripl-context style="width: 400px; height: 300px">
        <ripl-scene>
            <ripl-renderer>
                <ripl-transition
                    :enter="{ duration: 400, state: { opacity: 0, radius: 0 } }"
                    :update="{ duration: 250 }"
                    :leave="{ duration: 200, state: { opacity: 0 } }"
                >
                    <ripl-circle
                        v-for="item in items"
                        :key="item.id"
                        :cx="item.x"
                        :cy="item.y"
                        :radius="item.radius"
                        fill="#1e6978"
                        @click="select(item)"
                    />
                </ripl-transition>
            </ripl-renderer>
        </ripl-scene>
    </ripl-context>
</template>

Components can equally be imported one at a time, in which case the plugin is unnecessary.

The three tiers ​

Each level adds capability, and every element picks up the highest one above it:

TemplateWhat you get
<ripl-context>Elements paint directly. Pointer events and hit testing work.
+ <ripl-scene>A hoisted, flat instruction stream: z-ordering, group clipping, efficient large graphs.
+ <ripl-renderer>An animation loop, and <ripl-transition>.

Transitions ​

enter is the state an element animates from; leave is the state it animates to; update is how a prop change animates. Each takes an options object or a factory called per element, which is what makes staggering work:

vue
<template>
    <ripl-transition
        :enter="(element, index, length) => ({
            duration: 400,
            delay: (index / length) * 200,
            state: { opacity: 0 },
        })"
    >
        <ripl-rect v-for="bar in bars" :key="bar.id" v-bind="bar" />
    </ripl-transition>
</template>

An enter phase can reference a property the template never binds: the target is read off the element before the enter state is applied, so fading in from { opacity: 0 } recovers a target of 1 from the element's inherited or default state.

loop repeats a phase: true restarts it, 'alternate' plays it back and forth. A looping phase never completes, so its onComplete never fires and the renderer cannot idle while one runs; it is cancelled when its element leaves, and ignored on the leave phase, which has to finish in order to destroy the element.

Compositions ​

typescript
import {
    useRiplContext,
    useRiplElement,
    useRiplRenderer,
    useRiplScene,
} from '@ripl/vue';

const context = useRiplContext();
const scene = useRiplScene();
const renderer = useRiplRenderer();
const element = useRiplElement();

Providers construct during setup(), so these already resolve in a descendant's own setup() with no watching required. They are undefined outside a provider, and during server rendering.

A template ref on any of the components resolves to the Ripl object it wraps, typed as that object:

vue
<template>
    <ripl-context ref="context">
        <ripl-circle ref="circle" :cx="50" :cy="50" :radius="20" />
    </ripl-context>
</template>

Notes ​

  • A prop you do not bind is never written, so Ripl's own defaults and a group's cascading state survive. Changing a bound prop back to undefined likewise leaves the last value in place.
  • Props are compared by identity, so an inline :data="{ ... }" or :line-dash="[4, 2]" re-applies on every parent render. Hoist those to a computed. class is normalised first, so every binding form is stable.

Extending ​

@ripl/vue exports the pieces it is built from, so a sibling adapter can wrap a different kind of Ripl object without re-implementing the machinery. @ripl/vue-3d and @ripl/vue-charts are built this way.

ExportUse
defineRiplElement, elementFactoryWrap anything that extends Element as a component.
useElementPropsConstruct an object from bound props and keep it in sync with them.
useForwardedEventsForward a bus's own $events to Vue listeners, subscribing only to bound ones.
useExposedInstanceMake a template ref resolve to the Ripl object rather than a Vue proxy.
registerComponentsRegister components on an app, skipping names already taken.
RIPL_CONTEXT, RIPL_SCENE, RIPL_RENDERER, RIPL_PARENT, RIPL_ELEMENT, RIPL_TREE, RIPL_TRANSITIONThe injection keys the components provide.
readBoundProps, collectChangedProps, partitionProps, applyState, applyFieldsThe prop pipeline.

Two contracts a sibling adapter depends on:

  • This package owns the @ripl/web import, and with it the platform factory: requestAnimationFrame, devicePixelRatio, getDefaultState and measureText. A sibling adapter inherits that through its dependency on @ripl/vue and should not add an @ripl/web import of its own, because a bare side-effect import inside a sideEffects: false package can be tree-shaken away, whereas the value imports here cannot.
  • The injection keys are registry symbols (Symbol.for), so two copies of this module, which the standalone IIFE builds produce, still resolve to the same key.

Plugins compose in any order: createRipl3D() and createRiplCharts() install the core components themselves, and registering a name twice is a no-op.

Documentation ​

Full documentation lives at ripl.run.

License ​

MIT

Classes ​

ClassDescription
RiplTransitionScopeThe live transition phases in scope for a subtree of elements, resolved lazily so that reactive prop changes on the transition component take effect without re-registering its descendants.
RiplTreeCoordinates one context's declarative tree: which group each element belongs to, which paint tier is in effect, and when to repaint.

Interfaces ​

InterfaceDescription
RiplComponentA declarative component wrapping a Ripl object.
RiplContextPropsProps accepted by RiplContext.
RiplDragPayloadThe payload carried by a drag event: the current position plus the gesture's origin and delta.
RiplElementListenersEvery listener an element or group accepts.
RiplElementOptionPropsConstruction options every element accepts, which become plain fields rather than animatable state.
RiplElementPropsOptionsHow a component builds its element from props and writes later changes back onto it.
RiplNodeDefinitionDescribes one element to wrap as a component.
RiplPointerListenersPointer and drag listeners, shared by elements, groups and the context.
RiplPointerPayloadThe payload carried by a pointer event: the pointer position in logical (CSS) pixels.
RiplPropPartitionThe two halves a changed prop batch splits into, and what each half needs applying.
RiplRendererPropsProps accepted by RiplRenderer.
RiplScenePropsProps accepted by RiplScene.
RiplShapePropsPainting options accepted by every path-backed shape.
RiplTickPayloadThe payload carried by the renderer's tick event.
RiplTransitionPhaseOptionsOptions for a single transition phase. Mirrors Ripl's RendererTransitionOptions, except that state is optional: the update phase derives its target from whichever props changed.
RiplTransitionPhasesThe set of phases a transition component contributes to its subtree.
RiplTransitionPropsProps accepted by RiplTransition.
RiplUpdatedPayloadThe payload carried by an updated event: the state property that changed and its new value.

Type Aliases ​

Type AliasDescription
RiplElementPropsThe full prop surface of an element component: the element's own state, the shared construction options and paint flags, and its event listeners.
RiplElementStateResolves an element's state interface, falling back to the shared base state.
RiplFieldWritersPer-field write overrides, for fields an element exposes through a method rather than a setter.
RiplListenerA forwarded Ripl event listener. Receives the event's payload directly, with the underlying Event — carrying target, timestamp and stopPropagation — as a second argument.
RiplTransitionPhaseA transition phase: static options, or a factory invoked per element for staggering.
RiplTransitionPhaseNameThe three phases a transition scope can describe.
RiplWritableAn untyped view of an element, used to write state through its accessors by key.

Variables ​

VariableDescription
ANY_PROPA prop Ripl itself types, declared here only so Vue extracts it from attrs. Runtime validation would duplicate — and inevitably drift from — the element state interfaces.
BASE_STATE_KEYSEvery inheritable visual state property shared by all elements.
BOOLEAN_PROPA boolean prop. default: undefined is load-bearing: without an explicit default Vue casts an absent boolean prop to false, which would override Ripl's own defaults rather than leave them alone. Declaring the default keeps valueless-attribute casting (<ripl-rect clip>) while letting an omitted prop stay omitted.
CONSTRUCTION_ONLY_KEYSOptions an element only reads when it is constructed, so they cannot be synced on a prop change.
CONTEXT_EVENTSEvery event the context component forwards to its Vue listeners.
ELEMENT_EVENTSEvery event an element or group forwards to its Vue listeners.
ELEMENT_OPTION_KEYSConstruction options that become plain fields on the element rather than animatable state.
ELEMENT_STATE_KEYSThe state properties specific to each built-in element, keyed by element type.
NUMBER_PROPA numeric prop, left undefined when absent so it cannot override a Ripl default.
RENDERER_EVENTSEvery event the renderer component forwards to its Vue listeners.
RIPL_CONTEXTInjection key for the rendering context the subtree draws to.
RIPL_ELEMENTInjection key for the nearest enclosing element or group.
RIPL_PARENTInjection key for the group new elements attach themselves to.
RIPL_RENDERERInjection key for the renderer driving the subtree, if one was declared.
RIPL_SCENEInjection key for the scene the subtree belongs to, if one was declared.
RIPL_TRANSITIONInjection key for the transition phases applied to descendant elements.
RIPL_TREEInjection key for the RiplTree owned by the enclosing context component.
RiplArcAn arc or annular segment, the building block of pie, donut and gauge shapes.
RiplCircleA circle rendered at a center point with a given radius.
RiplContextCreates a Ripl rendering context and provides it to its subtree, mounting the canvas into its own root element.
RiplEllipseAn ellipse, optionally drawn as a partial sweep between two angles.
RiplGroupGroups its children, cascading its own state to them and transforming them as a unit.
RiplImageA bitmap drawn from any canvas image source.
RiplLineA straight line between two points.
RiplPathA shape drawn by a custom path renderer within a bounding box.
RiplPolygonA regular polygon with a given number of sides.
RiplPolylineA multi-segment line through a list of points.
RiplRectA rectangle, optionally with rounded corners.
RiplRendererDrives the enclosing scene with a requestAnimationFrame loop, and makes transitions available to its subtree.
RiplSceneCreates a scene bound to the enclosing context and parents its subtree to it.
RiplTextA run of text, optionally laid out along an SVG path.
RiplTransitionAnimates the descendants it wraps as they enter, update and leave, mirroring Vue's own enter-from / leave-to model.
SHAPE_FIELD_KEYSPlain Shape2D fields that change how a shape paints but emit no update event.
SHAPE_FIELDSPlain fields that change how a shape paints. They are also the adapter's only boolean props, so this doubles as the set needing Vue's valueless-attribute casting.

Functions ​

FunctionDescription
applyFieldsWrites the plain fields, which emit nothing, so a repaint has to be requested separately.
applyStateWrites state values through the element's accessors, which mark it dirty and emit updated.
collectChangedPropsReads the bound props, folding any that differ from applied into a changed batch.
createPropsBuilds a Vue runtime props declaration from a list of prop names.
createRiplCreates the Vue plugin that registers every Ripl component globally, so templates can use <ripl-circle> and <RiplCircle> without importing them.
createRiplTreeCreates a RiplTree, the per-context coordinator for a declarative Ripl graph.
defineRiplElementBuilds a declarative component for a Ripl element.
elementFactoryAdapts a typed element factory to RiplNodeDefinition's untyped create hook.
partitionPropsPartitions changed props into animatable state and plain fields.
readBoundPropsReads the props that were actually bound; an unbound prop must not overwrite a Ripl default.
registerComponentsRegisters components on an app, skipping any name already taken.
resolveClassNamesSplits any of Vue's class binding forms into individual class names.
useElementPropsConstructs an element from its bound props and keeps it in sync with them.
useExposedInstanceResolves a template ref on this component to the Ripl object it wraps rather than to Vue's component proxy, so <ripl-context ref="context"> hands back the Context.
useForwardedEventsForwards an event bus's events to Vue listeners, subscribing only to those a listener is actually bound to.
useRiplContextReturns the rendering context provided by the nearest context component.
useRiplElementReturns the nearest enclosing element, group or scene.
useRiplRendererReturns the renderer provided by the nearest renderer component.
useRiplSceneReturns the scene provided by the nearest scene component.