Interactions
Ajoutez des comportements interactifs à n'importe quel élément HTML via un simple attribut data-interact, sans écrire une seule ligne de JavaScript.
loader.js, fichiers core/js/interactions.js + core/css/interactions.css.
Vue d'ensemble
Le système d'interactions permet d'ajouter des comportements interactifs à n'importe quel élément HTML via un simple attribut data-interact, sans écrire une seule ligne de JavaScript.
Exemples de ce que vous pouvez faire :
- Afficher/masquer un élément au clic
- Ajouter une classe au survol
- Déclencher une action quand un élément entre dans le viewport
- Stocker une valeur dans le localStorage
- Appeler une fonction JavaScript globale
- Combiner plusieurs interactions sur un même élément
Syntaxe
Format général :
data-interact="trigger | action[:params] | cible [| timing] [| once]"
Les segments sont séparés par | (pipe entouré d'espaces).
| Segment | Obligatoire | Description | Exemple |
|---|---|---|---|
| trigger | Oui | L'événement déclencheur | click, hover, viewport-enter |
| action[:params] | Oui | L'action à exécuter, avec paramètres optionnels séparés par : | toggle-class:active, show |
| cible | Non | Sélecteur CSS de la cible (self par défaut) | self, #menu, .panel |
| timing | Non | Moment d'exécution par rapport aux animations framework | before, during, after |
| once | Non | Exécuter une seule fois | once |
Exemple complet :
<button data-interact="click | toggle-class:open | #sidebar">
Menu
</button>
Déclencheurs
10 déclencheurs disponibles :
| Trigger | Description | Exemple |
|---|---|---|
click |
Au clic sur l'élément | click | toggle-class:active | self |
hover |
Au survol (mouseenter + mouseleave avec auto‑reverse) | hover | add-class:highlight | self |
focus |
Au focus (focus + blur avec auto‑reverse) | focus | add-class:focused | self |
mouseenter |
Quand la souris entre dans l'élément (sans reverse) | mouseenter | show | .tooltip |
mouseleave |
Quand la souris quitte l'élément (sans reverse) | mouseleave | hide | .tooltip |
viewport-enter |
Quand l'élément entre dans le viewport (IntersectionObserver, seuil 10 %) | viewport-enter | add-class:visible | self |
viewport-exit |
Quand l'élément sort du viewport (après y être entré au moins une fois) | viewport-exit | remove-class:visible | self |
scroll |
Au scroll de la page (événement passif sur window) |
scroll | add-class:scrolled | .header |
load |
Immédiatement au chargement (DOMContentLoaded) | load | show | .welcome |
window-leave |
Quand le curseur quitte la fenêtre par le haut (intent de fermeture) | window-leave | show | .exit-popup |
Actions
11 actions disponibles :
| Action | Syntaxe | Description | Cible requise |
|---|---|---|---|
show |
show |
Affiche la cible (retire l'attribut data-interact-hidden) |
Optionnelle |
hide |
hide |
Masque la cible (ajoute data-interact-hidden) |
Optionnelle |
toggle-class |
toggle-class:nom |
Ajoute ou retire la classe CSS | Optionnelle |
add-class |
add-class:nom |
Ajoute la classe CSS | Optionnelle |
remove-class |
remove-class:nom |
Retire la classe CSS | Optionnelle |
set-attr |
set-attr:nom:valeur |
Définit un attribut HTML (valeur optionnelle) | Optionnelle |
remove-attr |
remove-attr:nom |
Supprime un attribut HTML | Optionnelle |
toggle-attr |
toggle-attr:nom:valeur |
Ajoute ou supprime un attribut HTML | Optionnelle |
call |
call:nomFonction:arg1:arg2 |
Appelle une fonction globale (window.nomFonction) |
Non |
storage-set |
storage-set:type:clé:valeur |
Stocke une valeur (local, session ou window) | Non |
storage-remove |
storage-remove:type:clé |
Supprime une valeur stockée | Non |
Cible
Le segment cible détermine sur quel(s) élément(s) l'action s'applique :
self(par défaut) — l'élément déclencheur lui‑même- N'importe quel sélecteur CSS — un ou plusieurs autres éléments
<!-- self : l'action s'applique au bouton lui-même -->
<button data-interact="click | toggle-class:active">Toggle</button>
<!-- Sélecteur CSS : l'action s'applique à #panel -->
<button data-interact="click | toggle-class:open | #panel">Ouvrir</button>
<div id="panel">Contenu du panneau</div>
<!-- Sélecteur multiple : tous les .card reçoivent la classe -->
<button data-interact="click | add-class:highlight | .card">Tout surligner</button>
Auto‑reverse (hover / focus)
Les triggers hover et focus inversent automatiquement l'action quand l'événement se termine (mouseleave / blur). Pas besoin d'écrire deux interactions :
| Action | Reverse automatique |
|---|---|
show | hide |
hide | show |
add-class | remove-class |
remove-class | add-class |
set-attr | remove-attr |
remove-attr | set-attr |
toggle-class | toggle-class |
toggle-attr | toggle-attr |
<!-- Au survol : ajoute .highlight, au départ : la retire automatiquement -->
<div data-interact="hover | add-class:highlight">Survolez-moi</div>
<!-- Au focus : affiche le tooltip, au blur : le masque automatiquement -->
<input data-interact="focus | show | .tooltip" placeholder="Cliquez ici">
<span class="tooltip" data-interact-hidden>Aide contextuelle</span>
Timing animation
Quand l'élément déclencheur possède une classe d'animation framework (anim-*), vous pouvez contrôler le moment d'exécution de l'interaction par rapport à cette animation :
| Timing | Comportement |
|---|---|
before | L'action s'exécute avant que l'animation ne se joue |
during (défaut) | L'action s'exécute en même temps que l'animation |
after | L'action s'exécute après la fin de l'animation (animationend / transitionend) |
<!-- L'action s'exécute après la fin de l'animation fade-in -->
<div class="anim-fade-in"
data-interact="viewport-enter | add-class:loaded | self | after">
Contenu animé
</div>
<!-- L'action s'exécute avant l'animation -->
<button class="anim-scale-in"
data-interact="click | toggle-class:active | #panel | before">
Action
</button>
Si l'élément n'a pas de classe anim-*, le timing est ignoré et l'action s'exécute immédiatement.
Option once
Ajoutez once à la fin pour n'exécuter l'interaction qu'une seule fois :
<!-- Le popup ne s'affiche qu'une seule fois -->
<div data-interact="window-leave | show | .exit-popup | once"></div>
<!-- La classe n'est ajoutée qu'au premier scroll -->
<header data-interact="scroll | add-class:scrolled | self | once"></header>
<!-- Combiné avec timing -->
<div class="anim-fade-in"
data-interact="viewport-enter | add-class:revealed | self | after | once">
Contenu
</div>
Interactions multiples
Pour attacher plusieurs interactions à un même élément, utilisez les attributs numérotés data-interact-1, data-interact-2, … jusqu'à data-interact-10 :
<button
data-interact="click | toggle-class:open | #menu"
data-interact-1="click | toggle-class:active | self"
data-interact-2="click | set-attr:aria-expanded:true | self">
Menu
</button>
L'attribut principal data-interact et les attributs numérotés fonctionnent ensemble. Tous sont exécutés.
data-interact-1 à data-interact-10), plus l'attribut principal data-interact.
Stockage navigateur
Les actions storage-set et storage-remove permettent de lire/écrire dans le stockage du navigateur :
| Type | Persistance | Portée |
|---|---|---|
local | Persistant (survit à la fermeture du navigateur) | Tous les onglets du même domaine |
session | Session (disparaît à la fermeture de l'onglet) | Onglet courant uniquement |
window | Non persistant (mémoire JS uniquement) | Onglet courant, perdu au rechargement |
<!-- Stocker un choix dans le localStorage -->
<button data-interact="click | storage-set:local:theme:dark">
Mode sombre
</button>
<!-- Supprimer la valeur -->
<button data-interact="click | storage-remove:local:theme">
Réinitialiser
</button>
<!-- Stocker en session uniquement -->
<button data-interact="click | storage-set:session:sidebar:collapsed">
Fermer la sidebar
</button>
<!-- Stockage window (onglet courant, non persistant) -->
<button data-interact="click | storage-set:window:step:2">
Étape suivante
</button>
API JavaScript
| Fonction | Description |
|---|---|
window.initInteractions(root) | Initialise les interactions dans un conteneur. Appelé automatiquement au chargement. Passez un élément DOM pour cibler un sous-arbre. |
Utilisez cette fonction après avoir injecté du contenu dynamique (AJAX, innerHTML) :
// Après injection de HTML dynamique
document.getElementById('container').innerHTML = newHTML;
window.initInteractions(document.getElementById('container'));
initInteractions() plusieurs fois sans risque de doublons.
Exemples complets
Toggle afficher/masquer
Ce panneau s'affiche et se masque au clic.
<button data-interact="click | toggle-class:open | #panel">Afficher / Masquer</button>
<div id="panel" data-interact-hidden>Contenu du panneau</div>
Classe au survol
Survolez‑moi pour ajouter la classe .highlight
<div data-interact="hover | add-class:highlight">
Survolez-moi
</div>
Stocker une préférence
<button
data-interact="click | storage-set:session:pref:active"
data-interact-1="click | add-class:btn--primary | self">
Activer
</button>
<button
data-interact="click | storage-remove:session:pref">
Réinitialiser
</button>
Exit intent popup
Déplacez votre curseur en dehors de la fenêtre par le haut pour déclencher le popup.
Attendez ! Ne partez pas sans découvrir nos offres.
<div data-interact="window-leave | show | #exit-popup | once"></div>
<div id="exit-popup" data-interact-hidden>
<p>Attendez ! Ne partez pas sans découvrir nos offres.</p>
<button data-interact="click | hide | #exit-popup">Fermer</button>
</div>
Action au scroll (une seule fois)
Scrollez la page pour ajouter une classe à cet élément.
<div data-interact="scroll | add-class:scrolled | .header | once"></div>
Interactions multiples
Ce bouton change de style, met à jour aria-pressed, et stocke une valeur en session.
<button
data-interact="click | toggle-class:btn--primary | self"
data-interact-1="click | toggle-attr:aria-pressed:true | self"
data-interact-2="click | storage-set:session:state:toggled">
Multi-actions
</button>
Tabs manuels
<button data-interact="click | add-class:active | self"
data-interact-1="click | show | #tab-1"
data-interact-2="click | hide | #tab-2">
Onglet 1
</button>
<div id="tab-1">Contenu onglet 1</div>
<div id="tab-2" data-interact-hidden>Contenu onglet 2</div>
Compteur de visites (localStorage)
<section data-interact="load | storage-set:local:visited:true">
<!-- Enregistre la visite au chargement -->
</section>
Animation + interaction séquencée
<div class="anim-fade-in-up"
data-interact="viewport-enter | add-class:highlight | self | after | once">
Ce bloc reçoit .highlight APRÈS son animation d'entrée
</div>
Problèmes courants
| Problème | Cause probable | Solution |
|---|---|---|
| L'interaction ne se déclenche pas | Le fichier interactions.js n'est pas chargé, ou l'attribut contient une erreur de syntaxe |
Vérifier que interactions.js est bien inclus (via loader.js ou manuellement). Vérifier les pipes | avec espaces. |
| La cible n'est pas trouvée | Le sélecteur CSS est invalide ou l'élément n'existe pas encore dans le DOM | Vérifier le sélecteur dans la console. Si le contenu est dynamique, appeler initInteractions() après l'injection. |
show/hide ne fonctionne pas |
L'élément n'a pas l'attribut data-interact-hidden initial |
Pour masquer un élément par défaut, ajouter data-interact-hidden sur l'élément cible dans le HTML. |
call affiche « Fonction non trouvée » |
La fonction n'est pas globale (window.nomFonction) |
Déclarer la fonction sur window : window.maFonction = function() { ... } |
Le timing after ne se déclenche pas |
L'élément n'a pas de classe anim-* du framework |
Le timing ne fonctionne qu'avec les animations du framework. Sans anim-*, l'action s'exécute immédiatement. |
| L'auto-reverse ne fonctionne pas | Le déclencheur ne supporte pas l'auto-reverse | Seuls hover et focus supportent l'auto-reverse. Pour mouseenter/mouseleave, créer deux interactions distinctes. |
| L'interaction se joue plusieurs fois | L'option once n'est pas activée |
Ajouter once en dernier segment. Pour viewport-enter, sans once l'action se rejoue à chaque entrée dans le viewport. |
Référence rapide
Déclencheurs
| Trigger | Événement natif | Auto‑reverse |
|---|---|---|
click | click | Non |
hover | mouseenter / mouseleave | Oui |
focus | focus / blur | Oui |
mouseenter | mouseenter | Non |
mouseleave | mouseleave | Non |
viewport-enter | IntersectionObserver (entrée) | Non |
viewport-exit | IntersectionObserver (sortie) | Non |
scroll | scroll (passif) | Non |
load | DOMContentLoaded | Non |
window-leave | mouseleave sur <html> | Non |
Actions
| Action | Paramètres | Reversible |
|---|---|---|
show | — | Oui (hide) |
hide | — | Oui (show) |
toggle-class | :className | Oui (toggle) |
add-class | :className | Oui (remove-class) |
remove-class | :className | Oui (add-class) |
set-attr | :name:value | Oui (remove-attr) |
remove-attr | :name | Oui (set-attr) |
toggle-attr | :name:value | Oui (toggle) |
call | :fn:arg1:arg2… | Non |
storage-set | :type:key:value | Non |
storage-remove | :type:key | Non |
Sécurité
Le système d'interactions inclut un modèle de sécurité complet pour prévenir les attaques XSS et les abus, notamment quand les interactions sont utilisées dans du contenu généré par les utilisateurs (wireframes, CMS, templates éditables).
Fonctions bloquées (BLOCKED_FN)
L'action call valide le nom de la fonction contre une liste noire. Les fonctions suivantes sont interdites :
eval, Function, setTimeout, setInterval, requestAnimationFrame,
importScripts, fetch, XMLHttpRequest, WebSocket,
Worker, SharedWorker, ServiceWorker, import, require,
__proto__, constructor, prototype
Toute tentative d'appel à ces fonctions est silencieusement ignorée.
Attributs bloqués (BLOCKED_ATTR_RE)
Les actions set-attr et toggle-attr rejettent certains noms d'attributs dangereux :
on*— tous les gestionnaires d'événements (onclick,onerror,onload, etc.)srcdoc— injection HTML dans les iframesformaction— redirection de formulaireaction— cible de soumission de formulairexlink:href— liens SVGdata:— protocole data inlinejavascript:— exécution JavaScript inline
Valeurs d'attributs dangereuses (DANGEROUS_ATTR_VALUE_RE)
Même si le nom de l'attribut est autorisé, les valeurs sont également vérifiées. Les valeurs contenant les patterns suivants sont bloquées :
javascript:
data:text/html
data:image/svg
vbscript:
Validations par regex
Chaque type de paramètre est validé par une regex stricte :
| Paramètre | Caractères autorisés | Exemple |
|---|---|---|
| Nom de classe CSS | [a-zA-Z0-9_-] uniquement | active, is-open, btn_primary |
| Nom de fonction | [a-zA-Z0-9_.$] uniquement | myApp.toggle, utils$init |
| Clé de stockage | [a-zA-Z0-9_.-], max 100 caractères | theme, sidebar.state, app_v2 |
Les espaces et caractères spéciaux sont rejetés pour empêcher les injections.
Limites
| Limite | Valeur | Description |
|---|---|---|
MAX_TARGETS | 100 | Nombre maximum d'éléments ciblés par une seule interaction |
| Longueur de règle | 500 caractères | Taille maximum d'une valeur data-interact |
| Sélecteur invalide | try/catch | querySelectorAll est encapsulé dans un try/catch pour éviter les erreurs sur un sélecteur malformé |
data-interact malicieux pourrait exécuter du code arbitraire, voler des données ou rediriger l'utilisateur.