Geçmişte CSS geçişleri, DOM'a ilk eklendiklerinde veya display özellikleri none olarak değiştirildiğinde öğelere animasyon uygulayamıyordu. @starting-style kuralı ve transition-behavior: allow-discrete, sorunsuz giriş ve çıkış animasyonları oluşturmak için bildirim temelli bir yöntem sunar.
Uygulama
1. display: none açma/kapatma düğmesi animasyonu
Bir öğeyi, görünürlüğünü bir özellik aracılığıyla açıp kapatırken canlandırmak için (ör. display: none ile hidden):
- Görünür durumu tanımlayın: Temel sınıfta nihai özellik değerlerini (ör.
opacity: 1) ayarlayın. - Giriş başlangıç durumunu tanımlama: Öğe görünür hale geldiğinde hangi değerlerden geçiş yapılacağını belirtmek için
@starting-stylekullanın. - Ayrık geçişleri etkinleştirme:
transitionözelliğinedisplaydeğerini ekleyin vetransition-behavior: allow-discretedeğerini kullanın. - Çıkış durumunu tanımlayın:
hiddenözelliğinde hedef değerleri ayarlayın.
.card {
display: block;
opacity: 1;
translate: 0;
/* MANDATORY: Use transition-behavior: allow-discrete for display transition */
transition:
display 0.4s,
opacity 0.4s ease-out,
translate 0.4s ease-out;
transition-behavior: allow-discrete;
}
/* Entry animation: transition FROM these values when first rendered */
@starting-style {
.card {
opacity: 0;
translate: 0 -20px;
}
}
/* Exit animation: transition TO these values when hidden */
.card[hidden] {
display: none;
opacity: 0;
translate: 0 -20px;
}
/* Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
.card {
/* Disable movement and shorten duration for a simple fade */
translate: none;
transition-duration: 0.1s;
}
@starting-style {
.card {
translate: none;
}
}
.card[hidden] {
translate: none;
}
}
2. DOM Ekleme ve Kaldırma İşlemlerini Animasyonla Gösterme
appendChild() ile eklenen veya remove() ile kaldırılan öğeler için:
- Giriş: Yukarıda gösterildiği gibi
@starting-stylekullanın. Tarayıcı, "hiçbir şey"den öğenin ilk stillerine geçişi otomatik olarak algılar ve@starting-styledeğerlerinden geçişi tetikler. - Kaldırma:
element.remove()anında gerçekleştiğinden ve kendi başına bir CSS geçişini tetiklemediğinden, çıkış geçişini önce tetiklemeniz ve düğümü DOM'dan kaldırmadan önce bitmesini beklemeniz gerekir.
// 1. Trigger exit transition
element.hidden = true;
// 2. Wait for all active transitions/animations to finish,
// with a failsafe timeout in case an animation never ends (e.g. for looping animations)
const animations = element.getAnimations();
if (animations.length > 0) {
await Promise.race([
// Promise.allSettled ensures we wait even if some animations fail
Promise.allSettled(animations.map(a => a.finished)),
new Promise(r => setTimeout(r, 2000))
]);
}
// 3. Finally remove the node from the DOM
element.remove();
Kısıtlamalar ve erişilebilirlik
- ZORUNLU:
displaygeçişi yaparkentransition-behavior: allow-discretekullanın. Bu özellik kullanılmazsa öğe, çıkış sırasında anında kaybolur. transitionkısaltmasındaallow-discreteKULLANMAYIN. Bu, eski tarayıcıların tümtransitionbildirimini yok saymasına neden olur. Bunun istendiği kullanım alanları dışında ayrı birtransition-behavior: allow-discretebeyanı kullanın.- ZORUNLU: Giriş animasyonları için
@starting-stylekullanın. Tarayıcılar, bu sağlanmadığı sürece bir öğenin ilk stil güncellemesindeki (ilk oluşturma veyadisplay: nonedeğişikliği) geçişleri atlar. - DO:
<dialog>veyapopovergibi üst katman öğelerine animasyon ekliyorsanız çıkış animasyonu sırasında üst katmanda kalmalarını sağlamak içinoverlayöğesinitransitionlistesine ekleyin. - DO:
prefers-reduced-motionmedya sorgusunu kullanarak kullanıcıların daha az hareketle ilgili tercihlerine saygı gösterin. - YAPMAYIN: Çıkış animasyonları için
@starting-stylekullanmayın. Bu özellik yalnızca giriş geçişinin başlangıç noktasını tanımlar. Çıkış animasyonları, gizli 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;
}
Ayrık display geçişleri desteklenmediğinde çıkış yedeklemesi
@starting-style kullanan giriş animasyonları, JavaScript olmadan tüm modern tarayıcılarda çalışır. Ayrı display geçişleri desteklenmediğinde (!canTransitionDisplay()) hidden ayarı display: none değerini hemen uygular ve çıkış animasyonunu atlar (hem gizlerken hem de element.remove() öncesinde). Görsel çıkış durumunu ([data-closing]) display: none ([hidden]) değerinden ayırın. Böylece opacity ve translate, öğeyi gizlemeden veya kaldırmadan önce animasyonu tamamlar. 1. adımdaki .card[hidden] kurallarını (prefers-reduced-motion geçersiz kılma dahil) şunlarla değiştirin:
.card:where([hidden], [data-closing]) {
opacity: 0;
translate: 0 -20px;
}
.card[hidden] {
display: none;
}
@media (prefers-reduced-motion: reduce) {
.card:where([hidden], [data-closing]) {
translate: none;
}
}
async function hideElement(el) {
if (canTransitionDisplay()) {
el.hidden = true;
} else {
el.setAttribute('data-closing', '');
}
const animations = el.getAnimations();
if (animations.length > 0) {
await Promise.race([
Promise.allSettled(animations.map((a) => a.finished)),
new Promise((r) => setTimeout(r, 2000)),
]);
}
// Skipped if showElement() cancelled the close mid-animation.
if (el.hasAttribute('data-closing')) {
el.removeAttribute('data-closing');
el.hidden = true;
}
}
function showElement(el) {
el.removeAttribute('data-closing');
el.hidden = false;
}
async function removeElement(el) {
await hideElement(el);
el.remove();
}