Transition ve Animation: @keyframes, Easing, @starting-style ve prefers-reduced-motion
Bir dialog display: none'dan açıldığı için önceki stili yoktur; transition'ı başlatmak için @starting-style ile başlangıç değeri, kapanışı canlandırmak için display ve overlay'e allow-discrete gerekir. Yazı transition ile animation farkını, linear() dahil easing'i, transform ve opacity'nin neden ucuz olduğunu ve prefers-reduced-motion ile hareketi kapatmadan azaltmayı Bookstore'un sepet dialog'unda ölçerek anlatıyor.
Sürüm notu: Bu yazı CSS Transitions, CSS Animations ve CSS Easing Functions Level 2 spesifikasyonlarına ve Baseline'ın Kasım 2026 durumuna göre yazıldı; örnekler Chrome 158 ve WebKit 27.2 (Playwright 1.65) ile denenmiştir, Firefox desteği web-features 3.46 verisinden alınmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için sepet artık ayrı bir sayfa değil, katalogda açılan bir <dialog>. Tasarımcı dialog'un sert bir şekilde belirmesini istemiyor, yumuşakça görünsün diyor. İlk deneme şu iki satır:
dialog { opacity: 0; transition: opacity 200ms linear; }
dialog[open] { opacity: 1; }
Kapalı bir dialog'un opacity'si 0, açık olanınki 1 ve aradaki değişim için 200ms'lik bir transition var. showModal() çağrıldıktan 100ms sonra dialog'un opacity'si kaç olur: 0.5 mi, yoksa başka bir değer mi?
Serinin bu yazısında hareketli kısma geçiyoruz: transition ile animation farkı, easing fonksiyonları ve linear(), display: none'dan açılan bir element'i @starting-style ile canlandırmak, hangi property'leri canlandırmanın ucuz olduğu ve prefers-reduced-motion ile hareketi kapatmak yerine azaltmak. Bütün ölçümler Playwright ile; tarayıcının animasyonlarını Web Animations API'nin getAnimations()'ı ile durdurup istediğim milisaniyeye sardım, böylece çıktılar her çalıştırmada aynı.
Transition mı, Animation mı?
İkisi de bir property'nin değerini zaman içinde değiştirir ama farklı sorulara cevap verir. Transition bir durum değişikliğine tepkidir: property'nin değeri bir sebeple değişince (class eklendi, hover başladı) eski değerden yenisine geçişi yumuşatır. Başlangıç ve bitişi cascade belirler; transition yalnız "nasıl geçilsin" der. Animation ise @keyframes ile kendi değerlerini tanımlar, bir durum değişikliği beklemeden başlayabilir, tekrar edebilir ve ara adımlar içerebilir. Lab sayfasında ikisinin farkını gösteren iki küçük örnek var:
/* 2) transition bulunduğu yerden geri döner; animation baştan başlar */
.chip { opacity: 0.2; transition: opacity 1s linear; }
.chip.on { opacity: 1; }
/* 3) keyframes: gecikme ve fill-mode */
@keyframes bump {
from { scale: 1.5; }
to { scale: 1; }
}
.count { display: inline-block; animation: bump 400ms ease-out 200ms; }
.count.backwards { animation-fill-mode: backwards; }
node scripts/13-motion.mjs
--- chromium
easing 200ms 500ms 800ms
linear 20.0px 50.0px 80.0px
ease 29.5px 80.2px 97.6px
ease-out 30.8px 68.5px 93.8px
overshoot 70.3px 108.7px 104.2px
steps 0.0px 50.0px 75.0px
spring 56.0px 105.3px 100.5px
chip: opacity 0.6 at 500ms, then back to 0.2 in 500ms
during the delay: fill-mode none scale none, backwards scale 1.5
--- webkit: the same values
Çıktının son iki satırına bak. .chip'e on class'ı eklendi ve 1 saniyelik transition yarısına geldiğinde (opacity 0.6) class geri alındı. Transition baştan başlamıyor: o anki değerden geri dönüyor ve geri dönüşün süresi 1000ms değil 500ms. Spesifikasyon buna "reversing shortening factor" diyor; yarıda kesilip geri çevrilen bir transition, ileri giderken katettiği yol oranında kısalıyor. Fareyi bir butonun üstünden hızlıca geçiren kullanıcı bu sayede yavaş bir geri dönüş görmüyor. Bir animation'da böyle bir şey yok; class kaldırılıp tekrar eklenirse animation baştan başlar.
Son satır animation-fill-mode'un etkisi. bump animasyonu 200ms gecikmeyle başlıyor. Gecikme süresince fill-mode: none olan sayaç hiç etkilenmiyor (scale none) ve animasyon başladığı an 1.5'e sıçrıyor. backwards ise ilk keyframe'in değerini gecikme boyunca da uyguluyor: sayaç baştan 1.5 ölçeğinde bekliyor. Gecikmeli her giriş animasyonunda bu ayrıntı bir karelik bir sıçrama ile düzgün bir giriş arasındaki fark. Bookstore'da kural basit: bir durumdan diğerine geçiş varsa transition, kendi zamanlaması olan bir hareket (yükleniyor göstergesi, dikkat çeken bir vurgu) varsa animation.
Easing: cubic-bezier(), steps() ve linear()
Easing fonksiyonu, geçen zamanı ilerlemeye çevirir: süre yarıya geldiğinde değer yolun yüzde kaçında olsun? Lab'de aynı 1 saniyelik, 100 piksellik hareketi altı farklı fonksiyonla tanımladım:
/* 1) aynı 1 saniyelik hareket, altı farklı timing function ile */
.track { translate: 0 0; transition: translate 1s var(--easing); }
.moved .track { translate: 100px 0; }
#linear { --easing: linear; }
#ease { --easing: ease; }
#ease-out { --easing: ease-out; }
#overshoot { --easing: cubic-bezier(0.34, 1.56, 0.64, 1); }
#steps { --easing: steps(4, end); }
/* yay benzeri bir eğri: linear() noktaları düz çizgilerle birleştirir */
#spring { --easing: linear(0, 0.7 25%, 1.08 45%, 0.97 65%, 1.01 82%, 1); }
Çıktının tablosu 200, 500 ve 800. milisaniyelerde ölçülen konumlar. linear zamanla birebir gidiyor. Varsayılan ease çok hızlı başlıyor ve yarıda yolun %80'ine gelmiş oluyor; son 500ms'de yalnız 20 piksel kat ediyor. ease-out daha dengeli yavaşlıyor. Ekrana giren bir element için genellikle ease-out, ekrandan çıkan için ease-in doğal görünür: gelen şey hızlı gelip yavaşça yerine oturur, giden şey yavaşça kalkıp hızlanarak gider.
cubic-bezier() eğrinin iki kontrol noktasını kendin vermeni sağlar. Y değeri 1'den büyük bir kontrol noktası, değerin hedefi geçip geri gelmesini sağlıyor: overshoot satırında 500ms'de 108.7px, 800ms'de 104.2px, sonunda 100px. steps(4, end) ise hareketi dört eşit sıçramaya bölüyor; 200ms'de hâlâ 0, 500ms'de 50, 800ms'de 75. Sprite animasyonları ve daktilo efekti gibi kesikli hareketler için kullanılır.
cubic-bezier() tek bir eğri olduğu için hedefi bir kere geçip geri gelebilir ama birkaç kere salınamaz. Yay (spring) benzeri bir hareket için linear() var: verdiğin noktaları düz çizgilerle birleştiriyor. linear(0, 0.7 25%, 1.08 45%, 0.97 65%, 1.01 82%, 1) sürenin %45'inde hedefin %8 ötesine gidiyor, %65'te biraz geride kalıyor ve sonra oturuyor; çıktıda 500ms'de 105.3px, 800ms'de 100.5px. Nokta sayısı arttıkça eğri yumuşuyor; gerçek bir yay hareketinin noktalarını üreten araçlar var ve çıktıları doğrudan linear()'a yapıştırılabiliyor. linear() Aralık 2023'te Baseline oldu ve Haziran 2026'dan beri widely available. İki motor da tablodaki her değeri aynı hesapladı.
display: none'dan Açılan Dialog: @starting-style
Tahmin sorusuna dönelim. Lab'de üç dialog var; ilki sorudaki kod, sonraki ikisi adım adım düzeltilmiş hali:
/* 1) yalnız transition: başlanacak bir "önceki" stil yok */
dialog { opacity: 0; transition: opacity 200ms linear; }
dialog[open] { opacity: 1; }
/* 2) + @starting-style: açılış yumuşak */
#starting[open] {
@starting-style { opacity: 0; }
}
/* 3) + display ve overlay için allow-discrete: kapanış da yumuşak */
#discrete { transition: opacity 200ms linear, display 200ms allow-discrete, overlay 200ms allow-discrete; }
#discrete[open] {
@starting-style { opacity: 0; }
}
node scripts/13-dialog.mjs
--- chromium
plain
open 0 transitions, opacity 1.00, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
close 0 transitions, opacity 0.00, translate none, display none, backdrop rgba(0, 0, 0, 0)
starting
open 1 transitions, opacity 0.50, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
close 0 transitions, opacity 0.00, translate none, display none, backdrop rgba(0, 0, 0, 0)
discrete
open 1 transitions, opacity 0.50, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
close 3 transitions, opacity 0.50, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
catalog, no-preference
open 3 transitions, opacity 0.68, translate 0px 5.04571px, display block, backdrop rgba(0, 0, 0, 0.34)
close 5 transitions, opacity 0.32, translate 0px 10.9543px, display block, backdrop rgba(0, 0, 0, 0.157)
catalog, reduce
open 2 transitions, opacity 0.68, translate none, display block, backdrop rgba(0, 0, 0, 0.34)
close 4 transitions, opacity 0.32, translate none, display block, backdrop rgba(0, 0, 0, 0.157)
--- webkit
plain
open 0 transitions, opacity 1.00, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
close 0 transitions, opacity 0.00, translate none, display none, backdrop rgba(0, 0, 0, 0.1)
starting
open 1 transitions, opacity 0.50, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
close 0 transitions, opacity 0.00, translate none, display none, backdrop rgba(0, 0, 0, 0.1)
discrete
open 1 transitions, opacity 0.50, translate none, display block, backdrop rgba(0, 0, 0, 0.1)
close 0 transitions, opacity 0.00, translate none, display none, backdrop rgba(0, 0, 0, 0.1)
catalog, no-preference
open 3 transitions, opacity 0.68, translate 0px 5.045708px, display block, backdrop rgba(0, 0, 0, 0.345)
close 0 transitions, opacity 0.00, translate 0px 16px, display none, backdrop rgba(0, 0, 0, 0)
catalog, reduce
open 2 transitions, opacity 0.68, translate none, display block, backdrop rgba(0, 0, 0, 0.345)
close 0 transitions, opacity 0.00, translate none, display none, backdrop rgba(0, 0, 0, 0)
İlk dialog (plain) 100ms'de opacity 1 ve çalışan transition sayısı 0. Transition hiç başlamadı. Sebebi, kapalı bir dialog'un display: none olması. Tarayıcı transition'ı bir element'in bir önceki stilinden yeni stiline geçerken başlatır; display: none olan bir element'in bir önceki stili yoktur, çünkü hiç render edilmemiştir. Dialog açıldığı ilk karede doğrudan son değeriyle, opacity 1 ile çizilir. Aynı sorun popover'larda, hidden ile gizlenen element'lerde ve DOM'a (Document Object Model; sayfanın tarayıcıdaki element ağacı) yeni eklenen her element'te var.
@starting-style bu eksik "önceki stili" tanımlar: element ilk kez render edildiğinde (ya da display: none'dan çıktığında) transition'ın hangi değerden başlayacağını söyler. İkinci dialog'da (starting) 100ms'de opacity 0.50; açılış artık yumuşak. @starting-style'ı [open] kuralının içine nested yazdım; bu durumda kendi selector'ü olmuyor ve ebeveyninin selector'üne uygulanıyor. Ağustos 2024'ten beri Baseline (Firefox 129 ile).
Kapanış: allow-discrete ve overlay
İkinci dialog açılırken yumuşak ama kapanırken hâlâ bir anda kayboluyor: kapanışta opacity 0, display none. Kapanınca display hemen none oluyor ve element artık çizilmediği için opacity'nin transition'ını görecek bir şey kalmıyor. display kesikli (discrete) bir property; iki değeri arasında ara değer yok. transition-behavior: allow-discrete (ya da transition kısaltmasının içinde allow-discrete kelimesi) kesikli property'lerin de transition'a girmesine izin veriyor. Kesikli property'ler normalde sürenin yarısında değer değiştirir; display için ayrı bir kural var: none'a ya da none'dan geçişte görünür değer transition'ın bütün süresi boyunca kullanılır. Açılışta dialog ilk karede görünür oluyor, kapanışta ise transition bitene kadar display: block kalıyor.
Modal bir dialog'da bir property daha var: overlay. Bir element'in top layer'da olup olmadığını gösteren bu property'yi yalnız tarayıcı değiştirebilir, ama transition'a eklenebilir. Eklemezsen dialog kapanınca top layer'dan hemen çıkar ve fade boyunca sayfanın normal akışında, başka element'lerin altında görünebilir. Üçüncü dialog (discrete) Chromium'da kapanırken de 100ms'de opacity 0.50 ve display block: üç transition çalışıyor (opacity, display, overlay).
WebKit'in çıktısı burada ayrılıyor: üçüncü dialog'un kapanışında transition yok ve dialog hemen kayboluyor. web-features'a göre display'in transition'ı Chrome 117 ve Safari 18'de var, Firefox'ta yok; overlay ise yalnız Chrome'da. Yani açılış animasyonu Baseline, kapanış animasyonu değil. Playwright'ın WebKit build'i yayınlanmış Safari ile birebir aynı olmadığı için Safari'nin bu durumda nasıl davrandığını bu ölçümle kesin söyleyemem; ama tasarım açısından sonuç aynı: kapanış animasyonunu progressive enhancement olarak düşün. Desteklemeyen tarayıcıda dialog yalnız animasyonsuz kapanıyor, başka hiçbir şey bozulmuyor.
Bookstore'un Sepet Dialog'u
Katalog sayfasının header'ındaki sepet bağlantısı artık bir buton ve sepeti bir dialog'da açıyor:
<button class="cart-link btn" type="button" commandfor="cart-dialog" command="show-modal">Cart <span class="cart-count">1</span></button>
<dialog id="cart-dialog" class="cart-dialog" aria-labelledby="cart-title">
<h2 id="cart-title">Your cart</h2>
<ul class="cart-items">
<li>Dune <span class="price">349.00 TRY</span></li>
</ul>
<div class="cart-actions">
<button class="btn" type="button" commandfor="cart-dialog" command="close">Continue shopping</button>
<a class="btn btn-primary" href="checkout.html">Checkout</a>
</div>
</dialog>
Hiç JavaScript yok. commandfor ve command attribute'ları invoker commands özelliği: buton, id'si verilen dialog'da show-modal ya da close komutunu çalıştırıyor. Aralık 2025'ten beri Baseline; HTML tarafı HTML5 ve Semantic HTML serisinin native etkileşimli element'ler yazısında anlatıldı. Dialog'un stilleri yeni bir component dosyasında, preferences.css'ten önce components layer'ına import ediliyor:
/* sepet dialog'u top layer'da; display: none'dan yumuşakça belirir ve yine yumuşakça kaybolur */
.cart-dialog {
inline-size: min(28rem, 100% - 2 * var(--space));
padding: var(--space);
border: 1px solid var(--color-border);
border-radius: var(--radius);
background-color: var(--color-surface);
color: var(--color-text);
opacity: 0;
/* display ve overlay kesikli: allow-discrete, dialog'u fade bitene kadar görünür ve top layer'da tutar */
transition:
opacity 200ms ease-out,
translate 200ms ease-out,
display 200ms allow-discrete,
overlay 200ms allow-discrete;
&[open] {
opacity: 1;
/* display: none'dan sonraki ilk karenin değerleri; transition'ın başlayacağı bir yer olsun diye */
@starting-style {
opacity: 0;
}
}
&::backdrop {
background-color: rgb(0 0 0 / 0%);
transition:
background-color 200ms ease-out,
display 200ms allow-discrete,
overlay 200ms allow-discrete;
}
&[open]::backdrop {
background-color: rgb(0 0 0 / 50%);
@starting-style {
background-color: rgb(0 0 0 / 0%);
}
}
}
/* kayma ek bir hareket: yalnız daha azını istemeyen kullanıcılar için; fade herkeste kalır */
@media (prefers-reduced-motion: no-preference) {
.cart-dialog {
translate: 0 1rem;
&[open] {
translate: 0 0;
@starting-style {
translate: 0 1rem;
}
}
}
}
.cart-items {
padding: 0;
list-style: none;
& li {
display: flex;
justify-content: space-between;
}
}
.cart-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
Lab'deki üç adımın hepsi burada: [open] içinde @starting-style, transition listesinde display ve overlay için allow-discrete. ::backdrop'un da kendi transition'ı var; arkadaki sayfa dialog'la birlikte kararıyor. Script'in "catalog, no-preference" satırlarında açılışın 100. milisaniyesinde opacity 0.68, dialog 1rem'lik yolun 5 pikselinde ve backdrop yarı yolda; kapanışta Chromium'da beş transition birlikte çalışıyor. Opacity 0.50 değil 0.68, çünkü burada ease-out kullandım.
Kayma hareketini ayrı bir media query'ye koydum ve bunu preferences.css'e değil component'in kendi dosyasına yazdım. preferences.css sayfa geneli tercihler için (smooth scroll, kontrast token'ları); bir dialog'un hangi hareketinin gereksiz olduğuna ise component karar verir ve bu bilgi component'le birlikte taşınmalı. Bu kararın ayrıntısı birkaç bölüm sonra.
Hangi Property'yi Canlandırmak Ucuz: transform, opacity ve will-change
Her property'yi canlandırmanın bedeli aynı değil. Bir property layout'u etkiliyorsa (genişlik, margin, top) tarayıcı her karede stili yeniden hesaplar, layout'u yeniden yapar ve sayfayı yeniden çizer. transform ve opacity ise layout'u etkilemez; tarayıcı element'i ayrı bir layer'a alıp her karede yalnız o layer'ın konumunu ya da saydamlığını değiştirebilir ve bu iş çoğunlukla ana thread'e uğramadan, compositor'da yapılır. Lab'de aynı hareketi bir kere margin-inline-start, bir kere transform ile yazdım ve Chromium'un DevTools protokolünden 1 saniye boyunca yapılan iş miktarını okudum:
/* aynı hareket, bir kere layout property'siyle, bir kere transform ile */
@keyframes slide-margin { to { margin-inline-start: 300px; } }
@keyframes slide-transform { to { transform: translateX(300px); } }
body.margin .box { animation: slide-margin 1s linear infinite alternate; }
body.transform .box { animation: slide-transform 1s linear infinite alternate; }
/* will-change, hiçbir şey hareket etmese de her kartı kendi layer'ına alır */
.card { inline-size: 100px; block-size: 20px; margin: 2px; background-color: rgb(229, 231, 235); }
body.promoted .card { will-change: transform; }
node scripts/13-cost.mjs
margin 1s: layouts 59, style recalcs 60
transform 1s: layouts 1, style recalcs 2
will-change off : 4 layers
will-change on 50 cards: 54 layers
Margin ile 1 saniyede 59 layout ve 60 stil hesaplaması var; ekran saniyede 60 kare çizdiği için her karede bir tane. Transform ile aynı sürede 1 layout ve 2 stil hesaplaması; animasyon başladıktan sonra ana thread neredeyse hiç çalışmıyor. Bu, ana thread JavaScript ile meşgulken bile transform animasyonunun takılmadan devam edebileceği anlamına geliyor. Bookstore'un dialog'u bu yüzden translate ve opacity kullanıyor; translate, transform'un ayrı yazılan bir parçası ve aynı şekilde layout'u etkilemiyor. Rendering pipeline'ın ayrıntısı ve bunun Core Web Vitals'a etkisi ilerideki Web Performansı serisinde.
will-change: transform tarayıcıya "bu element'in transform'u değişecek, hazırlıklı ol" der ve element'i önceden kendi layer'ına alır. Son iki satır bunun bedeli: 50 karta will-change verince sayfadaki layer sayısı 4'ten 54'e çıktı, oysa hiçbir şey hareket etmiyor. Her layer GPU (Graphics Processing Unit; ekran kartının işlemcisi) belleğinde ayrı bir yüzey demek; telefonlarda bu bellek çabuk dolar. will-change'i hareket etmek üzere olan birkaç element'e, mümkünse hareketten hemen önce ver ve iş bitince kaldır. Bir listedeki her karta CSS'te kalıcı olarak yazmak, çözmeye çalıştığın sorundan daha büyük bir sorun yaratır. Bu ölçüm yalnız Chromium'da, çünkü WebKit'in Playwright üzerinden böyle bir protokolü yok.
prefers-reduced-motion: Kapatmak Değil Azaltmak
İşletim sistemlerinde "hareketi azalt" ayarı var ve tarayıcı bunu prefers-reduced-motion: reduce olarak bildiriyor. Bu ayarı kullananların bir kısmı, büyük ekran hareketlerinde baş dönmesi ya da mide bulantısı yaşayan kişiler. Ayarın adı "azalt", "kapat" değil. Dialog'un yerinden kayması ekranın bir bölümünün hareket etmesi demek; yerinde yavaşça belirmesi ise bir durum değişikliğini anlatan, bu kullanıcılar için de zararsız ve yararlı bir geri bildirim. Bu yüzden Bookstore'da kayma prefers-reduced-motion: no-preference içinde, fade ise herkes için.
Script'teki "catalog, reduce" satırları Playwright'ın reducedMotion: 'reduce' seçeneğiyle açılan sayfa: translate none, ama opacity yine 0.68 ve çalışan transition sayısı bir eksik. Kuralı no-preference içine yazmak, yani hareketi tercihi bilinen kullanıcıya eklemek, tersinden daha güvenli: yeni bir hareket eklerken reduce bloğunu güncellemeyi unutursan hareket varsayılan olarak kapalı kalır. Bu tercihin erişilebilirlik gerekçesi ilerideki Accessibility (a11y) serisinde.
Üretimde Dikkat
Bu yazıdaki özelliklerin Baseline durumu farklı. Transition, animation ve cubic-bezier() yıllardır her yerde var. linear() widely available. @starting-style ve transition-behavior Ağustos 2024'ten, invoker commands Aralık 2025'ten beri Baseline. display'in transition'ı ve overlay ise Baseline değil. Bookstore'un sepet dialog'u bu yüzden üç kademede çalışıyor: her tarayıcıda açılıp kapanıyor, Baseline tarayıcılarda yumuşak açılıyor, Chromium'da kapanırken de yumuşak.
Animasyonlu bir element'i test ederken zamanlamaya güvenme. Playwright'ın waitForTimeout'u ile "200ms bekle, sonra ölç" yapan bir test, yavaş bir CI makinesinde ara bir değer okuyabilir. Bu yazıdaki script'ler gibi animasyonları getAnimations() ile durdurup belirli bir ana sarmak ya da animation.finished promise'ini beklemek her çalıştırmada aynı sonucu verir. Uçtan uca testlerde reducedMotion: 'reduce' ile çalışmak da bekleme süresini kısaltır, ama o zaman hareketli varyantı ayrıca test etmen gerekir.
Transition süreleri de bir tasarım kararı ama bir sınırı var: kullanıcının beklediği bir değişiklik 200–300ms'den uzun sürerse arayüz yavaş hissettirir. Dialog ve menü gibi sık açılan element'lerde kısa süreler kullan; uzun ve dikkat çekici hareketleri ilk açılış gibi bir kere görülen yerlere bırak.
Sık Düşülen Tuzaklar
Birincisi, display: none'dan açılan bir element'e yalnız transition yazıp çalışmasını beklemek. Başlangıç değerini @starting-style vermezse transition hiç başlamaz.
İkincisi, transition: all. Hangi property'nin değiştiğini düşünmeden yazılan all, layout tetikleyen property'leri de canlandırır ve bir tema değişikliğinde sayfadaki her rengin yavaşça değişmesine sebep olur. Transition'a giren property'leri tek tek yaz.
Üçüncüsü, prefers-reduced-motion: reduce için bütün sayfaya animation-duration: 0.01ms !important veren hazır bir kural kopyalamak. Bu hem bilgilendiren fade'leri de yok eder hem de !important yüzünden, serinin ilk yazısında gördüğümüz gibi, layer sırasını tersine çevirir. Azaltma kararını component component ver.
Dördüncüsü, will-change'i performans iyileştirmesi sanıp her yere yazmak. Ölçtüğümüz gibi her biri bir layer ve bellek demek.
Küçük Bir Deneme
★☆☆ Lab'deki #spring eğrisine iki nokta daha ekle (örneğin 0.995 90%) ve node scripts/13-motion.mjs çıktısında 800ms'deki değerin nasıl değiştiğini tahmin edip kontrol et.
★★☆ book.html'deki .stock-note şu an hidden ile gizli. Görünür olduğunda 300ms içinde belirmesini @starting-style ile sağla ve script'le 150. milisaniyedeki opacity'yi ölç. hidden attribute'unun reset'teki !important'lı kuralını göz önünde bulundur.
★★★ Sepet dialog'unu modal olmayan bir popover'a (popover attribute'u ve command="toggle-popover") çevir ve aynı giriş-çıkış animasyonunu yaz. 13-dialog.mjs'i popover için genişletip Chromium ile WebKit arasındaki farkı yeniden ölç.
Bir sonraki yazıda zamana değil scroll'a bağlı animasyonlara geçiyoruz: animation-timeline ile scroll() ve view(), bir okuma ilerleme çubuğu ve ekrana girerken beliren kitap kartları; ardından view transition'lar ile sayfa ve liste geçişleri, Angular router'da withViewTransitions() ve ASP.NET Core MVC (Model-View-Controller; veriyi, görünümü ve isteği karşılayan kodu ayıran yapı) gibi çok sayfalı uygulamalarda cross-document view transition.