Stacked to Spread
Six cartes CSS passent d'une pile légèrement inclinée à une grille 3×2 avec un rebond élastique cubic-bezier — bascule d'état au clic, zéro moteur d'animation JS.
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. Cartes comprend 41 effets, dont 6 gratuits. Découvrir la catégorie →
3 exemples d'usage
Comment ça marche
Six éléments .stack-card sont positionnés en position: absolute à l'intérieur d'un conteneur .stacked-spread-wrapper en position: relative; height: 160px. La logique d'état repose entièrement sur deux classes CSS — .stacked et .spread — appliquées au conteneur. Aucun moteur d'animation JS n'est impliqué : tout le mouvement vient des transitions CSS déclarées sur les cartes.
En mode .stacked, chaque carte reçoit via :nth-child un transform: translate(Xpx, Ypx) rotate(Ndeg) de quelques pixels et degrés, simulant une pile posée à la main avec de légères imperfections (de 2 px/2° à 5 px/4° selon la position). En mode .spread, les six cartes se dispersent sur trois colonnes et deux rangées : translateX(−70px) · translate(0) · translateX(+70px) et translateY(−30px) / translateY(+40px).
La transition élastique est déclarée une seule fois sur .stack-card : transition: .6s cubic-bezier(.34, 1.56, .64, 1). La valeur y1 = 1,56, supérieure à 1, fait dépasser les cartes leur cible avant de revenir en place — c'est l'overshoot qui produit le rebond de type spring. Les six cartes démarrent depuis des positions différentes et arrivent toutes à destination au même instant, créant un effet de déploiement en éventail.
La fonction setStackMode(el, mode) orchestre le basculement : elle retire .stacked/.spread du wrapper ciblé par l'id fixe stackDemo, ajoute la nouvelle classe, et synchronise l'état .active sur les boutons toggle. Déclarée globalement, elle peut être appelée depuis n'importe quel déclencheur — clic, hover, scroll ou événement métier.
Accessibilité
- prefers-reduced-motion : absent du code — aucune règle
@media (prefers-reduced-motion: reduce)n'est incluse. Les transitions élastiques s'exécutent même si l'utilisateur a activé cette préférence système. Ajoutez@media (prefers-reduced-motion: reduce) { .stack-card { transition: none } }si votre contexte le requiert. - Les boutons Stack et Spread sont des
<button>natifs — focusables au clavier (Tab), activables à Entrée et Espace, sans aucun JS supplémentaire. - Aucun attribut ARIA n'est présent sur le wrapper ou les cartes. Si les cartes portent du contenu sémantique (images, liens), ajoutez
role="group"etaria-labelsur.stacked-spread-wrapper, et unaltsur chaque image. - Le contraste des textes dans les cartes dépend entièrement du contenu que vous y placez — l'effet lui-même n'impose aucun texte visible dans les cartes.
- Aucun piège de focus n'est introduit : le toggle ne déplace pas le focus et les cartes restent dans l'ordre naturel du DOM.
Compatibilité navigateur
Nécessite CSS transition, transform et sélecteurs :nth-child — supportés dans tous les navigateurs modernes depuis 2014. Zéro dépendance externe.
Sans support CSS transition (navigateurs < 2014), les cartes sautent instantanément d'un état à l'autre sans animation — la mise en page reste correcte. Aucune erreur JS n'est levée.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="morph-toggle-group">
<button class="morph-toggle active" onclick="setStackMode(this, 'stacked')">Stack</button>
<button class="morph-toggle" onclick="setStackMode(this, 'spread')">Spread</button>
</div>
<div class="stacked-spread-wrapper stacked" id="stackDemo">
<div class="stack-card">1</div>
<div class="stack-card">2</div>
<div class="stack-card">3</div>
<div class="stack-card">4</div>
<div class="stack-card">5</div>
<div class="stack-card">6</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 |
|---|---|---|
| .stack-card (width / height) | 60 px × 80 px | Dimensions de chaque carte. Modifiez directement les propriétés CSS — les offsets de pile et de grille sont en px absolus, à recalculer si vous changez fortement la taille. |
| transition (.stack-card) | .6s cubic-bezier(.34, 1.56, .64, 1) | Durée et courbe d'animation. Remplacez y1 (1,56) par une valeur ≤ 1 pour supprimer le rebond, ou montez à 1,8–2,2 pour un overshoot plus prononcé. |
| Spread colonnes (translateX) | −70 px · 0 · +70 px | Écartement horizontal de la grille 3×2. Ajustez les translateX dans les règles .spread .stack-card:nth-child(n) pour resserrer ou élargir les colonnes. |
| Spread rangées (translateY) | −30 px · +40 px | Espacement vertical entre les deux rangées. Modifiez les translateY des six règles nth-child en mode .spread. |
| Stack offsets (nth-child) | 2–5 px · 2–4° | Imperfection visuelle de la pile. Chaque carte a un translate et un rotate propres. Montez les valeurs pour une pile plus désordonnée, ramenez-les à zéro pour un empilement parfait. |
| .stack-card:nth-child(n) background | Dégradé indigo→violet→rose→rouge→ambre | Couleur individuelle de chaque carte. Remplacez le dégradé par une couleur unie, une background-image ou votre propre gradient brand. |
| CSS var --radius-md | 0.5rem | Rayon d'arrondi des cartes. Modifiez la variable sur :root pour passer de rectangles nets (0) à des cartes plus arrondies (1rem+). |
| setStackMode(el, mode) | — | Fonction globale — appelez-la depuis n'importe quel événement. Pour déclencher sans référence au bouton toggle, basculez les classes directement : document.getElementById('stackDemo').classList.replace('stacked','spread'). |
FAQ
.stack-card dans le wrapper. Pour la pile, ajoutez des règles :nth-child avec de petits décalages. Pour la grille, calculez de nouveaux translate selon le nombre de colonnes et de lignes voulu — il n'y a aucune logique JS à adapter, seulement du CSS.setStackMode cible l'id fixe 'stackDemo'. Pour plusieurs instances, donnez à chaque wrapper un id différent et créez une fonction de toggle par instance — ou factorisez en passant l'id en paramètre : function toggleStack(el, mode, id) { var w = document.getElementById(id); w.classList.remove('stacked','spread'); w.classList.add(mode); }.const w = document.getElementById('stackDemo'); w.classList.remove('stacked'); w.classList.add('spread');. Encapsulez dans un IntersectionObserver, un setInterval ou le callback de votre événement selon votre déclencheur.