Flow: Edge Sequence
Mermaid
graph LR
A[Start] --> B{Branch}
B -->|yes| C[Do a thing]
B -->|no| D[Different path]
C --> E[Finish]
D --> E
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<div id="app"></div>
<script type="module">
import animate from 'mermaid-animate';
const el = document.getElementById('app');
const code = `
graph LR
A[Start] --> B{Branch}
B -->|yes| C[Do a thing]
B -->|no| D[Different path]
C --> E[Finish]
D --> E
`;
animate.initialize({ theme: 'light' });
await animate.flow.draw(el, code, { draw: { step: 1.0, gap: 0.25 }, loop: true });
</script>
Flow: Click-to-Traverse (waves)
Tip: Click any node to fan‑out traversal waves.
Mermaid
graph LR
A[Request] --> B{Router}
B -->|auth| C[Auth]
B -->|cache| D[Cache]
C --> E[Service]
D --> E
E --> F[DB]
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<style> svg{max-width:900px} </style>
<div id="app"></div>
<script type="module">
import animate, { renderMermaid, ensurePath, animateStrokeDraw, listFlowEdgesFromSvgRaw } from 'mermaid-animate';
import gsap from 'gsap';
const el = document.getElementById('app');
const code = `
graph LR
A[Request] --> B{Router}
B -->|auth| C[Auth]
B -->|cache| D[Cache]
C --> E[Service]
D --> E
E --> F[DB]
`;
animate.initialize({ theme: 'light' });
const { svg } = await renderMermaid(el, code);
const raw = listFlowEdgesFromSvgRaw(svg).map(e => ({ ...e, path: ensurePath(e.path) }));
const out = new Map(); raw.forEach(e => { if (!out.has(e.from)) out.set(e.from, []); out.get(e.from).push(e); });
function waves(start){
const tl = gsap.timeline({ paused:true });
const seen=new Set([start]); let frontier=[start]; const step=0.8,gap=0.2;
while(frontier.length){
const next=[]; const layer=[];
for(const u of frontier){ for(const e of (out.get(u)||[])){ layer.push(e.path); if(!seen.has(e.to)){ seen.add(e.to); next.push(e.to);} }}
if(layer.length){ const pos = tl.duration(); layer.forEach(p => animateStrokeDraw(tl,p,{duration:step},pos)); tl.to({}, {duration:gap}); }
frontier = next;
}
return tl.play(0);
}
// bind clicks on nodes
svg.querySelectorAll('g.node').forEach(n => {
n.style.cursor='pointer';
n.addEventListener('click', () => waves(n.id.replace(/^flowchart-([^-]+)-.*/, '$1')));
});
// autoplay from first root and loop
const roots = (() => { const map=new Map(); raw.forEach(e=>{map.set(e.to,true); if(!map.has(e.from)) map.set(e.from,false);}); return [...map].filter(([,v])=>!v).map(([k])=>k); })();
const start = roots[0] || raw[0]?.from; let tl = waves(start); tl.eventCallback('onComplete', () => setTimeout(() => { tl.kill(); tl = waves(start); }, 1000));
</script>
Flow: Advanced Fan-out (auto roots)
Mermaid
graph LR
A --> B
A --> C
B --> D
C --> D
D --> E
D --> F
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<div id="app"></div>
<script type="module">
import animate, { renderMermaid, ensurePath, animateStrokeDraw, listFlowEdgesFromSvgRaw } from 'mermaid-animate';
import gsap from 'gsap';
const el = document.getElementById('app');
const code = `
graph LR
A --> B
A --> C
B --> D
C --> D
D --> E
D --> F
`;
animate.initialize({ theme: 'light' });
const { svg } = await renderMermaid(el, code);
const edges = listFlowEdgesFromSvgRaw(svg).map(e => ({ ...e, path: ensurePath(e.path) }));
const roots = (() => { const m=new Map(); edges.forEach(e=>{m.set(e.to,true); if(!m.has(e.from)) m.set(e.from,false);}); return [...m].filter(([,v])=>!v).map(([k])=>k); })();
const out = new Map(); edges.forEach(e => { if (!out.has(e.from)) out.set(e.from, []); out.get(e.from).push(e); });
const master = gsap.timeline({ paused:true, repeat:-1, repeatDelay:1 });
const step = 0.7, gap = 0.2;
roots.forEach(r => {
const child = gsap.timeline();
let frontier=[r]; const seen=new Set([r]);
while(frontier.length){ const next=[]; const wave=[]; for(const u of frontier){ for(const e of (out.get(u)||[])){ wave.push(e.path); if(!seen.has(e.to)){ seen.add(e.to); next.push(e.to);} }} const pos=child.duration(); wave.forEach(p=>animateStrokeDraw(child,p,{duration:step},pos)); child.to({}, {duration:gap}); frontier=next; }
master.add(child, 0);
});
master.play(0);
</script>
Flow: Splitting Tracers (continuous)
Mermaid
graph LR
A --> B
A --> C
B --> D
C --> E
D --> F
E --> F
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<div id="app"></div>
<script type="module">
import animate, { renderMermaid, ensurePath, createTracerDot, listFlowEdgesFromSvgRaw } from 'mermaid-animate';
import { MotionPathPlugin } from 'gsap/all';
import gsap from 'gsap';
gsap.registerPlugin(MotionPathPlugin);
const el = document.getElementById('app');
const code = `
graph LR
A --> B
A --> C
B --> D
C --> E
D --> F
E --> F
`;
animate.initialize({ theme: 'light' });
const { svg } = await renderMermaid(el, code);
const edges = listFlowEdgesFromSvgRaw(svg).map(e => ({ ...e, path: ensurePath(e.path) }));
const out = new Map(); edges.forEach(e => { if (!out.has(e.from)) out.set(e.from, []); out.get(e.from).push(e); });
const roots = (() => { const m=new Map(); edges.forEach(e=>{m.set(e.to,true); if(!m.has(e.from)) m.set(e.from,false);}); return [...m].filter(([,v])=>!v).map(([k])=>k); })();
const parent = svg.querySelector('g.root') || svg;
let token = 0; let timer = null;
function advance(dot, node, t){
const es = out.get(node) || [];
if (es.length === 0) { dot.remove(); return; }
const step = 0.8;
es.forEach((e,i) => {
const d = i === 0 ? dot : dot.cloneNode(true);
if (i > 0) parent.appendChild(d);
try { gsap.killTweensOf(d); } catch {}
d.removeAttribute('transform'); d.removeAttribute('data-svg-origin'); gsap.set(d, { x:0, y:0, rotation:0 });
d.setAttribute('cx','0'); d.setAttribute('cy','0'); d.setAttribute('visibility','visible');
gsap.to(d, { duration: step, ease: 'power1.inOut', motionPath: { path: e.path, autoRotate:false }, overwrite:'auto', immediateRender:false,
onComplete: () => { if (t!==token) return; advance(d, e.to, t); } });
});
}
function run(){
token++; if (timer) { clearTimeout(timer); timer = null; }
svg.querySelectorAll('circle[data-follower="1"]').forEach(n => n.remove());
roots.forEach(r => advance(createTracerDot(svg, 4, '#e85d04', parent), r, token));
timer = setTimeout(() => { if (token===token) run(); }, 2000);
}
run();
</script>
Flow: Heatmap (pulse)
Mermaid
graph LR
A --> B
A --> C
B --> D
C --> D
D --> E
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<div id="app"></div>
<script type="module">
import animate, { renderMermaid } from 'mermaid-animate';
import gsap from 'gsap';
const el = document.getElementById('app');
const code = `
graph LR
A --> B
A --> C
B --> D
C --> D
D --> E
`;
animate.initialize({ theme: 'light' });
const { svg } = await renderMermaid(el, code);
Array.from(svg.querySelectorAll('g.edgePaths path')).forEach((p,i) => {
const hue = (i*47)%360; p.style.stroke = `hsl(${hue},80%,50%)`;
gsap.to(p, { strokeWidth: 3, duration: 0.6, yoyo: true, repeat: -1, ease: 'sine.inOut' });
});
</script>
Sequence: Tracer loop
Mermaid
sequenceDiagram
participant A as Client
participant B as API
participant C as DB
A->>B: Request
B->>C: Query
C-->>B: Rows
B-->>A: Response
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<div id="app"></div>
<button id="more">Add another tracer!</button>
<script type="module">
import animate from 'mermaid-animate';
import { MotionPathPlugin } from 'gsap/all';
import gsap from 'gsap';
gsap.registerPlugin(MotionPathPlugin);
const el = document.getElementById('app');
const code = `
sequenceDiagram
participant A as Client
participant B as API
participant C as DB
A->>B: Request
B->>C: Query
C-->>B: Rows
B-->>A: Response
`;
animate.initialize({ theme: 'light' });
await animate.sequence.tracer(el, code, { tracer: { count: 1 } });
document.getElementById('more').addEventListener('click', async () => {
const svg = el.querySelector('svg'); if (!svg) return;
const sels = ['path[marker-end]','line[marker-end]','polyline[marker-end]','path[marker-start]','line[marker-start]','polyline[marker-start]'];
const paths = Array.from(svg.querySelectorAll(sels.join(','))).map(el => MotionPathPlugin.convertToPath(el, true)).map(r => Array.isArray(r)? r[0] : r);
const dot = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg','circle'); dot.setAttribute('r','4'); dot.setAttribute('fill','hotpink'); dot.setAttribute('data-follower','1'); dot.setAttribute('visibility','hidden');
(svg.querySelector('g.root')||svg).appendChild(dot);
const tl = gsap.timeline({ paused:true }); paths.forEach(p => tl.to(dot, { duration: 1.0, ease: 'power1.inOut', motionPath: { path: p, align: p, autoRotate: false }, onStart: () => dot.setAttribute('visibility','visible') })); tl.eventCallback('onComplete', () => tl.play(0)); tl.play(0);
});
</script>
State: Step‑Through Tracer
Tip: Click a state to choose the starting transition.
Mermaid
stateDiagram-v2
[*] --> Idle
Idle --> Working: Start
Working --> Idle: Stop
Working --> Error: Fail
Error --> Idle: Reset
mermaid-animate
<!doctype html>
<meta charset="utf-8" />
<div id="app"></div>
<script type="module">
import animate, { renderMermaid, ensurePath, createTracerDot, parseTransitions } from 'mermaid-animate';
import gsap from 'gsap';
import { MotionPathPlugin } from 'gsap/all';
gsap.registerPlugin(MotionPathPlugin);
const el = document.getElementById('app');
const code = `
stateDiagram-v2
[*] --> Idle
Idle --> Working: Start
Working --> Idle: Stop
Working --> Error: Fail
Error --> Idle: Reset
`;
animate.initialize({ theme: 'light' });
const { svg } = await renderMermaid(el, code);
const transitions = parseTransitions(code);
const sels = ['path[marker-end]','line[marker-end]','polyline[marker-end]','path[marker-start]','line[marker-start]','polyline[marker-start]'];
const connectors = Array.from(svg.querySelectorAll(sels.join(','))).map(el => ensurePath(el));
let paths = connectors.slice();
if (paths.length === transitions.length + 1) paths = paths.slice(1);
else if (paths.length > transitions.length) paths = paths.slice(0, transitions.length);
const nodes = Array.from(svg.querySelectorAll('g.node'));
const parent = svg.querySelector('g.root') || svg;
let token = 0; let current = null;
function rotate(arr, k){ const n = arr.length; if (!n) return arr.slice(); const i=((k%n)+n)%n; return arr.slice(i).concat(arr.slice(0,i)); }
function run(start){ token++; if (current){ try{ current.kill(); }catch{} current=null; } svg.querySelectorAll('circle[data-follower="1"]').forEach(n=>n.remove()); const dot=createTracerDot(svg,4,getComputedStyle(svg).getPropertyValue('--ma-accent')||'#1b74e4', parent); const tl=gsap.timeline({paused:true}); current=tl; rotate(paths,start).forEach(p=> tl.to(dot,{duration:0.9,ease:'power1.inOut',motionPath:{path:p,align:p,autoRotate:false}, onStart:()=>dot.setAttribute('visibility','visible')})); tl.eventCallback('onComplete',() => setTimeout(() => run(start), 1000)); tl.play(0); }
run(0);
nodes.forEach(n => { n.style.cursor='pointer'; n.addEventListener('click', () => { const label=(n.textContent||'').trim(); const idx=transitions.findIndex(t=>t.from===label); if (idx>=0) run(idx); }); });
</script>
Mapping heuristic: we match transitions to visible connectors by order; if there’s one extra connector (usually [*] → first state), we skip the first.