Masonry to Grid
Six items basculent en douceur entre un layout masonry organique et une grille stricte grâce à la technique FLIP — zéro bibliothèque, CSS flexbox + un seul requestAnimationFrame.
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
Deux classes CSS — .masonry-mode et .grid-mode — définissent les deux états sur le même conteneur flexbox enveloppant. En mode masonry, chaque item reçoit des dimensions individuelles via :nth-child (largeurs 40–55 %, hauteurs 40–70 px). En mode grille, tous passent à calc(33.33% - 6px) et 50 px de hauteur. Le swap de classe suffit — le moteur de layout recalcule seul ; aucun positionnement absolu.
La fluidité vient de la technique FLIP (First, Last, Invert, Play). Avant le changement de classe, setMasonryGrid() capture la géométrie de chaque item avec getBoundingClientRect() (First). Après le swap (Last), elle calcule le delta de position et d'échelle, puis applique immédiatement transform: translate(dx, dy) scale(sx, sy) avec transition: none pour maintenir les items visuellement à leur ancienne place (Invert).
Dans le frame suivant (requestAnimationFrame), la transition all 0.6s cubic-bezier(.4, 0, .2, 1) est restituée et le transform forcé est effacé (Play). Le navigateur anime chaque item depuis sa position fictive jusqu'à sa vraie cible CSS. Les deux layouts partagent les mêmes nœuds DOM — aucune création, aucune destruction d'élément au toggle.
La fonction globale setMasonryGrid(btn, mode) gère aussi l'état visuel des boutons toggle (.morph-toggle.active). L'implémentation est intégralement vanilla : CSS pour les layouts, un unique requestAnimationFrame pour le départ d'animation — pas de Framer Motion, pas de GSAP, pas de Web Animations API.
Accessibilité
- prefers-reduced-motion absent : la transition
.6ss'applique même si l'utilisateur a activé la réduction de mouvement dans son OS. À corriger avant production : testerwindow.matchMedia('(prefers-reduced-motion: reduce)').matcheset passertransition: nonedirectement à la place de0.6s cubic-bezier(…). - Les boutons de bascule sont des
<button>natifs — focusables au clavier sans configuration supplémentaire. Ajoutertype="button"pour éviter une soumission de formulaire parent accidentelle. - Aucun
aria-labelnirolesur le conteneur.masonry-grid-container. Si les items contiennent des images, ajouteraltdescriptifs ; si purement décoratifs, ajouteraria-hidden="true"sur le conteneur. - Les boutons
.morph-toggle.activepassent enbackground: #6366f1; color: #fff— contraste blanc sur indigo-500 ≈ 4,5:1, seuil WCAG AA atteint. - Aucun état
aria-pressedsur les boutons toggle : ajouteraria-pressed="true/false"depuis la fonctionsetMasonryGridpour signaler l'état actif aux lecteurs d'écran.
Compatibilité navigateur
Requiert CSS flexbox et requestAnimationFrame — supportés dans tous les navigateurs modernes. Zéro dépendance externe.
Sans JS, le conteneur reste en mode masonry initial (.masonry-mode) — layout statique valide, aucune erreur console. Les boutons toggle restent visibles mais inactifs.
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" type="button"
onclick="setMasonryGrid(this, 'masonry')">Masonry</button>
<button class="morph-toggle" type="button"
onclick="setMasonryGrid(this, 'grid')">Grille</button>
</div>
<div class="masonry-grid-container masonry-mode" id="masonryDemo">
<!-- Un .masonry-item par élément — background ou contenu en inline style -->
<div class="masonry-item" style="background: linear-gradient(135deg, #6366f1, #818cf8);"></div>
<div class="masonry-item" style="background: linear-gradient(135deg, #8b5cf6, #a78bfa);"></div>
<div class="masonry-item" style="background: linear-gradient(135deg, #d946ef, #e879f9);"></div>
<div class="masonry-item" style="background: linear-gradient(135deg, #ec4899, #f472b6);"></div>
<div class="masonry-item" style="background: linear-gradient(135deg, #f43f5e, #fb7185);"></div>
<div class="masonry-item" style="background: linear-gradient(135deg, #f59e0b, #fbbf24);"></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 |
|---|---|---|
| transition sur .masonry-item | .6s cubic-bezier(.4,0,.2,1) | Durée et easing de l'animation. Remplacez par .4s ease-out pour un rendu vif, ou .9s ease-in-out pour un tempo éditorial lent. |
| gap sur .masonry-grid-container | 6px | Espacement entre items dans les deux modes. 12px pour une galerie aérée, 2px pour un assemblage mosaïque serré. |
| width / height dans .masonry-mode:nth-child(n) | 45–55 % / 40–70 px | Dimensions de chaque item en masonry. Modifiez par :nth-child pour créer votre rythme organique — grand item en tête, secondaires plus petits. |
| width dans .grid-mode .masonry-item | calc(33.33% - 6px) | Largeur uniforme en mode grille. Changez 33.33% en 25% pour 4 colonnes, 50% pour 2 colonnes. |
| height dans .grid-mode .masonry-item | 50px | Hauteur uniforme en grille. Augmentez pour des cartes plus hautes ou passez à auto si les items ont du contenu texte à hauteur variable. |
| border-radius sur .masonry-item | 6px | Arrondi des coins. 12px pour un rendu carte, 0 pour une mosaïque stricte sans bords. |
FAQ
.masonry-item dans le HTML, puis ajustez les règles :nth-child dans le CSS pour définir la largeur et la hauteur de chaque item en mode masonry. En mode grille, le calc(33.33% - 6px) s'applique automatiquement à tous les items présents — aucune modification JS n'est nécessaire.setMasonryGrid(btn, mode) attend un élément bouton pour mettre à jour l'état .active. Si vous n'avez pas de bouton visible, passez null et protégez la ligne e.parentElement.querySelectorAll(…) avec un if (e). Appelez ensuite setMasonryGrid(null, 'grid') sur n'importe quel événement : scroll, timer, fin de chargement.setMasonryGrid depuis un useEffect ou un handler onClick après que le composant est monté dans le DOM. Dans Vue, utilisez mounted() ou un @click handler. La fonction ne touche qu'au DOM — pas de state framework, pas de conflit.