Ders 15 / 23
Anahtar Kare Animasyonları
Kendi zaman çizelgesini taşıyan animasyonlar; anahtar kare kümesinin tanımı, yerel ilerlemenin yineleme ve yön değerlerinden çözülmesi, doldurma kipinin animasyon dışındaki değerleri ve zamanlama fonksiyonunun kare aralıklarına uygulanması.
İçindekiler
Geçiş bir koşula bağlıdır: bir değerin değişmesi gerekir. Değeri değiştirecek bir olay yoksa geçişin tetikleyicisi de yoktur. Ölçüm istasyonunun bağlantı göstergesi, kimse sayfaya dokunmadan da “veri bekleniyor” durumunu bildirmelidir.
Bunun aracı, zaman çizelgesini bildirimin kendisine yazan mekanizmadır. Bu ders çizelgenin nasıl tanımlandığını, verilen bir anda hangi değerin geçerli olduğunu ve çizelgenin dışındaki zamanda ne olduğunu ele alır.
Anahtar Kare Kümesi
@keyframes kuralı bir ad ve bir anahtar kare (keyframe) listesi tanımlar. Her kare bir
konum ve o konumda geçerli bildirimleri taşır. Konum yüzdedir; from ve to anahtar
sözcükleri %0 ile %100 karşılığıdır.
@keyframes nabiz { 0% { opacity: 1; transform: scale(1); animation-timing-function: ease-in-out; } 60% { opacity: 0.35; transform: scale(1.06); } 100% { opacity: 1; transform: scale(1); } }
Dört kural bu tanımı okumayı belirler:
%0ya da%100yazılmamışsa o uç, ögenin kendi bildirimlerinden gelen değerle doldurulur. Bu değere taban değer denir.- Aynı konumda iki kare yazılırsa sonraki kazanır; aynı adla iki
@keyframeskuralı yazılırsa sonraki öncekini bütünüyle değiştirir — kareler birleştirilmez. - Bir kareye yazılan
animation-timing-function, o kareden sonraki aralık için geçerlidir. Son kareye yazılırsa etkisizdir. - Karelerde geçemeyen değer yazılırsa o bildirim yok sayılır;
!importanttaşıyan bir bildirim de yok sayılır.
Üçüncü madde bir yanlış anlamanın kaynağıdır: ögeye yazılan animation-timing-function
animasyonun tamamına değil, her kare aralığına ayrı ayrı uygulanır. Üç kareli bir
animasyonda ease-in-out yazmak iki ayrı yavaşla–hızlan–yavaşla eğrisi üretir.
Animasyon Bildirimleri
| Bildirim | Yanıtladığı soru |
|---|---|
animation-name |
Hangi anahtar kare kümesi |
animation-duration |
Bir tur ne kadar sürecek |
animation-timing-function |
Kare aralıklarında hız nasıl dağılacak |
animation-delay |
Başlangıç ne kadar ertelenecek |
animation-iteration-count |
Kaç tur dönecek |
animation-direction |
Hangi turlar ters yönde oynatılacak |
animation-fill-mode |
Çizelge dışındaki zamanda hangi değer geçerli |
animation-play-state |
Çalışıyor mu, duraklatılmış mı |
Kısayol animation bunların tümünü alır. Virgülle ayrılmış liste, aynı ögeye birden çok
animasyon bağlamayı sağlar; aynı özelliğe yazan iki animasyondan listede sonra gelen
kazanır.
Zaman Çizelgesinin Çözülmesi
Verilen bir anda geçerli değeri bulmak dört adımdır: geçen süreden tur numarası çıkarılır, tur içindeki ham ilerleme bulunur, yön değerine göre gerekiyorsa ters çevrilir, sonra ilerlemeye karşılık gelen kare çifti aranır.
// anahtarkare.mjs — anahtar kare cozumlemesi, yineleme, yon ve doldurma kipi const bez = (a, b, t) => { const u = 1 - t; return 3*u*u*t*a + 3*u*t*t*b + t*t*t; }; const egri = (x1, y1, x2, y2) => (x) => { if (x <= 0) return 0; if (x >= 1) return 1; let alt = 0, ust = 1, t = x; for (let i = 0; i < 60; i++) { t = (alt + ust) / 2; if (bez(x1, x2, t) < x) alt = t; else ust = t; } return bez(y1, y2, t); }; const DOGRUSAL = egri(0, 0, 1, 1); const EASE_IN_OUT = egri(0.42, 0, 0.58, 1); // @keyframes nabiz { 0% {...} 60% {...} 100% {...} } const KARELER = [ { konum: 0, saydamlik: 1.00, olcek: 1.00, egri: EASE_IN_OUT }, { konum: 60, saydamlik: 0.35, olcek: 1.06, egri: DOGRUSAL }, { konum: 100, saydamlik: 1.00, olcek: 1.00, egri: DOGRUSAL }, ]; // yerel ilerlemeden (0..1) deger uretimi: kare cifti bulunur, o cifte ait egri uygulanir function deger(yerel) { const y = yerel * 100; let i = 0; while (i < KARELER.length - 2 && y >= KARELER[i + 1].konum) i++; const a = KARELER[i], b = KARELER[i + 1]; const oran = (y - a.konum) / (b.konum - a.konum); const e = a.egri(Math.min(Math.max(oran, 0), 1)); return { cift: `%${a.konum}-%${b.konum}`, saydamlik: a.saydamlik + (b.saydamlik - a.saydamlik) * e, olcek: a.olcek + (b.olcek - a.olcek) * e, }; } const SURE = 1400, GECIKME = 200, YINELEME = 3; // yon degerine gore turun ilerlemesi ters cevrilir mi? const ters = (yon, tur) => yon === "reverse" ? true : yon === "alternate" ? tur % 2 === 1 : yon === "alternate-reverse" ? tur % 2 === 0 : false; console.log(`sure=${SURE}ms gecikme=${GECIKME}ms yineleme=${YINELEME} yon=alternate`); console.log("\n ms | tur | ham ilerleme | yerel ilerleme | kare cifti | saydamlik | olcek"); for (const ms of [0, 200, 620, 1040, 1600, 2200, 2600, 3000, 3400, 4600]) { const gecen = ms - GECIKME; if (gecen < 0) { console.log(`${String(ms).padStart(4)} | gecikme suresi, animasyon henuz baslamadi`); continue; } if (gecen >= SURE * YINELEME) { console.log(`${String(ms).padStart(4)} | animasyon bitti`); continue; } const tur = Math.floor(gecen / SURE); const ham = (gecen % SURE) / SURE; const yerel = ters("alternate", tur) ? 1 - ham : ham; const d = deger(yerel); console.log( `${String(ms).padStart(4)} | ${String(tur).padStart(4)} | ${ham.toFixed(4).padStart(12)} | ${yerel.toFixed(4).padStart(14)} | ${d.cift.padStart(10)} | ${d.saydamlik.toFixed(4).padStart(9)} | ${d.olcek.toFixed(4)}`, ); } console.log("\n--- yon degerlerinin turlere etkisi (t = ters cevrilir) ---"); console.log("yon".padEnd(20) + [0, 1, 2, 3].map((n) => `tur ${n}`.padStart(8)).join("")); for (const yon of ["normal", "reverse", "alternate", "alternate-reverse"]) { console.log(yon.padEnd(20) + [0, 1, 2, 3].map((n) => (ters(yon, n) ? "t" : "-").padStart(8)).join("")); } // Doldurma kipi tek turluk bir giris animasyonunda gorunur olur: // @keyframes belirme { from { opacity: 0; translateY 8px } to { opacity: 1; translateY 0 } } console.log("\n--- doldurma kipi: 'belirme' animasyonunun disindaki degerler ---"); const BELIRME = { bas: { saydamlik: 0, oteleme: 8 }, son: { saydamlik: 1, oteleme: 0 } }; const g = (d) => `saydamlik=${d.saydamlik.toFixed(2)} translateY=${d.oteleme}px`; // Taban deger = ogenin kendi bildirimlerinden gelen deger; iki ayri taban denenir. for (const TEMEL of [{ saydamlik: 1, oteleme: 0 }, { saydamlik: 0, oteleme: 8 }]) { console.log(`\ntaban deger: ${g(TEMEL)}`); for (const kip of ["none", "forwards", "backwards", "both"]) { const once = (kip === "backwards" || kip === "both") ? BELIRME.bas : TEMEL; const sonra = (kip === "forwards" || kip === "both") ? BELIRME.son : TEMEL; console.log(` ${kip.padEnd(10)} gecikme sirasinda: ${g(once).padEnd(34)} bittikten sonra: ${g(sonra)}`); } }
sure=1400ms gecikme=200ms yineleme=3 yon=alternate ms | tur | ham ilerleme | yerel ilerleme | kare cifti | saydamlik | olcek 0 | gecikme suresi, animasyon henuz baslamadi 200 | 0 | 0.0000 | 0.0000 | %0-%60 | 1.0000 | 1.0000 620 | 0 | 0.3000 | 0.3000 | %0-%60 | 0.6750 | 1.0300 1040 | 0 | 0.6000 | 0.6000 | %60-%100 | 0.3500 | 1.0600 1600 | 1 | 0.0000 | 1.0000 | %60-%100 | 1.0000 | 1.0000 2200 | 1 | 0.4286 | 0.5714 | %0-%60 | 0.3528 | 1.0597 2600 | 1 | 0.7143 | 0.2857 | %0-%60 | 0.7016 | 1.0275 3000 | 2 | 0.0000 | 0.0000 | %0-%60 | 1.0000 | 1.0000 3400 | 2 | 0.2857 | 0.2857 | %0-%60 | 0.7016 | 1.0275 4600 | animasyon bitti --- yon degerlerinin turlere etkisi (t = ters cevrilir) --- yon tur 0 tur 1 tur 2 tur 3 normal - - - - reverse t t t t alternate - t - t alternate-reverse t - t - --- doldurma kipi: 'belirme' animasyonunun disindaki degerler --- taban deger: saydamlik=1.00 translateY=0px none gecikme sirasinda: saydamlik=1.00 translateY=0px bittikten sonra: saydamlik=1.00 translateY=0px forwards gecikme sirasinda: saydamlik=1.00 translateY=0px bittikten sonra: saydamlik=1.00 translateY=0px backwards gecikme sirasinda: saydamlik=0.00 translateY=8px bittikten sonra: saydamlik=1.00 translateY=0px both gecikme sirasinda: saydamlik=0.00 translateY=8px bittikten sonra: saydamlik=1.00 translateY=0px taban deger: saydamlik=0.00 translateY=8px none gecikme sirasinda: saydamlik=0.00 translateY=8px bittikten sonra: saydamlik=0.00 translateY=8px forwards gecikme sirasinda: saydamlik=0.00 translateY=8px bittikten sonra: saydamlik=1.00 translateY=0px backwards gecikme sirasinda: saydamlik=0.00 translateY=8px bittikten sonra: saydamlik=0.00 translateY=8px both gecikme sirasinda: saydamlik=0.00 translateY=8px bittikten sonra: saydamlik=1.00 translateY=0px
İlk tablonun her satırı bir anı çözüyor. 1040. milisaniyede geçen süre 840 milisaniye, tur
sıfır, ham ilerleme ; bu değer üçüncü kareye tam denk geldiği için çift %60-%100
olarak okunuyor ve değerler o karenin bildirimleridir.
- milisaniyede iki adım birden görünür. Tur bir, ham ilerleme ; yön
alternateolduğu için tek numaralı turda çizelge ters çevrilir ve yerel ilerleme olur. Kare çifti bu yerel değere göre aranır.
Kare aralıklarının uzunluğu eşit değildir: ilk aralık turun yüzde 60’ı, ikincisi yüzde
40’ıdır. %0 karesine yazılan ease-in-out yalnız birinci aralıkta çalışır ve etkisi
2200. milisaniyede görünür. Yerel ilerleme , bunun birinci aralık içindeki oranı
; eğri bu oranı değerine çeker ve saydamlık olur — doğrusal
ara değer verirdi. Aralığın tam ortasında ise eğri simetrik olduğu için fark
sıfırdır: 620. milisaniyedeki değeri iki yolla da aynı çıkar.
Yön ve Yineleme
animation-iteration-count bir sayı ya da infinite alır. Kesirli değer de geçerlidir:
yazmak animasyonu üçüncü turun ortasında keser.
İkinci tablo dört yön değerinin hangi turları ters çevirdiğini gösteriyor. normal hiçbir
turu, reverse hepsini ters çevirir. alternate çift numaralı turları düz, tek numaralı
turları ters oynatır; alternate-reverse bunun tersidir.
Ayrım sayı değil davranıştır: alternate ile animasyon başladığı yere geri döner ve
sıçrama olmaz. normal ile üç tur dönen bir animasyon, her turun sonunda son kareden ilk
kareye atlar. Kareler uçlarda aynı değeri taşımıyorsa bu atlama görünür.
Doldurma Kipi
Bir animasyon iki zaman aralığının dışında kalır: gecikme süresi ve bitişten sonrası. Bu
aralıklarda hangi değerin geçerli olduğunu animation-fill-mode bildirir.
Üçüncü blok, iki farklı taban değer için dört kipi hesaplıyor ve seçimi bir kurala bağlıyor.
Taban değer opacity: 1 iken — yani öge biçem dosyasında zaten görünürken — forwards ile
none aynı sonucu veriyor, çünkü animasyonun son karesi de tabanla aynı. Ayrımı yapan
backwards: gecikme süresince ilk kareyi uygular ve ögenin bir an görünüp sonra kaybolmasını
önler.
Taban değer opacity: 0 iken durum tersine döner. Öge biçem dosyasında gizlidir; none ile
animasyon bittiğinde gizli hâline geri döner ve girmiş olduğu boşa gider. Burada gereken
forwards değeridir.
Kural şudur: doldurma kipi, animasyonun uçlarındaki değerlerle ögenin taban değeri uyuşmadığında gerekir. İkisi uyuşuyorsa hangi kip yazıldığı görünmez.
İstasyon Sayfasında Nabız
/* hareket.css — 3. adım: veri bekleyen ölçüm göstergesi */ @keyframes nabiz { 0% { opacity: 1; transform: scale(1); animation-timing-function: ease-in-out; } 60% { opacity: 0.35; transform: scale(1.06); } 100% { opacity: 1; transform: scale(1); } } .istasyon-durumu[data-durum="bekliyor"] .gosterge { animation: nabiz 1400ms 200ms infinite alternate; transform-origin: center; } .istasyon-durumu[data-durum="cevrimdisi"] .gosterge { animation: none; }
Gösterge yalnız bekleme durumunda hareket eder. Durum bilgisi data-durum özniteliğinden
okunur; öznitelik seçicileri CSS ile Görsel Sunum kursunda tanımlanmıştı. Çevrimdışı durumda
animasyon açıkça kapatılır — sessizlik de bir bildirimdir.
Hareket süresiz döndüğü için burada bir sorumluluk doğar: sürekli dönen bir animasyon, kullanıcının durduramayacağı bir dikkat çekicidir. Bu konunun son dersi bu sorumluluğu ele alır.
Özet
@keyframesbir zaman çizelgesi tanımlar; yazılmayan uçlar ögenin taban değeriyle doldurulur ve aynı adla ikinci bir kural öncekini bütünüyle değiştirir.- Bir andaki değer dört adımda çözülür: tur numarası, tur içindeki ham ilerleme, yön değerine göre ters çevirme, kare çiftinin bulunması.
- Zamanlama fonksiyonu animasyonun tamamına değil her kare aralığına ayrı uygulanır; bir kareye yazılan eğri o kareden sonraki aralık için geçerlidir.
alternatetek numaralı turları ters oynatarak tur sonlarındaki sıçramayı kaldırır;normalile son kareden ilk kareye atlanır.- Doldurma kipi yalnız animasyonun uç değerleri ögenin taban değerinden farklıysa görünür bir etki üretir.
Sonraki Adım
Bu üç dersin bildirimleri hareketi tanımladı ama bedelini hiç konuşmadı. Ölçek değiştiren bir animasyonla genişlik değiştiren bir animasyon aynı görünür; tarayıcı için ikisi aynı iş değildir. Sonraki ders, hareketin çizim hattındaki karşılığını ele alır: hangi özellik hangi aşamayı yeniden çalıştırır ve bu neden hareketin akıcılığını belirler?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.