Attraction Field
80 particules attirées vers le curseur avec F = min(2, 50/d) — liaisons entre voisins à < 80 px, amortissement 0,96 par frame, enroulement toroïdal.
Vous aurez accès à la démo interactive avec un compte gratuit.
Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Atmosphère comprend 57 effets, dont 10 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
Un IIFE initialise un canvas pleine-largeur via setupCanvas('psAttraction') (lookup getElementById). Un pool de 80 particules est créé au démarrage avec des positions aléatoires, une vitesse nulle et un rayon aléatoire entre 1 et 3 px. Le curseur est suivi par un écouteur mousemove sur le canvas, avec conversion des coordonnées CSS → coordonnées canvas via getBoundingClientRect(). Un requestAnimationFrame non annulable tourne en continu — il n'y a ni IntersectionObserver ni pause hors-viewport.
À chaque frame, chaque particule calcule le vecteur vers le curseur (dx, dy) et la distance d. La force d'attraction vaut F = min(2, 50/d) — croissante à courte portée, plafonnée à 2 pour éviter l'instabilité numérique. L'accélération appliquée est F × 0,02, soit au maximum 0,04 px/frame². La vitesse est amortie à chaque frame par un facteur 0,96 (arrêt naturel en ~25 frames si la force cesse). La position est ensuite mise à jour, puis enroulée toroïdalement : une particule qui sort par un bord réapparaît au bord opposé.
Le rendu commence par un fond plein #0a0a0f (fillRect total, pas de clearRect). Chaque particule est dessinée comme un cercle rgba(139, 92, 246, 0,4 + r/5) — les petites particules (r ≈ 1) ont une opacité de 0,6 ; les grandes (r ≈ 3) atteignent 1,0. Une double boucle parcourt ensuite toutes les paires : si la distance entre deux particules est inférieure à 80 px, une liaison de 0,5 px d'épaisseur est tracée avec une opacité 0,15 × (1 − d/80) — les connexions s'estompent graduellement avec la distance.
Accessibilité
- prefers-reduced-motion absent : le code ne vérifie pas cette préférence média — l'animation tourne en boucle perpétuelle quel que soit le réglage système. Ajoutez un garde
matchMedia('(prefers-reduced-motion: reduce)')manuellement pour les projets accessibles. - Le
<canvas>ne porte pas d'aria-hidden="true"dans la structure fournie — à ajouter pour que les lecteurs d'écran ignorent l'élément canvas. - Pas de
role="img"ni d'aria-labelsur le conteneur.demo-preview— l'effet n'a aucune description sémantique accessible. - L'attraction est déclenchée uniquement par
mousemove— aucun équivalent tactile (touchmove) ni clavier. Sur mobile, les particules dérivent vers le centre par défaut. - Contraste des particules :
#8b5cf6violet sur fond#0a0a0fatteint ~4,7:1 à pleine opacité (WCAG AA pour éléments graphiques) ; les liaisons à alpha 0,15 restent sous le seuil — elles sont décoratives, pas informationnelles.
Compatibilité navigateur
Nécessite Canvas 2D Context et requestAnimationFrame — supportés dans tous les navigateurs modernes. Aucune dépendance externe.
Si <code>canvas</code> n'est pas supporté, le conteneur reste vide (aucune erreur JS fatale). Si <code>getElementById('psAttraction')</code> ne trouve pas le canvas, l'IIFE retourne immédiatement sans lancer de boucle.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="demo-preview">
<div class="ps-canvas-wrap" role="img" aria-label="Particules violettes attirées vers le curseur avec liaisons entre voisins proches">
<canvas class="ps-canvas" id="psAttraction" aria-hidden="true"></canvas>
<span class="ps-hint" aria-hidden="true">Bougez la souris</span>
<!-- Optionnel : overlay de contenu (scrim + titre + CTA) -->
<!-- <div class="ps-overlay"> ... </div> -->
</div>
</div>
HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| Nombre de particules [JS : < 80] | 80 | Boucle de spawn : for (let e = 0; e < 80; e++). 40–60 est plus adapté à un composant embarqué ; dépasser 120 pèse sur les navigateurs mobiles. |
| Rayon des particules [JS : 2 × rand + 1] | 1 – 3 px | Contrôle taille et opacité (opacité = 0,4 + r/5). Remplacer par rand + 0.5 pour un rendu plus fin ; 3 × rand + 2 pour des particules grandes et lumineuses. |
| Constante d'attraction [JS : 50] | 50 | Numérateur dans min(2, 50/d). Augmenter étend la portée de l'attraction ; réduire la concentre autour du curseur. |
| Facteur d'accélération [JS : 0.02] | 0,02 | Multiplie la force à chaque frame : dx/d × force × 0.02. 0,02 donne une attraction fluide ; 0,05 la rend brusque et instable. |
| Amortissement [JS : 0.96] | 0,96 | Facteur de décélération par frame : vx *= .96. 0,96 = arrêt naturel en ~25 frames. 0,99 = traîne longue, 0,90 = stop brusque. |
| Seuil de liaison [JS : 80] | 80 px | Distance maximum entre deux particules pour tracer une liaison : if (i < 80). Augmenter densifie le graphe ; réduire à 50 l'épure. |
| Couleur des particules [JS : 139,92,246] | violet #8b5cf6 | Valeurs RVB dans rgba(139,92,246,...) et rgba(139,92,246,...) (liaisons). Remplacez les trois entiers par les composantes de votre couleur brand. |
FAQ
IntersectionObserver. Le requestAnimationFrame tourne en continu même si le canvas est hors écran. Pour optimiser les performances, encapsulez la boucle dans un IntersectionObserver qui coupe le RAF quand le ratio d'intersection tombe à zéro et le relance au retour.mousemove. Sur mobile, les particules dérivent vers le centre (position par défaut). Pour activer le toucher, ajoutez un écouteur touchmove sur le canvas qui met à jour les variables e et n avec evt.touches[0].clientX et clientY, convertis via getBoundingClientRect().id fixe 'psAttraction'. Dans React, montez le canvas via une ref dans useEffect, extrayez la logique de particules dans une fonction initAttraction(canvas) qui reçoit l'élément canvas en paramètre (pas un id), et annulez le requestAnimationFrame dans la fonction de cleanup. Dans Vue, même principe via onMounted / onUnmounted.