Morph Transition
Un élément qui passe vraiment de display: flex à display: none — et retour — sans perdre son animation : il rétrécit à 0,3, s'arrondit en ovale et s'efface en 0,6 s avant de disparaître du flux, puis réapparaît par le même chemin grâce à @starting-style. transition-behavior: allow-discrete là où le navigateur sait animer display, repli mesuré en JavaScript ailleurs (Firefox), fondu de 0,15 s sous mouvement réduit.
Mis à jour le
Cet effet fait partie d'Effect.Labs — 840 effets vanilla, une partie gratuite, une partie premium. Transitions comprend 52 effets, dont 9 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
Le bloc .morph-tr-element (dégradé rose #ec4899 → #f43f5e, coins de 14 px) est piloté par un bouton qui le désigne par aria-controls. Pour le cacher, une seule classe, .morph-tr-display-none, qui demande à la fois display: none, opacity: 0, scale(0.3) et border-radius: 50%. D'ordinaire, display bascule d'un coup et coupe toute animation. Ici la transition le cite avec allow-discrete (transition: all 0.6s …, display 0.6s allow-discrete) : le navigateur garde l'élément affiché pendant les 0,6 s, joue le rétrécissement et l'arrondi, puis seulement le retire. Mesuré dans Chromium 151 et WebKit 26.5 : à 100 ms, opacité 0,38 à 0,43 et échelle 0,55 à 0,60 ; à 950 ms, display: none.
Le retour pose un autre problème : un élément qui sort de display: none n'a pas d'état « avant », donc rien à animer. La règle @starting-style lui en fournit un (opacité 0, échelle 0,3, coins à 50 %) : à l'affichage, il part de là et revient à sa forme sur la courbe à rebond cubic-bezier(0.34, 1.56, 0.64, 1) — mesuré à 100 ms : opacité 0,57 à 0,62, échelle 0,70 à 0,73, dans les trois moteurs. Conséquence à connaître : @starting-style joue aussi au premier affichage de la page, l'élément entre donc en douceur au chargement.
Firefox 153 répond oui à CSS.supports('transition-behavior', 'allow-discrete') mais n'anime pas encore display : sans repli, l'élément y disparaîtrait d'un coup (mesuré : display: none et opacité 0 dès 150 ms). Le script (2,4 Ko) le vérifie sur un témoin invisible au chargement — il passe un div en display: none avec la transition et lit si display a tenu. S'il n'a pas tenu, il anime par la classe .morph-tr-hidden puis pose .morph-tr-gone (display: none) au bout des 0,6 s ; à l'entrée, il retire .morph-tr-gone, force un calcul, puis retire .morph-tr-hidden. Même résultat mesuré dans Firefox : opacité 0,39 à 100 ms, display: none à 700 ms. Un départ caché se déclare par aria-expanded="false" sur le bouton : le script pose les classes de l'état caché qui conviennent au navigateur.
Accessibilité
- Vraiment absent une fois caché : en
display: none, l'élément n'est ni lu par les lecteurs d'écran, ni atteint par la touche Tab — ce qu'une simple opacité à 0 ne garantit pas. C'est l'intérêt de cet effet pour un champ de formulaire ou un panneau de filtres. - État du bouton : le bouton porte
aria-controlsetaria-expanded, mis à jour à chaque clic (truequand l'élément est affiché, mesuré). Pour une case à cocher qui affiche un champ (scène 1), gardez un vrai bouton ou une vraie case dont le changement appelle la même bascule. - Mouvement réduit : sous
prefers-reduced-motion, ni rétrécissement ni arrondi : un fondu de 0,15 s, puisdisplay: none(mesuré dans les trois moteurs : échelle 1 et opacité 0,08 à 0,11 à 100 ms, retiré à 350 ms). - Contraste : texte blanc en gras de 13,6 px sur #ec4899 → #f43f5e, soit 3,5 à 3,7:1, sous le seuil AA de 4,5:1. Pour un texte qui compte, prenez #be185d → #be123c (6,0 et 6,3:1) ou mettez le contenu sur fond clair, comme dans les scènes.
Compatibilité navigateur
CSS (@starting-style, transition-behavior: allow-discrete, @supports) + 2,4 Ko de JavaScript, aucune dépendance. Mesuré dans Chromium 151, WebKit 26.5 (chemin CSS) et Firefox 153 (chemin JavaScript) : même sortie animée suivie de display: none, même entrée, même état final, double clic rapide sans état bloqué, deux éléments indépendants dans la même page. Repères publics : @starting-style depuis Chrome 117, Safari 17.5 et Firefox 129 ; transition de display depuis Chrome 117 et Safari 18 ; Safari 17.4 à 17.6 ne connaît que la valeur allow-discrete, et le script y prend le relais.
Dans les navigateurs plus anciens, le script prend le chemin par classes : sortie animée puis display: none, entrée animée. Seule l'arrivée au chargement de la page (portée par @starting-style) n'y est pas animée. Sans JavaScript, le bouton ne fait rien et l'élément reste dans son état de départ.
Le code
Structure HTML à coller dans votre page (le CSS + JS sont inclus avec Premium) :
<div class="morph-tr-stage">
<div class="morph-tr-wrapper">
<div class="morph-tr-element" id="morphTrElement">…</div>
</div>
<button class="morph-tr-btn" type="button" aria-controls="morphTrElement" aria-expanded="true">…</button>
</div>
Le code complet HTML + CSS + JS de Morph Transition, prêt à coller — et les 840 effets. Ou 299 € TTC une fois, accès à vie.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Forme de sortie (CSS — .morph-tr-display-none, .morph-tr-hidden et @starting-style) |
scale(0.3), border-radius: 50%, opacité 0 |
translateY(-8px) à la place de l'échelle donne une sortie vers le haut plus sobre. Changez les trois règles ensemble pour que l'entrée et la sortie se répondent. |
Durée (CSS — 0.6s ; JS — 600 dans le repli) |
0,6 s | Gardez la même valeur dans le CSS (display 0.6s allow-discrete compris) et dans le délai du script, sinon Firefox retire l'élément trop tôt ou trop tard. |
Libérer la place (CSS — .morph-tr-wrapper et position: absolute de l'élément) |
cadre fixe de 200 × 100 px : la place reste réservée | Pour que le contenu suivant remonte, remettez l'élément dans le flux (position: relative) et retirez la hauteur du cadre. Ne posez pas de display avec un sélecteur plus fort que .morph-tr-element.morph-tr-display-none (style en ligne, id…) : la règle de l'état caché doit pouvoir le passer à none. Le display: flex du CSS vendu, moins précis, ne gêne pas. |
Départ caché (HTML — aria-expanded="false" sur le bouton, classe morph-tr-display-none sur l'élément) |
affiché au chargement | La classe évite un affichage furtif avant le script ; le script pose ensuite la bonne combinaison de classes pour le navigateur. |
Entrée au chargement (CSS — bloc @starting-style) |
l'élément affiché entre en douceur à l'ouverture de la page | Pour qu'il soit là d'emblée, ajoutez une classe sur la page après le premier affichage et limitez la règle @starting-style à cette classe. |
Rebond (CSS — cubic-bezier(.34, 1.56, .64, 1)) |
léger dépassement à l'entrée | ease pour un formulaire sérieux ; le rebond convient mieux à une bulle ou une carte. |
FAQ
display: none : pendant les 0,6 s, l'élément reste affiché (il rétrécit visuellement, mais scale ne change pas la place qu'il occupe), puis il est retiré et ce qui suit remonte d'un bloc. À l'entrée, c'est l'inverse : la place s'ouvre d'abord, l'élément grandit ensuite. Pour un glissement progressif du contenu suivant, il faut animer la hauteur, comme le fait Accordion Fold (fx-0720).CSS.supports('transition-behavior', 'allow-discrete') tout en retirant l'élément sans attendre la fin de la transition (mesuré). Le test sur témoin interroge le comportement réel : il passe un div invisible en display: none avec la transition et regarde si display tient encore. Le jour où Firefox animera display, il prendra le chemin CSS sans rien changer au code.@starting-style pour l'entrée.