22. januar 2026.
Kako animirati neuronsku mrežu u SVG-u
Mala animirana neuronska mreža sa naslovne strane, rastavljena na delove — signali putuju kroz veze, čvorovi pulsiraju, sve u čistom SVG-u i CSS-u.
Animacije su ilustrativne — urađene za potrebe prezentacije.
Mala animirana mreža sa naslovne strane počela je kao mali eksperiment: može li neuronska mreža da deluje kao da razmišlja koristeći samo SVG putanje i CSS keyframe-ove, bez ijednog reda JavaScripta? Ovo je taj eksperiment, izdvojen u posebnu stranicu.
Sve se svodi na dva jednostavna trika. Signali koji putuju kroz veze zapravo
su samo jedna kratka crtica koja klizi duž putanje. Postaviš
pathLength="1" da svaka putanja bude normalizovana na istu dužinu, dodaš
kratak stroke-dasharray i animiraš stroke-dashoffset — i izgleda kao da
paket putuje između neurona. Kada malo razmakneš kašnjenja između putanja,
dobiješ utisak saobraćaja umesto metronoma.
Čvorovi samo pulsiraju — jednostavna animacija veličine i providnosti na aktivnim čvorovima. Kašnjenja su usklađena sa signalima, pa izgleda kao da puls nastaje baš u trenutku kada signal stigne. Naravno, nije tako. To su dve potpuno nezavisne animacije koje se samo lepo poklope. Veći deo stilizovane animacije upravo je u tome: ne simuliraš stvarnost, već složiš nekoliko vizuelnih nagoveštaja od kojih oko samo napravi priču.
Kao i sve ostalo na sajtu, boje preuzima iz tokena teme, pa automatski radi u
svetlom i tamnom režimu. Ako je uključen prefers-reduced-motion, animacija
se zamrzne u miran statičan dijagram. Malo koda, bez biblioteka i sasvim
dovoljno da zadovoljim radoznalost.
Ceo eksperiment staje u jedan HTML fajl — kopiraj ga ili preuzmi:
Izvorni kod — neural-net.html(131 linija)Preuzmi .html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Neural net — SVG + CSS, no JavaScript</title>
<!--
A tiny animated neural network in pure SVG + CSS — no JavaScript.
From slavkobabic.com/experiments — copy freely.
The two tricks:
- signals: a short dash pushed around each path (pathLength="1" normalises
every path to the same length; animating stroke-dashoffset slides the dash)
- nodes: a scale/opacity pulse with the same staggered delays, so the pulse
*looks* caused by the arriving signal.
-->
<style>
:root {
color-scheme: dark;
--bg: #0f0e17;
--bg-raised: #1a1826;
--fg-muted: #a09cb0;
--border: #312e40;
--accent: #a78bfa;
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-color-scheme: light) {
:root {
color-scheme: light;
--bg: #fbfaff;
--bg-raised: #f1effa;
--fg-muted: #5a5568;
--border: #dedbe8;
--accent: #6d28d9;
}
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: var(--bg);
}
.net {
width: min(90vw, 480px);
height: auto;
}
.edge {
stroke: var(--border);
stroke-width: 1.2;
fill: none;
}
/* Signal packets travelling along a synapse: a short dash pushed around
the normalized (pathLength=1) path. */
.flow {
stroke: var(--accent);
stroke-width: 1.6;
fill: none;
stroke-dasharray: 0.12 0.88;
animation: netflow 3.2s linear infinite;
opacity: 0.9;
}
.flow.f2 { animation-delay: -1.1s; }
.flow.f3 { animation-delay: -2.2s; }
.node {
fill: var(--bg-raised);
stroke: var(--fg-muted);
stroke-width: 1.2;
}
.node.hot {
fill: var(--accent);
stroke: var(--accent);
transform-box: fill-box;
transform-origin: center;
animation: netpulse 3.2s var(--ease-out) infinite;
}
.node.hot.p2 { animation-delay: -1.1s; }
.node.hot.p3 { animation-delay: -2.2s; }
@keyframes netflow {
to { stroke-dashoffset: -1; }
}
@keyframes netpulse {
0%, 100% { transform: scale(1); opacity: 0.75; }
50% { transform: scale(1.35); opacity: 1; }
}
/* Reduced motion: freeze into a calm static diagram. */
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}
</style>
</head>
<body>
<svg class="net" viewBox="0 0 300 220" aria-hidden="true">
<path class="edge" d="M40 40 L150 30" />
<path class="edge" d="M40 40 L150 110" />
<path class="edge" d="M40 110 L150 30" />
<path class="edge" d="M40 110 L150 110" />
<path class="edge" d="M40 180 L150 110" />
<path class="edge" d="M40 180 L150 190" />
<path class="edge" d="M150 30 L260 75" />
<path class="edge" d="M150 110 L260 75" />
<path class="edge" d="M150 110 L260 145" />
<path class="edge" d="M150 190 L260 145" />
<path class="flow" d="M40 40 L150 30 L260 75" pathLength="1" />
<path class="flow f2" d="M40 110 L150 110 L260 145" pathLength="1" />
<path class="flow f3" d="M40 180 L150 110 L260 75" pathLength="1" />
<circle class="node" cx="40" cy="40" r="7" />
<circle class="node hot" cx="40" cy="110" r="7" />
<circle class="node" cx="40" cy="180" r="7" />
<circle class="node hot p2" cx="150" cy="30" r="8" />
<circle class="node hot p3" cx="150" cy="110" r="8" />
<circle class="node" cx="150" cy="190" r="8" />
<circle class="node hot" cx="260" cy="75" r="7" />
<circle class="node hot p2" cx="260" cy="145" r="7" />
</svg>
</body>
</html>