"Üst katmanda" oluşturulan öğeler (ör. <dialog>, popover özelliğine sahip öğeler veya ipuçları), display: none ile görünür durum arasında geçiş yaptıkları için geçmişte animasyon oluşturmak zordu. Modern CSS, bu öğeler için sorunsuz giriş ve çıkış geçişleri sağlamak amacıyla @starting-style, transition-behavior: allow-discrete ve overlay özelliğini sunar. Aşağıdaki örneklerde yerel CSS iç içe yerleştirme özelliğinin kullanıldığını unutmayın.
Uygulama
1. Ayrık Geçişleri Etkinleştirme
display özelliğine animasyon eklemek için transition-behavior: allow-discrete değerini ayarlamanız gerekir. Bu sayede öğe, çıkış geçişi sırasında görünür kalır. Geçiş kısaltmaları kullanıyorsanız kısaltmanın bunu geçersiz kılmasını önlemek için transition-behavior: allow-discrete değerini daha sonra yerleştirdiğinizden emin olun.
2. overlay mülkü
Bir öğe üst katmana girdiğinde veya üst katmandan çıktığında overlay özelliğini geçirmelidir. Bu sayede öğe, animasyon süresince üst katmanda kalır ve diğer öğeler veya görüntü alanı tarafından erken kırpılması önlenir.
3. @starting-style ile Giriş Animasyonları
Bir öğenin ilk kez oluşturulduğunda veya display değeri none değerinden değiştiğinde başlangıç stillerini tanımlamak için @starting-style kuralını kullanın.
4. Arka plan animasyonu
::backdrop sözde öğesi, kendi özelliklerine geçişler uygulanarak benzer şekilde animasyonlu hale getirilebilir.
Örnek
/* 1. Define the visible (open) state */
dialog[open],
[popover]:popover-open {
opacity: 1;
transform: scale(1);
/* 2. Define the starting state for entry (must come after open state) */
@starting-style {
opacity: 0;
transform: scale(0.9);
}
}
/* 3. Define the base (closed/exit) state and transitions */
dialog,
[popover] {
opacity: 0;
transform: scale(0.9);
/* MANDATORY: transition display and overlay for top-layer elements */
transition-property: opacity, transform, display, overlay;
transition-duration: 0.3s;
transition-timing-function: ease-out;
/* Applies to discrete properties like display and overlay */
transition-behavior: allow-discrete; /* Note: be sure to write this after the shorthand */
}
/* 4. Animate the backdrop */
dialog::backdrop,
[popover]::backdrop {
background-color: rgba(0, 0, 0, 0);
transition-property: background-color, display, overlay;
transition-duration: 0.3s;
transition-timing-function: ease-out;
transition-behavior: allow-discrete;
}
dialog[open]::backdrop,
[popover]:popover-open::backdrop {
background-color: rgba(0, 0, 0, 0.5);
@starting-style {
background-color: rgba(0, 0, 0, 0);
}
}
/* 5. Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
dialog,
[popover] {
/* Disable movement and shorten duration for a simple fade */
transform: none;
transition-duration: 0.1s;
}
@starting-style {
dialog[open],
[popover]:popover-open {
transform: none;
}
}
}
Kısıtlamalar ve erişilebilirlik
- ZORUNLU: Üst katmana giren veya üst katmandan çıkan tüm öğeler için
transitionlistenizeoverlayekleyin. - ZORUNLU:
displaymülk geçişi içinallow-discretekullanın. - ZORUNLU: Geçişleri basitleştirerek (ör. dönüşümleri kaldırarak ve süreyi kısaltarak)
prefers-reduced-motionkullanarak azaltılmış hareketle ilgili kullanıcı tercihlerine uyun. - DO: Doğru basamaklandırmayı sağlamak için
@starting-stylebloğunu "açık" durum seçicisinin içine veya sonrasına yerleştirin. - YAPMAYIN: Çıkış animasyonları için
@starting-stylekullanmayın. Çıkış animasyonları, temel (kapalı) duruma geçişle tanımlanır.
Yedek stratejiler
transition-behavior için yedekler ve tarayıcı desteği
Firefox 129 ve sonraki sürümler, display özelliğini gerçekten geçiş yapmadan transition-behavior: allow-discrete (CSS.supports('transition-behavior', 'allow-discrete'), true değerini döndürür) ayrıştırır (Firefox hatası 1882408). Bu durum, öğelerin çıkışta hemen kaybolmasına neden olur.
Ayrık display geçiş desteğini güvenilir bir şekilde algılamak için yalnızca CSS.supports('transition-behavior', 'allow-discrete') özelliğine güvenmek yerine, geçici bir öğenin hesaplanan display değerinin none değerine geçiş yaparken görünür kalıp kalmadığını kontrol edin:
let supportsDisplayTransition;
function canTransitionDisplay() {
if (supportsDisplayTransition !== undefined) return supportsDisplayTransition;
if (!window.CSS?.supports?.('transition-behavior', 'allow-discrete') || !document.body) {
return false;
}
const probe = document.createElement('div');
// The shorthand is intentional here: browsers that don't parse allow-discrete
// drop the whole declaration, so display: none applies instantly and the probe
// correctly returns false. !important guards against global reduced-motion
// resets like `* { transition: none !important }`.
probe.style.cssText = 'transition: display 1s allow-discrete !important; display: block;';
document.body.appendChild(probe);
getComputedStyle(probe).display;
probe.style.display = 'none';
supportsDisplayTransition = getComputedStyle(probe).display === 'block';
probe.remove();
return supportsDisplayTransition;
}
Üst katman çıkış yedeği
Giriş animasyonları, @starting-style özelliğini destekleyen tüm tarayıcılarda saf CSS ile çalışır. Giriş geçişleri overlay veya ayrı display geçişlerine bağlı olmadığından .is-opening sınıfı gerekmez.
Çıkış animasyonları için hem overlay hem de ayrı display geçiş desteği gerekir. İkisinden biri desteklenmediğinde (ör. Firefox ve Safari'de) açık durum seçicileri :is() içine alın ve :where(:not([data-closing])) (&::backdrop ve @starting-style iç içe yerleştirme) ekleyin. Böylece data-closing ayarı, öğe üst katmanda kalırken çıkış geçişini tetikler. Ardından .close() veya .hidePopover() yöntemini çağırmadan önce getAnimations() öğesinin yerleşmesini bekleyin:
:is(dialog[open], [popover]:popover-open):where(:not([data-closing])) {
opacity: 1;
transform: scale(1);
@starting-style {
opacity: 0;
transform: scale(0.9);
}
&::backdrop {
background-color: rgb(0 0 0 / 0.5);
@starting-style {
background-color: transparent;
}
}
}
// Evaluate lazily: canTransitionDisplay() needs document.body, so a top-level
// const would be permanently false if this script runs in <head>.
function supportsTopLayerExit() {
return window.CSS?.supports?.('overlay', 'auto') && canTransitionDisplay();
}
async function closeTopLayer(element) {
if (!supportsTopLayerExit()) {
element.setAttribute('data-closing', '');
const animations = element.getAnimations({ subtree: true });
if (animations.length > 0) {
await Promise.race([
Promise.allSettled(animations.map((a) => a.finished)),
new Promise((r) => setTimeout(r, 2000)),
]);
}
if (!element.hasAttribute('data-closing')) return;
element.removeAttribute('data-closing');
}
element.close();
// Or for popover:
// element.hidePopover();
}
// Route native close requests (Esc, closedby light dismiss) through the same
// helper so they animate too, instead of closing instantly.
dialog.addEventListener('cancel', (event) => {
event.preventDefault();
closeTopLayer(dialog);
});
Popover'ın hafif kapatma ve popovertarget açma/kapatma işlemleri engellenemez (beforetoggle yalnızca açılırken iptal edilebilir). Bu nedenle, yedek gerektiren tarayıcılarda bu çıkışlar anında gerçekleşir. Çıkış animasyonu önemliyse closeTopLayer() yöntemini çağıran açık bir kapatma kontrolü sağlayın.
Popover için yedekler ve tarayıcı desteği
Popover API çoğunlukla progresif geliştirme ile ilgilidir ancak tanımlayıcı davranışlarının (üst katman tanıtımı, hafif kapatma ve popovertarget çağrısı) yalnızca CSS'ye dayalı bir eşdeğeri yoktur. Eski tarayıcılar için polyfill veya polyfill kullanmak istemiyorsanız manuel yedek gerekir.
Polyfill: Eski tarayıcılarda popover özelliğini desteklemek için @oddbird/popover-polyfill öğesini koşullu olarak yükleyin. ZORUNLU: HTMLElement.prototype üzerinde popover özelliğini kontrol ederek özelliği tespit edin ve polyfill'i yalnızca yerel destek eksik olduğunda yükleyin. Koşulsuz olarak YÜKLEMEYİN.
Paketleyici veya içe aktarma eşlemesiyle:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Paketleyici olmadan, <script type="module"> içindeki bir CDN'den içe aktarın:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Stil uyarısı: Polyfill, gerçek :popover-open sözde sınıfını tanımlayamadığı için bunun yerine .\:popover-open sınıfını uygular. ZORUNLU: İkisini :is() veya :where() ile birleştirin. Aksi takdirde, :popover-open içermeyen tarayıcılar kuralın tamamını siler:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Alternatif olarak, polyfill içermeyen eski bir yedek için position: fixed kullanın ve koordinatları getBoundingClientRect() aracılığıyla manuel olarak hesaplayın veya kullanım alanına uygunsa inset: auto ile varsayılan konumlandırmayı kullanın.