Neural Net Viz
A graph in four columns of 3, 5, 4 and 3 nodes, each node linked to every node of the next column: the 47 links pulse in width and opacity, the 15 nodes swell and glow each at its own pace — a 2D canvas sharp on Retina, a graph that stays whole at any frame size, a loop that stops off screen, a still image when the user reduces motion. Decorative pulses: no neural network computation.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Liquid has 31 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .fx-0413-root frame is 300 px tall and full width, and the canvas fits it (display: block, 100% × 100%). Internal size = displayed size × devicePixelRatio, drawing in CSS pixels through setTransform: measured at density 2, 1280 × 600 for 640 × 300. Node positions are kept as fractions of the frame and converted to pixels on every frame: columns at 20, 40, 60 and 80% of the width, nodes of a column of n at (i + 1) / (n + 1) of the height. A ResizeObserver follows the frame; shrunk to 360 px wide, the graph fits between 62 and 298 px (measured in Chromium 151, Firefox 153 and WebKit 26).
Each link gets a random weight between 0 and 1 at start, which only offsets its pulse: its intensity follows (sin(2 l + 10 × weight) + 1) / 2, giving an opacity of 0.1 to 0.4 and a width of 0.5 to 2 px, in indigo rgb(99, 102, 241). Each node has its own phase: its radius goes from 6 to 12 px and its radial gradient goes from nearly opaque violet rgb(139, 92, 246) at the center to indigo at 10% on the edge. Time l moves by 0.02 per frame: a link pulses in about 2.6 s at 60 frames per second, a node in 5.2 s. The canvas is cleared on every frame; the background is the root's.
The graph reads from left to right because links only join two neighboring columns: the [3, 5, 4, 3] array describes the whole structure, and columns spread out on their own if you change their number. The requestAnimationFrame loop stops as soon as an IntersectionObserver sees the canvas leave the screen (measured: 0 frames per second off screen against 120 on screen) and resumes when it comes back.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, the graph is drawn once, nodes and links at varied intensities, and no longer moves; it is redrawn only if the frame changes size. Measured in all three engines: 0% of pixels changed in 500 ms. - The diagram must read as text: the canvas has no accessible name. If the graph describes a real structure (a course, a heating network, a wiring plan), write the columns and their items in HTML, like the examples' labels, or in a list right after.
- Contrast: the faintest links drop to 10% opacity on the dark background; they are mood lines. If a specific link must be seen, draw it separately, thicker and solid.
- Speed tied to the screen: time moves per frame; at 120 Hz, pulses go twice as fast.
Browser compatibility
2D canvas + 2.6 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same pulses, internal size doubled at density 2, stable height after 20 resizes, whole graph at 360 px wide, loop stopped off screen, still image under reduced motion. Measured cost: 0.1 ms of JavaScript per frame in Chromium, about 1 ms in Firefox. ResizeObserver exists since Chrome 64, Firefox 69 and Safari 13.1.
Without JavaScript, the frame stays bluish black, with no graph: keep the labels in HTML so the diagram stays understandable. Without IntersectionObserver, the loop also runs off screen; without ResizeObserver, the graph follows window resizing only.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0413-root">
<div class="ai-neural-container">
<canvas class="ai-neural-canvas" width="393" height="280"></canvas>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Columns (JS — const a = [3, 5, 4, 3]) |
4 columns of 3, 5, 4 and 3 nodes | [2, 4, 4, 2] or [3, 6, 6, 6, 2]: columns spread across the width on their own and each node links to every node of the next column. Beyond 8 nodes per column on 300 px, nodes touch. |
Link color (JS — rgba(99, 102, 241, …), two places) |
indigo | Change the first three numbers in the link and on the node edge; the opacity that follows is computed, do not replace it. |
Node color (JS — rgba(139, 92, 246, …)) |
violet | The center of the radial gradient; a light color (94, 234, 212) gives glowing nodes. |
Node size (JS — 6 + 6 * t.activation) |
6 to 12 px radius | 4 + 3 * t.activation for a quieter diagram, 8 + 10 * t.activation for nodes that breathe strongly. |
Link width and opacity (JS — .5 + 1.5 * a and .1+.3*a) |
0.5 to 2 px, 10 to 40% | .2+.5*a for opacity keeps every link readable, even at its lowest. |
Pace (JS — l += .02) |
0.02 per frame: link in 2.6 s, node in 5.2 s at 60 frames per second | 0.01 halves everything. The 2 * l factor makes links pulse twice as fast as nodes. |
Height (CSS — .fx-0413-root { height: 300px }) |
300 px | The graph stretches with the frame; nodes keep their size in pixels. |
FAQ
left: 20%, top: 25%): they stay aligned at every size, as in the three examples.(Math.sin(2 * l - 1.5 * o.layer) + 1) / 2 in the computation of a starts the pulse on the left and shifts it column by column. The effect stays decorative, but then reads as a flow crossing the graph.