@nitesh-tyagi/vectorflow
v1.0.3
Published
A minimal, JSON-driven SVG pose animation library
Maintainers
Readme
VectorFlow
A minimal, JSON-driven SVG pose animation library.
What is VectorFlow?
VectorFlow loads an SVG containing multiple pose states and a JSON config defining routes and actions, then animates smoothly between poses using attribute interpolation. No path morphing, no crossfades — just clean, transform-and-attribute-based transitions.
Features:
- Declarative JSON config — define states, routes, and actions
- Smooth SVG attribute interpolation (position, size, color, transform)
- Sequence and loop action types with cancellation support
- Clean public API with event system
- Zero-config playback — just load SVG + JSON and go
- ~29 KB UMD bundle (including anime.js)
Table of Contents
- Quick Start
- Live Demo
- Preparing Your SVG
- Writing the JSON Config
- Sample Files
- API Reference
- Full Example
- Links
- License
Quick Start
Install
npm install @nitesh-tyagi/vectorflowBrowser (UMD)
<script src="https://unpkg.com/@nitesh-tyagi/vectorflow/dist/vectorflow.umd.js"></script>
<script>
const vf = new VectorFlow({
svgElement: document.querySelector('svg'),
json: config,
});
vf.play('shuffle');
</script>ES Module
import VectorFlow from '@nitesh-tyagi/vectorflow';
const vf = new VectorFlow({
svgElement: document.querySelector('#my-svg'),
json: configObject, // or a JSON string
});
vf.play('left'); // play an action
vf.play('shuffle'); // auto-cancels previous, starts new
vf.stop(); // cancel current playbackLive Demo
Try VectorFlow instantly — no install required:
- Download the sample SVG and sample JSON
- Open the Demo Player
- Upload both files and click Load & Play
- Use the action buttons to trigger animations
The demo player source is at Nitesh-WebMuffin/SVG-Animator.
Preparing Your SVG
VectorFlow discovers animation states and parts directly from your SVG markup. Follow these rules when creating your SVG:
1. Define State Groups
Wrap each pose/state in a <g> (group) element with an id of the form state_{name}:
<g id="state_left"> <!-- state name: "left" -->
...
</g>
<g id="state_center"> <!-- state name: "center" -->
...
</g>
<g id="state_right"> <!-- state name: "right" -->
...
</g>- The
state_prefix is required. Everything after it becomes the state name used in your JSON config. - You can have as many states as you need.
2. Tag Parts with data-part
Inside each state group, mark the elements that should be animated with a data-part attribute:
<g id="state_left">
<rect data-part="body" x="20" y="80" width="40" height="40" fill="red" />
<circle data-part="head" cx="40" cy="60" r="15" fill="orange" />
</g>- VectorFlow matches parts by name across states. A part called
"body"instate_leftwill animate to the"body"instate_right. - Part names must be identical across all states.
3. Keep Part Sets Consistent
Every state group must contain the exact same set of data-part names. If state_left has parts body and head, then state_center and state_right must also have body and head.
VectorFlow will throw an error if part sets don't match:
VectorFlow: Part mismatch between states. State "left" has parts [body,head]
but state "right" has parts [body].4. Animatable Attributes
VectorFlow interpolates the following SVG attributes and properties between states:
| Type | Attributes / Properties |
|------|------------------------|
| Numeric | x, y, width, height, rx, ry, cx, cy, r, opacity |
| Colors | fill, stroke (hex #RGB/#RRGGBB or rgb()) |
| Transforms | translate(x,y), scale(x,y), rotate(angle) |
Design your states so that the visual differences are expressed through these attributes. For example, to move a shape left, change its x; to recolor it, change its fill.
5. Minimal SVG Template
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- State: left -->
<g id="state_left">
<rect data-part="shape" x="20" y="80" width="40" height="40" fill="#e74c3c" />
</g>
<!-- State: center -->
<g id="state_center">
<rect data-part="shape" x="80" y="80" width="40" height="40" fill="#3498db" />
</g>
<!-- State: right -->
<g id="state_right">
<rect data-part="shape" x="140" y="80" width="40" height="40" fill="#2ecc71" />
</g>
</svg>Tip: At runtime, VectorFlow hides all state groups and creates a single
<g id="live_character">clone to animate. Your original state groups serve as snapshots defining each pose.
Writing the JSON Config
The JSON config tells VectorFlow which state to start in, how to transition between states, and what actions are available.
Top-Level Structure
{
"name": "my-animation",
"initial_state": "center",
"ms": 120,
"routes": { ... },
"actions": { ... }
}| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | No | A label for this config (for your own reference). |
| initial_state | string | Yes | The state name to start in (must match an id="state_..." in your SVG). |
| ms | number | No | Default animation duration in milliseconds (default: 120). |
| routes | object | Yes | Defines how to travel between states. |
| actions | object | Yes | Named animation sequences/loops that users can trigger. |
Routes
Routes define the path the animation takes when transitioning to a target state. Each route key is a target and its value maps source states (or "*" for any source) to an array of intermediate + destination states.
"routes": {
"left": {
"*": ["left"],
"right": ["center", "left"]
},
"right": {
"*": ["right"],
"left": ["center", "right"]
},
"center": {
"*": ["center"],
"ms": 60
}
}How route resolution works:
- When the engine needs to go to
"left"from the current state, it looks uproutes["left"]. - If there's a specific entry for the current state (e.g.
"right": ["center", "left"]), it follows that path — first animate tocenter, then toleft. - If no specific entry exists, it uses the wildcard
"*"path. - If no route exists at all, VectorFlow jumps directly to the target state.
Route-level ms: You can set a per-route ms to override the global duration for transitions to that target state.
Route keys vs state names: Route keys don't have to match a state name. You can create alias routes that resolve to real states:
"routes": {
"direct-left": { "*": ["left"] },
"direct-right": { "*": ["right"] }
}Here "direct-left" is a route key (not a state) that resolves to state "left". This is useful for defining multiple paths to the same state.
Actions
Actions are the named animations you trigger via vf.play('actionName'). Each action has a type — either "sequence" (play once) or "loop" (repeat).
"actions": {
"left": { "type": "sequence", "order": ["left"] },
"right": { "type": "sequence", "order": ["right"] },
"center": { "type": "sequence", "order": ["center"] },
"shuffle": {
"type": "loop",
"mode": "direct",
"order": ["left", "center", "right", "center"],
"ms": 80,
"count": 99999
}
}| Field | Type | Default | Description |
|-------|------|---------|-------------|
| type | "sequence" \| "loop" | "sequence" | sequence plays once; loop repeats count times. |
| order | string[] | — | Array of route keys or state names to visit in order. |
| ms | number | — | Override animation duration for this action (takes highest priority). |
| mode | "direct" | — | If "direct", skips intermediate route steps — jumps straight to the final resolved state. |
| count | number | 99999 | For loops: how many times to repeat the order cycle. |
order items can be route keys or state names. The engine resolves each entry through the routes table. If an entry matches a route key, VectorFlow follows that route's path. If it matches a state name directly, it goes there.
Duration Precedence
When determining how long a step takes, VectorFlow checks in this order:
action.ms > routes[target].ms > json.ms > 120ms (fallback)Full JSON Example
{
"name": "character",
"initial_state": "idle",
"ms": 150,
"routes": {
"idle": { "*": ["idle"] },
"walk-left": { "*": ["lean-left", "walk-left"], "walk-right": ["idle", "lean-left", "walk-left"] },
"walk-right": { "*": ["lean-right", "walk-right"], "walk-left": ["idle", "lean-right", "walk-right"] },
"lean-left": { "*": ["lean-left"], "ms": 80 },
"lean-right": { "*": ["lean-right"], "ms": 80 }
},
"actions": {
"go-left": { "type": "sequence", "order": ["walk-left"] },
"go-right": { "type": "sequence", "order": ["walk-right"] },
"reset": { "type": "sequence", "order": ["idle"], "ms": 200 },
"patrol": { "type": "loop", "order": ["walk-left", "idle", "walk-right", "idle"], "count": 10 }
}
}Sample Files
Ready-to-use sample files are available in the demo-files folder:
| File | Description |
|------|-------------|
| Shapes.svg | SVG with 3 pose states: left, center, right |
| Shapes.json | JSON config with routes, actions, and a shuffle loop |
Download both and pass them to VectorFlow, or upload them to the Live Demo Player.
API Reference
Constructor
const vf = new VectorFlow({ svgElement, json });| Param | Type | Description |
|-------|------|-------------|
| svgElement | SVGElement \| string | An SVG DOM element or raw SVG string. |
| json | object \| string | VectorFlow JSON config (object or JSON string). |
Properties
| Property | Type | Description |
|----------|------|-------------|
| vf.states | string[] | All discovered state names from the SVG. |
| vf.currentState | string | The current active state. |
| vf.actions | object | Actions config from the JSON. |
| vf.isPlaying | boolean | Whether an action is currently playing. |
| vf.currentAction | string \| null | Name of the playing action, or null. |
| vf.svgElement | SVGElement | The SVG element being animated. |
Methods
| Method | Returns | Description |
|--------|---------|-------------|
| vf.play(actionName) | Promise<void> | Play a named action. Auto-cancels any running action. |
| vf.stop() | void | Cancel current playback immediately. |
| vf.on(event, callback) | VectorFlow | Register an event listener (chainable). |
| vf.off(event, callback) | VectorFlow | Remove an event listener (chainable). |
| vf.destroy() | void | Stop playback, restore original SVG state groups, remove listeners. |
Events
| Event | Callback Arg | Description |
|-------|-------------|-------------|
| stateChange | string | Fired after each intermediate step completes (receives the new state name). |
| actionStart | string | Fired when an action begins playing. |
| actionEnd | string | Fired when an action finishes naturally or is cancelled. |
| error | Error | Fired on runtime errors during playback. |
vf.on('stateChange', (state) => {
console.log('Now in state:', state);
});
vf.on('actionEnd', (actionName) => {
console.log(`Action "${actionName}" finished`);
});Full Example
SVG (character.svg)
<svg viewBox="0 0 300 200" xmlns="http://www.w3.org/2000/svg">
<g id="state_left">
<rect data-part="body" x="30" y="60" width="50" height="80" rx="8" fill="#e74c3c" />
<circle data-part="head" cx="55" cy="45" r="18" fill="#f5b041" />
</g>
<g id="state_center">
<rect data-part="body" x="125" y="60" width="50" height="80" rx="8" fill="#3498db" />
<circle data-part="head" cx="150" cy="45" r="18" fill="#f5b041" />
</g>
<g id="state_right">
<rect data-part="body" x="220" y="60" width="50" height="80" rx="8" fill="#2ecc71" />
<circle data-part="head" cx="245" cy="45" r="18" fill="#f5b041" />
</g>
</svg>JSON (character.json)
{
"name": "character",
"initial_state": "center",
"ms": 150,
"routes": {
"left": { "*": ["left"], "right": ["center", "left"] },
"right": { "*": ["right"], "left": ["center", "right"] },
"center": { "*": ["center"], "ms": 80 }
},
"actions": {
"go-left": { "type": "sequence", "order": ["left"] },
"go-right": { "type": "sequence", "order": ["right"] },
"reset": { "type": "sequence", "order": ["center"] },
"patrol": {
"type": "loop",
"order": ["left", "center", "right", "center"],
"ms": 100,
"count": 5
}
}
}JavaScript
import VectorFlow from '@nitesh-tyagi/vectorflow';
// Load SVG (already in the DOM) and JSON config
const vf = new VectorFlow({
svgElement: document.querySelector('#character-svg'),
json: characterConfig,
});
// Wire up buttons
document.querySelector('#btn-left').onclick = () => vf.play('go-left');
document.querySelector('#btn-right').onclick = () => vf.play('go-right');
document.querySelector('#btn-reset').onclick = () => vf.play('reset');
document.querySelector('#btn-patrol').onclick = () => vf.play('patrol');
document.querySelector('#btn-stop').onclick = () => vf.stop();
// Listen for state changes
vf.on('stateChange', (state) => {
document.querySelector('#current-state').textContent = state;
});
// Clean up when done
window.addEventListener('beforeunload', () => vf.destroy());Links
| Resource | URL | |----------|-----| | npm | @nitesh-tyagi/vectorflow | | Live Demo | vectorflow.webmuffin.io | | Demo Source | Nitesh-WebMuffin/SVG-Animator | | Sample Files | demo-files/ | | Docs | docs/ |
License
MIT
