À la prochaine intersection, qui a la priorité ?
Réponse enregistrée. 39 questions répondues avant celle-ci.
37/40
Admis
Il fallait 35 bonnes réponses. 3 erreurs à revoir.
Une fenêtre modale qui s'ouvre en trois temps : le fond s'assombrit, deux rideaux glissent des bords et se rejoignent au centre en 0,6 s, puis le contenu grossit devant eux après 0,5 s — et tout se referme dans l'ordre inverse. Un vrai élément dialog ouvert par showModal (page derrière inerte, focus dans la fenêtre, Échap), 2,3 Ko de JavaScript, état final direct si l'utilisateur réduit les animations.
Mis à jour le
Cet effet est gratuit — la catégorie Transitions en contient 52 au total, dont 9 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
À la prochaine intersection, qui a la priorité ?
Réponse enregistrée. 39 questions répondues avant celle-ci.
37/40
Il fallait 35 bonnes réponses. 3 erreurs à revoir.
Cirque Volpi
Sam. 14 mars · 15 h
Gradin B · rang 4 · places 12 à 14
84 €Gradin B, rang 4 · billets envoyés par e-mail
COMPAGNIE DU GRENIER
Troupe de théâtre amateur · saison 2027
● La mise en scène a publié la distribution
Première répétition mardi à 20 h · salle polyvalente
Votre rôle
La fenêtre est un élément <dialog> plein écran (position: fixed, inset: 0, 100 % × 100 %, sans bordure ni fond) qui contient quatre calques : un fond .dialog-curtain-backdrop, deux rideaux .dialog-curtain-left et .dialog-curtain-right de 50 % de large chacun, et la carte .dialog-curtain-content centrée par translate(-50%, -50%). Au repos, les rideaux attendent hors champ (translateX(-100%) et translateX(100%)), le fond est transparent et la carte vaut opacity: 0 et scale(0.8). La classe .dialog-curtain-open donne l'état d'arrivée : fond noir à 50 %, rideaux à translateX(0), carte à 1 et 1.
Les trois temps viennent des durées et des retards : fond en 0,6 s, rideaux en 0,6 s sur cubic-bezier(0.65, 0, 0.35, 1), carte en 0,4 s avec transition-delay: 0.5s et un léger rebond. Mesuré 250 ms après l'ouverture : rideau gauche encore à −334 px sur 450, fond à 0,35, carte invisible ; à 700 ms, rideaux fermés et carte à 0,8 ; tout est en place vers 0,9 s (mêmes valeurs dans Chromium 151, Firefox 153 et WebKit 26.5). La fermeture pose .dialog-curtain-closing, qui inverse l'ordre : la carte s'efface en 0,2 s, puis rideaux et fond repartent avec 0,15 s de retard. Le script laisse 700 ms à cette sortie avant d'appeler dialog.close().
Le script (2,3 Ko) relie chaque bouton .dialog-curtain-btn à sa fenêtre par aria-controls, ce qui permet plusieurs fenêtres dans la même page. À l'ouverture, il appelle showModal(), lit offsetWidth puis seulement ajoute la classe : la fenêtre sort de display: none, et sans ce calcul intermédiaire le navigateur n'aurait pas d'état de départ et afficherait directement l'état final, sans aucun des trois temps. Il intercepte l'évènement cancel (Échap) pour jouer la fermeture animée au lieu d'une disparition sèche, ferme aussi au clic sur le fond ou sur les rideaux — une fois fermés, ils couvrent tout l'écran — et remet les classes à zéro à chaque évènement close, quelle qu'en soit l'origine. La vignette .dialog-curtain-preview rejoue les rideaux en petit dans la page ; elle est facultative.
showModal() rend le reste de la page inerte (ni clic ni tabulation derrière), place la fenêtre au-dessus de tout et donne le focus à son premier bouton, « Fermer » (mesuré dans les trois moteurs). À la fermeture, le focus revient au bouton qui l'a ouverte (mesuré dans Chromium et Firefox ; dans Safari, quand l'ouverture s'est faite au clavier). La fenêtre est nommée par son titre (aria-labelledby) et le bouton annonce aria-haspopup="dialog".prefers-reduced-motion, toutes les transitions de la scène et de la fenêtre sont coupées : les rideaux et la carte prennent directement leur état, et la fenêtre se ferme sans délai (mesuré : carte à 1 dès 250 ms, fermée 200 ms après Échap).Élément <dialog> et showModal(), pseudo-élément ::backdrop, transitions CSS + 2,3 Ko de JavaScript, aucune dépendance. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 : mêmes trois temps à l'ouverture, même fermeture inverse, Échap animé, focus dans la fenêtre puis rendu au bouton, deux fenêtres indépendantes dans la même page. showModal() existe depuis Chrome 37, Firefox 98 et Safari 15.4 ; la propriété inset depuis Chrome 87 et Safari 14.1.
Sans l'élément dialog (navigateurs antérieurs à 2022), le script ne relie pas le bouton et la fenêtre reste fermée : prévoyez un lien vers une page de confirmation si le contenu est indispensable. Sans JavaScript, même chose : le bouton ne fait rien. Sous mouvement réduit, la fenêtre s'ouvre et se ferme sans animation, avec les mêmes garanties de focus.
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="dialog-curtain-stage">
<div class="dialog-curtain-preview" aria-hidden="true">
<div class="dialog-curtain-preview-left"></div>
<div class="dialog-curtain-preview-right"></div>
<span class="dialog-curtain-preview-label">Rideau de scène</span>
</div>
<button class="dialog-curtain-btn" type="button" aria-haspopup="dialog" aria-controls="dialogCurtainModal">Ouvrir la fenêtre</button>
<dialog class="dialog-curtain-modal" id="dialogCurtainModal" aria-labelledby="dialogCurtainTitle">
<div class="dialog-curtain-backdrop"></div>
<div class="dialog-curtain-left"></div>
<div class="dialog-curtain-right"></div>
<div class="dialog-curtain-content">
<h3 id="dialogCurtainTitle">Rideau de scène</h3>
<p>Le contenu apparaît une fois les rideaux fermés.</p>
<button class="dialog-curtain-close" type="button">Fermer</button>
</div>
</dialog>
</div>
.dialog-curtain-stage,
.dialog-curtain-stage *,
.dialog-curtain-stage *::before,
.dialog-curtain-stage *::after {
box-sizing: border-box
}
.dialog-curtain-stage {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 250px;
overflow: hidden;
position: relative;
background: rgb(26, 26, 36);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.dialog-curtain-btn {
padding: 10px 24px;
font-size: 0.85rem;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
font-family: inherit;
background: #6366f1;
color: white;
transition: background 0.2s, transform 0.1s;
}
.dialog-curtain-btn:hover {
background: #4f46e5;
}
.dialog-curtain-btn:active {
transform: scale(0.96);
}
.dialog-curtain-preview {
position: relative;
width: 200px;
height: 120px;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
background: #0a0a1a;
}
.dialog-curtain-preview-left,
.dialog-curtain-preview-right {
position: absolute;
top: 0;
width: 50%;
height: 100%;
transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}
.dialog-curtain-preview-left {
left: 0;
background: linear-gradient(135deg, #312e81, #4338ca);
transform: translateX(-100%);
}
.dialog-curtain-preview-right {
right: 0;
background: linear-gradient(225deg, #312e81, #4338ca);
transform: translateX(100%);
}
.dialog-curtain-preview.dialog-curtain-active .dialog-curtain-preview-left {
transform: translateX(0);
}
.dialog-curtain-preview.dialog-curtain-active .dialog-curtain-preview-right {
transform: translateX(0);
}
.dialog-curtain-preview-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
opacity: 0;
color: white;
font-weight: 600;
font-size: 0.75rem;
z-index: 2;
transition: opacity 0.3s ease 0.4s, transform 0.3s ease 0.4s;
white-space: nowrap;
}
.dialog-curtain-preview.dialog-curtain-active .dialog-curtain-preview-label {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.dialog-curtain-left,
.dialog-curtain-right {
width: 50%;
height: 100%;
background: linear-gradient(135deg, #1a1a2e, #0f172a);
}
.dialog-curtain-left {
transform-origin: left center;
}
.dialog-curtain-right {
transform-origin: right center;
}
.dialog-curtain-modal {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
margin: 0;
padding: 0;
border: none;
background: transparent;
color: #fff;
overflow: hidden;
font-family: sans-serif;
}
/* Le fond assombri animé est .dialog-curtain-backdrop : celui du navigateur reste transparent. */
.dialog-curtain-modal::backdrop {
background: transparent;
}
.dialog-curtain-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0);
transition: background 0.6s ease;
}
.dialog-curtain-modal.dialog-curtain-open .dialog-curtain-backdrop {
background: rgba(0, 0, 0, 0.5);
}
.dialog-curtain-modal .dialog-curtain-left,
.dialog-curtain-modal .dialog-curtain-right {
position: absolute;
top: 0;
width: 50%;
height: 100%;
transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}
.dialog-curtain-modal .dialog-curtain-left {
left: 0;
background: linear-gradient(135deg, #1e1b4b, #312e81);
transform: translateX(-100%);
}
.dialog-curtain-modal .dialog-curtain-right {
right: 0;
background: linear-gradient(225deg, #1e1b4b, #312e81);
transform: translateX(100%);
}
.dialog-curtain-modal.dialog-curtain-open .dialog-curtain-left {
transform: translateX(0);
}
.dialog-curtain-modal.dialog-curtain-open .dialog-curtain-right {
transform: translateX(0);
}
.dialog-curtain-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
opacity: 0;
background: linear-gradient(135deg, #1e1e2e, #2d2b55);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 32px;
color: white;
text-align: center;
z-index: 2;
transition: opacity 0.4s ease 0.5s, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.5s;
min-width: 200px;
}
.dialog-curtain-modal.dialog-curtain-open .dialog-curtain-content {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.dialog-curtain-modal.dialog-curtain-closing .dialog-curtain-content {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.dialog-curtain-modal.dialog-curtain-closing .dialog-curtain-left {
transform: translateX(-100%);
transition-delay: 0.15s;
}
.dialog-curtain-modal.dialog-curtain-closing .dialog-curtain-right {
transform: translateX(100%);
transition-delay: 0.15s;
}
.dialog-curtain-modal.dialog-curtain-closing .dialog-curtain-backdrop {
background: rgba(0, 0, 0, 0);
transition-delay: 0.15s;
}
.dialog-curtain-content h3 {
font-size: 1.2rem;
margin: 0 0 8px;
}
.dialog-curtain-content p {
margin: 0 0 16px;
opacity: 0.7;
font-size: 0.85rem;
}
.dialog-curtain-close {
padding: 8px 20px;
background: #6366f1;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-family: inherit;
font-weight: 600;
font-size: 0.85rem;
}
.dialog-curtain-close:focus-visible {
outline: 2px solid #c7d2fe;
outline-offset: 3px;
}
/* Mouvement réduit : pas de rideaux qui glissent, chaque élément prend directement son état. */
@media (prefers-reduced-motion: reduce) {
.dialog-curtain-stage *,
.dialog-curtain-modal *,
.dialog-curtain-modal *::before,
.dialog-curtain-modal *::after {
transition: none !important;
}
}
(function() {
/* Dialog Curtain : un vrai <dialog> ouvert en modale (showModal) — le reste de la page devient
inerte, le focus entre dans la fenêtre et revient au bouton à la fermeture. Les classes
ne pilotent que l'animation en trois temps : fond, rideaux, puis contenu. */
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
document.querySelectorAll('.dialog-curtain-btn[aria-controls]').forEach(function(btn) {
var dialog = document.getElementById(btn.getAttribute('aria-controls'));
if (!dialog || typeof dialog.showModal !== 'function') return;
var stage = btn.closest('.dialog-curtain-stage');
var preview = stage && stage.querySelector('.dialog-curtain-preview');
var timer = null;
function reset() {
clearTimeout(timer);
timer = null;
dialog.classList.remove('dialog-curtain-open', 'dialog-curtain-closing');
}
function open() {
if (dialog.open) return;
reset();
dialog.showModal();
void dialog.offsetWidth; /* point de départ des transitions : rideaux ouverts, contenu caché */
dialog.classList.add('dialog-curtain-open');
if (preview) preview.classList.add('dialog-curtain-active');
}
function close() {
if (!dialog.open || timer) return;
dialog.classList.add('dialog-curtain-closing');
if (preview) preview.classList.remove('dialog-curtain-active');
timer = setTimeout(function() {
dialog.close();
}, reduce.matches ? 0 : 700);
}
btn.addEventListener('click', open);
/* Une fois fermés, les rideaux recouvrent tout le fond : un clic dessus ferme aussi. */
dialog.querySelectorAll('.dialog-curtain-close, .dialog-curtain-backdrop, .dialog-curtain-left, .dialog-curtain-right').forEach(function(el) {
el.addEventListener('click', close);
});
/* Échap : la fermeture passe par l'animation inverse au lieu d'être immédiate. */
dialog.addEventListener('cancel', function(e) {
e.preventDefault();
close();
});
/* Quelle que soit la façon dont la fenêtre se ferme, elle repart propre. */
dialog.addEventListener('close', function() {
reset();
if (preview) preview.classList.remove('dialog-curtain-active');
});
});
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Rideaux (CSS — .dialog-curtain-modal .dialog-curtain-left et -right) |
dégradés #1e1b4b → #312e81, 0,6 s sur cubic-bezier(0.65, 0, 0.35, 1) |
Un repeating-linear-gradient donne des plis de velours ; 0,9 s rend la fermeture plus théâtrale. Changez les deux rideaux ensemble. |
Retard du contenu (CSS — transition-delay: 0.5s sur .dialog-curtain-content) |
0,5 s : la carte arrive quand les rideaux finissent | 0,2 s fait arriver la carte pendant que les rideaux glissent ; au-delà de 0,6 s, un temps mort s'installe entre rideaux fermés et contenu. |
Durée de sortie (JS — 700 dans close()) |
700 ms | Elle doit couvrir à peu près la sortie CSS (0,15 s de retard + 0,6 s des rideaux) : à 700 ms, les rideaux sont à 99 % de leur course, rien de visible ne manque. Si vous allongez les rideaux, allongez ce délai, sinon la fenêtre disparaît avant leur retour. |
Fond assombri (CSS — rgba(0, 0, 0, 0.5) sur .dialog-curtain-open .dialog-curtain-backdrop) |
noir à 50 % | Il ne se voit que pendant le glissement des rideaux, qui couvrent ensuite tout l'écran. Pour garder la page visible autour, réduisez la largeur des rideaux (40 % chacun) : le fond prend alors tout son sens. |
Fermeture au clic (JS — liste .dialog-curtain-close, .dialog-curtain-backdrop, .dialog-curtain-left, .dialog-curtain-right) |
bouton, fond et rideaux ferment | Retirez les rideaux de la liste pour une confirmation qu'on ne doit pas fermer par erreur ; gardez au moins le bouton et Échap. |
Vignette d'aperçu (HTML — .dialog-curtain-preview) |
200 × 120 px, rideaux en petit et libellé | Supprimez-la sans toucher au script : elle ne sert qu'à montrer le motif dans la page. Le bouton suffit à ouvrir la fenêtre. |
Oui. Les rideaux partent des bords et se rejoignent au centre, comme un rideau de scène qui tombe ; la fenêtre se présente ensuite devant eux, et ils se rouvrent à la fermeture. Pour l'effet inverse, des rideaux qui s'écartent pour découvrir le contenu, placez la carte sous les rideaux (z-index), mettez les rideaux à translateX(0) au repos et à ±100% dans .dialog-curtain-open, et retirez le retard de la carte.
Une div en position: fixed se voit comme une fenêtre, mais la page derrière reste cliquable et atteignable au clavier, Échap ne fait rien et le focus reste sur le bouton caché dessous. showModal() règle tout cela d'un coup : page inerte, focus dans la fenêtre puis rendu au bouton, Échap, affichage au-dessus de tout z-index. Le script n'ajoute que l'animation.
Juste avant showModal(), la fenêtre est en display: none : elle n'a aucun état affiché d'où partir. Si la classe .dialog-curtain-open arrive dans la même image, le navigateur calcule directement l'état final et les transitions ne se jouent pas (rideaux déjà fermés, carte déjà visible). Lire offsetWidth force un calcul de l'état « rideaux ouverts, carte cachée » ; la classe ajoutée ensuite déclenche les trois temps. @starting-style ferait le même travail dans les navigateurs récents.