NavigationGratuit

Connected Nodes

Quatre nœuds colorés qu'on déplace à la souris ou au doigt, reliés par des lignes SVG qui les suivent en temps réel — un graphe interactif en HTML, CSS et JavaScript vanilla, sans librairie.

SVGJSDrag

Mis à jour le

A
B
C
D
Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Navigation en contient 37 au total, dont 4 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Topologie de services 4 services live
4 services 4 connexions actives Faites glisser un nœud pour explorer

① Dashboard — Topologie de microservices

QuandDans un panneau de monitoring ou d'architecture système pour visualiser les dépendances entre services (API, base de données, cache, file de messages) sans charger une librairie de graphe tierce.
PourquoiLa structure SVG + CSS pose le graphe ; le glisser-déposer fourni permet de réorganiser la topologie à la main pour l'explorer. Fond sombre et couleurs par domaine indiquent le type de service d'un coup d'œil.
RéglagesPositions initiales CSS conservées, stroke-width: 2, stroke-dasharray: 6 4, nœuds 48 px, palette par défaut (indigo API, rose DB, vert Cache, ambre Queue).
Mon espace › Carte conceptuelle — Article

② Éditeur de contenu — Carte conceptuelle d'article

QuandDans un outil d'écriture ou un wiki pour visualiser la structure d'un article (concepts, sections, thèmes) et les liens entre idées avant la rédaction, avec un nœud central comme pivot.
PourquoiLa topologie hub-and-spoke (un nœud central relié aux autres) modélise naturellement une carte mentale ; les lignes en pointillés sont lisibles sur fond clair ou sombre sans alourdir l'interface.
RéglagesNœud hub centré (top: 40%, left: 40%), stroke-width: 1 pour un rendu plus aérien, libellés courts (4–5 caractères max), couleurs alignées sur la charte éditoriale.
⚙ Pipeline de données

③ Outil low-code — Éditeur de pipeline de traitement

QuandDans une interface no-code où l'utilisateur connecte visuellement des blocs de traitement (Source → Filtre → Transform → Sortie) en les reliant par glisser-déposer.
PourquoiNœuds et lignes SVG constituent l'UI primitive de tout éditeur de workflow ; la mise à jour des coordonnées SVG au drag est native au DOM, sans canvas ni librairie tierce.
Réglages4 nœuds disposés en diagonale, connexions linéaires (a→b→c→d, pas de retour d→a), étiquettes avec initiales de traitement (SRC, MAP, FLT, OUT).

Comment ça marche

Chaque nœud est un <div class="node-point"> en position: absolute dans un conteneur .nodes-scene qui couvre 100 % × 100 % de son parent. Les coordonnées initiales sont déclarées en pourcentages via les classes .node-a à .node-d (ex. top: 20%; left: 15%) — aucune valeur en pixels codée en dur. Chaque nœud affiche un dégradé linéaire à 135° (deux tons d'une même teinte) pour simuler un léger relief ; user-select: none empêche la sélection de texte pendant un éventuel glisser.

Un <svg class="node-lines"> couvre toute la scène en position: absolute; inset: 0 avec pointer-events: none pour ne jamais intercepter les clics sur les nœuds. Les quatre éléments <line> (a→b, b→c, c→d, d→a) définissent les connexions : tracés en indigo clair (stroke: rgb(129,140,248)), épaisseur 2, motif stroke-dasharray: 6 4 (6 px de trait, 4 px d'espace), opacité 0,6.

Au survol, chaque nœud émet un halo de lueur via box-shadow en transition 0.2s. Le curseur grab (et grabbing pendant le glisser, via :active) annonce qu'on peut le déplacer.

Le script calcule le centre de chaque nœud avec getBoundingClientRect() et y accroche les extrémités x1/y1/x2/y2 des quatre lignes, au chargement puis de nouveau 100 ms plus tard (le temps que les positions en pourcentages soient appliquées). Un mousedown ou touchstart sur un nœud le saisit ; chaque mousemove/touchmove le déplace — limité à l'intérieur de la scène — et recalcule les quatre lignes, qui suivent donc le nœud sans décalage. Le relâchement (mouseup/touchend) le dépose. Le tout tient dans une IIFE d'environ 70 lignes, sans variable globale.

Accessibilité

  • Aucune gestion de prefers-reduced-motion dans le code — la seule animation est transition: box-shadow 0.2s au survol, non répétitive ; le déplacement suit directement la main de l'utilisateur. Ajoutez @media (prefers-reduced-motion: reduce) { .node-point { transition: none } } si vous voulez supprimer le halo animé.
  • Le glisser fonctionne à la souris et au doigt, pas au clavier : les nœuds sont des <div> sans tabindex ni role. Pour un graphe accessible, ajoutez tabindex="0", role="button" et déplacez le nœud actif aux touches fléchées en appelant ensuite updateLines().
  • Le <svg> de lignes est purement décoratif mais ne porte pas aria-hidden="true" — à ajouter pour éviter qu'un lecteur d'écran ne le parcoure inutilement.
  • Contraste insuffisant en configuration par défaut : texte blanc sur fond ambre (#f59e0b) et rose (#ec4899) n'atteint pas 4,5:1 WCAG AA pour du texte à 0,75 rem — optez pour du texte sombre (color:#1e1b4b) ou des teintes de fond plus foncées.

Compatibilité navigateur

CSS + SVG natif + environ 70 lignes de JavaScript vanilla (souris et tactile) — zéro dépendance externe, aucune balise canvas.

Chrome 88+✓ Complet
Firefox 87+✓ Complet
Safari 15+✓ Complet
Edge 88+✓ Complet
Mobile iOS✓ Complet — glisser au doigt
Android Chrome✓ Complet

Sans JavaScript, les nœuds colorés s'affichent à leur place mais ne se déplacent pas, et les lignes restent invisibles (leurs extrémités ne sont pas calculées). Si SVG n'est pas supporté, seules les lignes disparaissent.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="anchor-demo">
    <div class="nodes-scene" id="nodesScene">
      <svg class="node-lines">
        <line id="line-ab" x1="0" y1="0" x2="0" y2="0"/>
        <line id="line-bc" x1="0" y1="0" x2="0" y2="0"/>
        <line id="line-cd" x1="0" y1="0" x2="0" y2="0"/>
        <line id="line-da" x1="0" y1="0" x2="0" y2="0"/>
      </svg>
      <div class="node-point node-a" data-node="a">A</div>
      <div class="node-point node-b" data-node="b">B</div>
      <div class="node-point node-c" data-node="c">C</div>
      <div class="node-point node-d" data-node="d">D</div>
    </div>
  </div>
CSS
.anchor-demo  {
   position: relative;
   width: 100%;
   height: 220px;
   display: flex;
   align-items: center;
   justify-content: center;
   overflow: hidden;
   }

.anchor-demo *  {
   box-sizing: border-box;
   }

.nodes-scene  {
   position: relative;
   width: 100%;
   height: 100%;
   }

.nodes-scene svg.node-lines  {
   position: absolute;
   inset: 0px;
   width: 100%;
   height: 100%;
   pointer-events: none;
   }

.nodes-scene svg.node-lines line  {
   stroke: rgb(129, 140, 248);
   stroke-width: 2;
   stroke-dasharray: 6, 4;
   opacity: 0.6;
   }

.node-point  {
   position: absolute;
   width: 48px;
   height: 48px;
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
   font-size: 0.75rem;
   font-weight: 700;
   color: rgb(255, 255, 255);
   cursor: grab;
   user-select: none;
   z-index: 2;
   transition: box-shadow 0.2s;
   }

.node-point:active  {
   cursor: grabbing;
   }

.node-point:hover  {
   box-shadow: rgba(129, 140, 248, 0.6) 0px 0px 20px;
   }

.node-a  {
   background: linear-gradient(135deg, rgb(99, 102, 241), rgb(129, 140, 248));
   top: 20%;
   left: 15%;
   }

.node-b  {
   background: linear-gradient(135deg, rgb(236, 72, 153), rgb(244, 114, 182));
   top: 60%;
   left: 55%;
   }

.node-c  {
   background: linear-gradient(135deg, rgb(16, 185, 129), rgb(52, 211, 153));
   top: 25%;
   left: 70%;
   }

.node-d  {
   background: linear-gradient(135deg, rgb(245, 158, 11), rgb(251, 191, 36));
   top: 70%;
   left: 20%;
   }
JavaScript (fx-0476)
(function() {
  // ============================================
  // 2. Connected Nodes - draggable with SVG lines
  // ============================================
  (function(){
    var scene = document.getElementById('nodesScene');
    if(!scene) return;
    var nodes = scene.querySelectorAll('.node-point');
    var lineAb = document.getElementById('line-ab');
    var lineBc = document.getElementById('line-bc');
    var lineCd = document.getElementById('line-cd');
    var lineDa = document.getElementById('line-da');
    if(!lineAb || !lineBc || !lineCd || !lineDa) return;
    var lines = { ab: lineAb, bc: lineBc, cd: lineCd, da: lineDa };
    var nodeMap = {};
    nodes.forEach(function(n){ nodeMap[n.dataset.node] = n; });

    function getCenter(el){
      var r = el.getBoundingClientRect();
      var sr = scene.getBoundingClientRect();
      return { x: r.left - sr.left + r.width/2, y: r.top - sr.top + r.height/2 };
    }
    function updateLines(){
      var a=getCenter(nodeMap.a), b=getCenter(nodeMap.b), c=getCenter(nodeMap.c), d=getCenter(nodeMap.d);
      lines.ab.setAttribute('x1',a.x); lines.ab.setAttribute('y1',a.y);
      lines.ab.setAttribute('x2',b.x); lines.ab.setAttribute('y2',b.y);
      lines.bc.setAttribute('x1',b.x); lines.bc.setAttribute('y1',b.y);
      lines.bc.setAttribute('x2',c.x); lines.bc.setAttribute('y2',c.y);
      lines.cd.setAttribute('x1',c.x); lines.cd.setAttribute('y1',c.y);
      lines.cd.setAttribute('x2',d.x); lines.cd.setAttribute('y2',d.y);
      lines.da.setAttribute('x1',d.x); lines.da.setAttribute('y1',d.y);
      lines.da.setAttribute('x2',a.x); lines.da.setAttribute('y2',a.y);
    }
    updateLines();

    var dragging = null, offsetX = 0, offsetY = 0;
    nodes.forEach(function(n){
      n.addEventListener('mousedown', function(e){
        e.preventDefault();
        dragging = n;
        var r = n.getBoundingClientRect();
        offsetX = e.clientX - r.left;
        offsetY = e.clientY - r.top;
      });
      n.addEventListener('touchstart', function(e){
        e.preventDefault();
        dragging = n;
        var r = n.getBoundingClientRect();
        var t = e.touches[0];
        offsetX = t.clientX - r.left;
        offsetY = t.clientY - r.top;
      }, {passive:false});
    });
    function onMove(cx, cy){
      if(!dragging) return;
      var sr = scene.getBoundingClientRect();
      var x = cx - sr.left - offsetX;
      var y = cy - sr.top - offsetY;
      x = Math.max(0, Math.min(sr.width - 48, x));
      y = Math.max(0, Math.min(sr.height - 48, y));
      dragging.style.left = x + 'px';
      dragging.style.top = y + 'px';
      updateLines();
    }
    document.addEventListener('mousemove', function(e){ onMove(e.clientX, e.clientY); });
    document.addEventListener('touchmove', function(e){ if(dragging){ e.preventDefault(); onMove(e.touches[0].clientX, e.touches[0].clientY); }}, {passive:false});
    document.addEventListener('mouseup', function(){ dragging = null; });
    document.addEventListener('touchend', function(){ dragging = null; });

    // Animate initial line draw
    setTimeout(updateLines, 100);
  })();
})();

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
.anchor-demo height 220px Hauteur du conteneur. Passez à 100% ou 360px pour occuper toute une carte ou une section hero.
.node-point width / height 48px Diamètre de chaque nœud. 64 px pour des libellés longs, 32 px pour un graphe dense à nombreux nœuds.
stroke (svg line) rgb(129,140,248) Couleur des lignes SVG. Remplacez par votre couleur d'accent, ou variez par type de connexion (obligatoire vs optionnelle).
stroke-width (svg line) 2 Épaisseur des lignes. 1 = discret, 3–4 = connexion forte. Peut encoder le poids d'une arête dans un graphe pondéré.
stroke-dasharray (svg line) 6 4 Motif de tirets. '0' = ligne pleine (lien direct), '2 6' = pointillé fin (lien faible), '8 3 2 3' = tiret-point.
opacity (svg line) 0.6 Opacité des lignes. 1 = pleine visibilité, 0.3 = connexions en retrait pour mettre les nœuds au premier plan.
Fond des nœuds (.node-a à .node-d background) 4 dégradés (indigo, rose, vert, ambre) Remplacez par vos couleurs brand ou encodez l'état du nœud : vert = sain, rouge = erreur, gris = hors ligne.
limite de déplacement (JS, lignes 59-60) 48 Taille d'un nœud en px, utilisée pour le garder entièrement dans la scène. Si vous changez la taille des nœuds, mettez la même valeur ici.

FAQ

Les lignes ne suivent plus mes nœuds après un redimensionnement de la fenêtre — pourquoi ?

Les lignes sont recalculées au chargement et à chaque déplacement, pas au redimensionnement : si la scène change de taille, les nœuds placés en pourcentages bougent mais les lignes attendent le prochain glisser. Ajoutez window.addEventListener('resize', updateLines); juste après l'appel updateLines(); (ligne 34).

Comment ajouter un cinquième nœud et de nouvelles connexions ?

Ajoutez un <div class="node-point"> avec data-node="e" et ses coordonnées CSS, puis une <line id="line-ae"> dans le SVG. Récupérez-la à côté des autres lignes (lignes 9-12) et ajoutez ses extrémités dans updateLines() (lignes 23-33) — le glisser fonctionne tout seul pour le nouveau nœud.

Peut-on ajouter une flèche directionnelle sur les connexions ?

Oui : définissez un <marker> dans un bloc <defs> SVG, puis appliquez marker-end="url(#arrow)" sur chaque <line> ciblée. Les styles CSS existants (couleur, épaisseur, tirets) restent inchangés — seule la balise SVG évolue.