İçeriğe geç
academia.sh

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:

  1. %0 ya da %100 yazılmamışsa o uç, ögenin kendi bildirimlerinden gelen değerle doldurulur. Bu değere taban değer denir.
  2. Aynı konumda iki kare yazılırsa sonraki kazanır; aynı adla iki @keyframes kuralı yazılırsa sonraki öncekini bütünüyle değiştirir — kareler birleştirilmez.
  3. Bir kareye yazılan animation-timing-function, o kareden sonraki aralık için geçerlidir. Son kareye yazılırsa etkisizdir.
  4. Karelerde geçemeyen değer yazılırsa o bildirim yok sayılır; !important taşı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 0,60{,}6; bu değer üçüncü kareye tam denk geldiği için çift %60-%100 olarak okunuyor ve değerler o karenin bildirimleridir.

  1. milisaniyede iki adım birden görünür. Tur bir, ham ilerleme 0,42860{,}4286; yön alternate olduğu için tek numaralı turda çizelge ters çevrilir ve yerel ilerleme 10,4286=0,57141 - 0{,}4286 = 0{,}5714 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 0,57140{,}5714, bunun birinci aralık içindeki oranı 0,95230{,}9523; eğri bu oranı 0,99560{,}9956 değerine çeker ve saydamlık 0,35280{,}3528 olur — doğrusal ara değer 0,38100{,}3810 verirdi. Aralığın tam ortasında ise eğri simetrik olduğu için fark sıfırdır: 620. milisaniyedeki 0,6750{,}675 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: 2,52{,}5 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

  • @keyframes bir 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.
  • alternate tek numaralı turları ters oynatarak tur sonlarındaki sıçramayı kaldırır; normal ile 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat