← HTML+CSS

Neural Network Visualization

Анимированная визуализация нейронной сети — слои нейронов с пульсирующими связями и передачей сигнала. Подходит для AI проектов, лендингов и презентаций. Чистый HTML+CSS без библиотек.

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Neural Network</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      background: #020818;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      font-family: monospace;
      padding: 20px;
    }
    canvas {
      border-radius: 16px;
      max-width: 100%;
    }
    .labels {
      display: flex;
      justify-content: space-between;
      width: 100%;
      max-width: 500px;
      margin-top: 12px;
      font-size: 11px;
      color: rgba(255,255,255,0.3);
      letter-spacing: 0.08em;
      text-transform: uppercase;
    }
  </style>
</head>
<body>

<canvas id="c" width="500" height="320"></canvas>
<div class="labels">
  <span>Input</span>
  <span>Hidden 1</span>
  <span>Hidden 2</span>
  <span>Output</span>
</div>

<script>
var canvas = document.getElementById('c');
var ctx    = canvas.getContext('2d');
var W = canvas.width;
var H = canvas.height;

// Архитектура сети
var layers = [4, 6, 6, 3];

// Цвета
var COLOR_NODE    = 'rgba(0,207,255,0.9)';
var COLOR_ACTIVE  = 'rgba(0,255,156,1)';
var COLOR_CONN    = 'rgba(0,207,255,0.08)';
var COLOR_SIGNAL  = 'rgba(0,255,156,1)';
var COLOR_OUTPUT  = 'rgba(168,85,247,0.9)';

var NODE_R = 14;
var nodes  = [];
var conns  = [];

// Вычисляем позиции нейронов
var layerX = [];
for (var l = 0; l < layers.length; l++) {
  layerX.push(W * 0.1 + l * (W * 0.8 / (layers.length - 1)));
}

for (var l = 0; l < layers.length; l++) {
  nodes.push([]);
  var count = layers[l];
  for (var n = 0; n < count; n++) {
    var y = H/2 + (n - (count-1)/2) * (H / (Math.max.apply(null, layers) + 1));
    nodes[l].push({
      x: layerX[l],
      y: y,
      active: 0,
      pulse: Math.random() * Math.PI * 2
    });
  }
}

// Связи между слоями
for (var l = 0; l < layers.length - 1; l++) {
  for (var a = 0; a < nodes[l].length; a++) {
    for (var b = 0; b < nodes[l+1].length; b++) {
      conns.push({
        from: { l: l, n: a },
        to:   { l: l+1, n: b },
        signal: -1,      // -1 = нет сигнала
        progress: 0,
        active: false
      });
    }
  }
}

// Сигнал который идёт по сети
var signalTimer  = 0;
var signalLayer  = 0;
var activeInputs = [];

function fireSignal() {
  signalLayer  = 0;
  activeInputs = [];

  // Активируем случайные входные нейроны
  for (var n = 0; n < nodes[0].length; n++) {
    if (Math.random() > 0.4) {
      activeInputs.push(n);
      nodes[0][n].active = 1;
    }
  }

  // Активируем связи из входного слоя
  for (var c = 0; c < conns.length; c++) {
    var conn = conns[c];
    if (conn.from.l === 0 && activeInputs.indexOf(conn.from.n) !== -1) {
      conn.active   = true;
      conn.progress = 0;
    }
  }
}

function draw() {
  ctx.clearRect(0, 0, W, H);

  // Фон
  ctx.fillStyle = '#020818';
  ctx.fillRect(0, 0, W, H);

  var t = Date.now() / 1000;

  // Рисуем связи
  for (var c = 0; c < conns.length; c++) {
    var conn = conns[c];
    var from = nodes[conn.from.l][conn.from.n];
    var to   = nodes[conn.to.l][conn.to.n];

    // Базовая линия
    ctx.beginPath();
    ctx.moveTo(from.x, from.y);
    ctx.lineTo(to.x, to.y);
    ctx.strokeStyle = COLOR_CONN;
    ctx.lineWidth   = 1;
    ctx.stroke();

    // Сигнал
    if (conn.active) {
      conn.progress += 0.018;

      var px = from.x + (to.x - from.x) * conn.progress;
      var py = from.y + (to.y - from.y) * conn.progress;

      // Линия сигнала
      ctx.beginPath();
      ctx.moveTo(from.x, from.y);
      ctx.lineTo(px, py);
      var grad = ctx.createLinearGradient(from.x, from.y, px, py);
      grad.addColorStop(0, 'rgba(0,255,156,0)');
      grad.addColorStop(1, 'rgba(0,255,156,0.8)');
      ctx.strokeStyle = grad;
      ctx.lineWidth   = 1.5;
      ctx.stroke();

      // Точка сигнала
      ctx.beginPath();
      ctx.arc(px, py, 3, 0, Math.PI * 2);
      ctx.fillStyle   = COLOR_SIGNAL;
      ctx.shadowColor = COLOR_SIGNAL;
      ctx.shadowBlur  = 10;
      ctx.fill();
      ctx.shadowBlur = 0;

      // Дошёл до нейрона
      if (conn.progress >= 1) {
        conn.active   = false;
        conn.progress = 0;

        var targetNode = nodes[conn.to.l][conn.to.n];
        targetNode.active = 1;

        // Активируем следующий слой
        var nextLayer = conn.to.l + 1;
        if (nextLayer < layers.length) {
          for (var nc = 0; nc < conns.length; nc++) {
            if (conns[nc].from.l === conn.to.l && conns[nc].from.n === conn.to.n) {
              if (Math.random() > 0.3) {
                conns[nc].active   = true;
                conns[nc].progress = 0;
              }
            }
          }
        }
      }
    }
  }

  // Рисуем нейроны
  for (var l = 0; l < layers.length; l++) {
    for (var n = 0; n < nodes[l].length; n++) {
      var node = nodes[l][n];

      // Затухание активации
      if (node.active > 0) node.active -= 0.01;
      if (node.active < 0) node.active = 0;

      // Пульсация
      var pulse = Math.sin(t * 2 + node.pulse) * 0.3 + 0.7;

      // Цвет в зависимости от слоя
      var isOutput = l === layers.length - 1;
      var baseColor = isOutput ? '168,85,247' : '0,207,255';
      var activeColor = isOutput ? '200,100,255' : '0,255,156';

      var alpha = node.active > 0
        ? (0.5 + node.active * 0.5)
        : (pulse * 0.4 + 0.2);

      // Свечение активного нейрона
      if (node.active > 0.3) {
        ctx.beginPath();
        ctx.arc(node.x, node.y, NODE_R * 2.5, 0, Math.PI * 2);
        var glowGrad = ctx.createRadialGradient(node.x, node.y, 0, node.x, node.y, NODE_R * 2.5);
        glowGrad.addColorStop(0, 'rgba(' + activeColor + ',' + (node.active * 0.3) + ')');
        glowGrad.addColorStop(1, 'rgba(' + activeColor + ',0)');
        ctx.fillStyle = glowGrad;
        ctx.fill();
      }

      // Внешний круг
      ctx.beginPath();
      ctx.arc(node.x, node.y, NODE_R, 0, Math.PI * 2);
      ctx.strokeStyle = node.active > 0.3
        ? 'rgba(' + activeColor + ',' + alpha + ')'
        : 'rgba(' + baseColor + ',' + (alpha * 0.6) + ')';
      ctx.lineWidth = node.active > 0.3 ? 2 : 1;
      ctx.stroke();

      // Внутренний круг
      ctx.beginPath();
      ctx.arc(node.x, node.y, NODE_R * 0.5, 0, Math.PI * 2);
      var nodeGrad = ctx.createRadialGradient(
        node.x - 3, node.y - 3, 1,
        node.x, node.y, NODE_R * 0.5
      );
      nodeGrad.addColorStop(0, 'rgba(' + (node.active > 0.3 ? activeColor : baseColor) + ',' + alpha + ')');
      nodeGrad.addColorStop(1, 'rgba(' + baseColor + ',' + (alpha * 0.3) + ')');
      ctx.fillStyle   = nodeGrad;
      ctx.shadowColor = node.active > 0.3 ? 'rgba(' + activeColor + ',0.8)' : 'transparent';
      ctx.shadowBlur  = node.active > 0.3 ? 15 : 0;
      ctx.fill();
      ctx.shadowBlur = 0;
    }
  }

  // Запускаем новый сигнал каждые 2 сек
  signalTimer++;
  if (signalTimer > 120) {
    signalTimer = 0;
    fireSignal();
  }

  requestAnimationFrame(draw);
}

// Первый сигнал через 0.5 сек
setTimeout(fireSignal, 500);
draw();
</script>

</body>
</html>

👁 1