Definition
A rendering pattern for nodes drawn on Canvas (dots in graph and network visualizations) that computes decorative values such as color, size, and twinkle so the same node ID always yields the same values rather than using Math.random() every frame, and resets Canvas 2D context state (globalAlpha, shadowBlur, etc.) to defaults immediately after drawing.
Why it matters
Force-directed graph libraries usually draw nodes with Canvas 2D (the browser API that paints directly into a pixel buffer). Because nodes are not DOM elements, you cannot attach CSS @keyframes to individual nodes. Choosing colors with Math.random() makes the same document node appear in different colors and rhythms on every refresh, so the graph looks unstable.
To add twinkle and glow, you must compute opacity over time inside the node draw callback. If you set globalAlpha or shadowBlur without restoring them afterward, labels, edges, and subsequent nodes blur as a side effect. For users with prefers-reduced-motion (the accessibility setting to reduce animation), twinkle must stop.
How it works
- ID → hash: Convert a document ID string into a small integer seed (cryptographic hash is unnecessary; visual spread is the goal).
- Attach visual meta: When a node joins graph data, compute color (hue/palette index), twinkle period, and phase once and store them on the node object.
- Draw callback: Each frame, update opacity only, e.g.
alpha = base + amplitude * sin(2π·t/period + phase). Keep the color palette fixed. - Glow: Draw the center circle sharply; use only
shadowColorandshadowBlurfor a soft halo. - State restore: Immediately after drawing one node, reset with
ctx.globalAlpha = 1,ctx.shadowBlur = 0, etc. - Reduced motion: If
matchMedia('(prefers-reduced-motion: reduce)')matches, fix alpha at 1 and enable the graph library's automatic redraw (autoPauseRedraw) to stop the twinkle loop. - Legend separation: If node color does not encode category, use neutral colors for legend dots.
Practical application
- Reuse the existing graph redraw loop rather than adding a separate
requestAnimationFrameloop. - For visual regression, capture with reduced motion on/off after waiting for force layout to stabilize at a 320px viewport.
Trade-offs
- An ID-hash palette can produce similar colors on collision, but that is acceptable at hundreds of nodes.
shadowBlursuits small radii and low node counts. With thousands of nodes, paint cost can grow.- Canvas animation runs on the main thread. If it is heavy, reduce node count and effect strength, or consider a WebGL layer.
When not to use
- If you only have dozens of nodes and DOM/SVG is enough, custom Canvas draw adds maintenance cost without benefit.
- If color must encode category, ID-hash color conflicts with the requirement — separate legend and data channels, or map an explicit color scale.
- Ignoring reduced motion and keeping twinkle is an accessibility violation.
Common mistakes
- Failing to reset
globalAlphaandshadowBlur, so text labels blur overall. - Re-hashing the ID string every frame wastes CPU — attach meta at attach time.
- Background twinkles via CSS while nodes stay static, so UX feels mismatched — the Canvas path needs its own twinkle logic.
- Misunderstanding
autoPauseRedrawso redraw stops in normal mode.
Related concepts
- bfs-document-relation-graph — local graph data and BFS traversal
- critical-rendering-path — Canvas paint runs on the main-thread render path
- css-view-timeline-entry-animation — CSS timelines do not apply to non-DOM Canvas nodes