İçeriğe geç
academia.sh

Ders 16 / 23

Animasyon Başarımı

Hareketin çizim hattındaki karşılığı; hangi özellik değişiminin yerleşim, boyama ve bileşim aşamalarını yeniden çalıştırdığı, bileşik katmanın ne olduğu, zorlanmış yerleşim hesabı ve kare bütçesi.

İçindekiler

Önceki üç ders hareketi tanımladı: dönüşüm geometriyi verdi, geçiş ve anahtar kareler zamanı verdi. Bedelinden hiç söz edilmedi.

Bir kartı sağa kaydırmanın iki yolu var: left değerini artırmak ya da transform listesine öteleme yazmak. Ekranda ikisi aynı görünür. Tarayıcı için ikisi aynı iş değildir. Bu ders farkın nereden geldiğini kural olarak kurar.

Çizim Hattı

Bir sayfanın ekrana gelmesi sıralı aşamalardan geçer. Hareket açısından dördü önemlidir:

  1. Stil hesabı. Hangi bildirimin hangi ögeye uygulandığı çözülür; basamaklama ve kalıtım burada işler. Her değişim bu aşamayı çalıştırır.
  2. Yerleşim. Her kutunun ölçüsü ve yeri hesaplanır. Bir kutunun genişliği değişirse kardeşleri, ana ögesi ve sonraki akış etkilenebilir; hesap yerel kalmayabilir.
  3. Boyama. Kutuların içeriği — renk, kenarlık, gölge, metin — piksellere çevrilir.
  4. Bileşim. Boyanmış yüzeyler sıralanır, dönüştürülür ve ekrana yerleştirilir.

Aşamalar sıralıdır: yerleşim yeniden çalışırsa boyama ve bileşim de çalışır. Ama tersi geçerli değildir; bileşim tek başına çalışabilir.

Hangi Özellik Hangi Aşamayı Tetikler

// basarim.mjs — ozellik degisiminin cizim hattinda tetikledigi asamalar ve zorlanmis duzen sayimi
const HAT = ["stil", "duzen", "boyama", "bilesim"];

// Kural: bir ozellik hangi asamadan itibaren yeniden calisir?
const BASLANGIC = {
  width: "duzen", height: "duzen", "margin-block-start": "duzen", padding: "duzen",
  "border-width": "duzen", "font-size": "duzen", "line-height": "duzen",
  top: "duzen", left: "duzen", display: "duzen", "flex-basis": "duzen",
  color: "boyama", "background-color": "boyama", "background-image": "boyama",
  "border-radius": "boyama", "box-shadow": "boyama", outline: "boyama",
  visibility: "boyama", "text-decoration": "boyama",
  transform: "bilesim", opacity: "bilesim",
};

console.log("--- ozellik degisimi hangi asamadan itibaren yeniden calisir ---");
console.log("ozellik".padEnd(20) + HAT.map((a) => a.padStart(10)).join("") + "   asama sayisi");
for (const [ozellik, bas] of Object.entries(BASLANGIC)) {
  const i = HAT.indexOf(bas);
  const isaret = HAT.map((_, k) => (k === 0 || k >= i ? "x" : "-").padStart(10)).join("");
  console.log(ozellik.padEnd(20) + isaret + String(HAT.length - i + (i > 0 ? 1 : 0)).padStart(15));
}

console.log("\n--- ayni gorsel sonucu veren iki yazim ---");
for (const [a, b] of [["left", "transform"], ["width", "transform"], ["visibility", "opacity"]]) {
  const ia = HAT.indexOf(BASLANGIC[a]), ib = HAT.indexOf(BASLANGIC[b]);
  console.log(
    `${a.padEnd(12)} -> ${HAT.slice(ia).join(" > ").padEnd(26)}` +
    `${b.padEnd(12)} -> ${HAT.slice(ib).join(" > ")}`,
  );
}

// --- zorlanmis duzen (forced synchronous layout) sayimi ---
// Model: yazma islemi duzeni gecersiz kilar; olcu okuma islemi gecersiz duzeni
// okumak icin duzeni yeniden hesaplatir. Kare sonunda bir hesap zaten yapilir.
function duzenSayimi(islemler) {
  let kirli = false, zorlanmis = 0;
  const iz = [];
  for (const [tip, ad] of islemler) {
    if (tip === "yaz") { kirli = true; iz.push(`yaz ${ad}`); }
    else {
      if (kirli) { zorlanmis++; kirli = false; iz.push(`oku ${ad} -> DUZEN HESABI`); }
      else iz.push(`oku ${ad}`);
    }
  }
  return { zorlanmis, kareSonu: kirli ? 1 : 0, iz };
}

const KARISIK = [
  ["oku", "genislik(kart 1)"], ["yaz", "width(kart 1)"],
  ["oku", "genislik(kart 2)"], ["yaz", "width(kart 2)"],
  ["oku", "genislik(kart 3)"], ["yaz", "width(kart 3)"],
];
const AYRIK = [
  ["oku", "genislik(kart 1)"], ["oku", "genislik(kart 2)"], ["oku", "genislik(kart 3)"],
  ["yaz", "width(kart 1)"], ["yaz", "width(kart 2)"], ["yaz", "width(kart 3)"],
];

for (const [ad, islemler] of [["okuma ve yazma ic ice", KARISIK], ["once okuma, sonra yazma", AYRIK]]) {
  const s = duzenSayimi(islemler);
  console.log(`\n--- ${ad} ---`);
  for (const satir of s.iz) console.log("  " + satir);
  console.log(`  zorlanmis duzen hesabi: ${s.zorlanmis}, kare sonundaki hesap: ${s.kareSonu}`);
}

console.log("\n--- kare butcesi (yenileme hizindan aritmetik) ---");
for (const hz of [60, 90, 120, 144]) {
  console.log(`${String(hz).padStart(3)} kare/s -> kare basina ${(1000 / hz).toFixed(2)} ms`);
}
--- ozellik degisimi hangi asamadan itibaren yeniden calisir ---
ozellik                   stil     duzen    boyama   bilesim   asama sayisi
width                        x         x         x         x              4
height                       x         x         x         x              4
margin-block-start           x         x         x         x              4
padding                      x         x         x         x              4
border-width                 x         x         x         x              4
font-size                    x         x         x         x              4
line-height                  x         x         x         x              4
top                          x         x         x         x              4
left                         x         x         x         x              4
display                      x         x         x         x              4
flex-basis                   x         x         x         x              4
color                        x         -         x         x              3
background-color             x         -         x         x              3
background-image             x         -         x         x              3
border-radius                x         -         x         x              3
box-shadow                   x         -         x         x              3
outline                      x         -         x         x              3
visibility                   x         -         x         x              3
text-decoration              x         -         x         x              3
transform                    x         -         -         x              2
opacity                      x         -         -         x              2

--- ayni gorsel sonucu veren iki yazim ---
left         -> duzen > boyama > bilesim  transform    -> bilesim
width        -> duzen > boyama > bilesim  transform    -> bilesim
visibility   -> boyama > bilesim          opacity      -> bilesim

--- okuma ve yazma ic ice ---
  oku genislik(kart 1)
  yaz width(kart 1)
  oku genislik(kart 2) -> DUZEN HESABI
  yaz width(kart 2)
  oku genislik(kart 3) -> DUZEN HESABI
  yaz width(kart 3)
  zorlanmis duzen hesabi: 2, kare sonundaki hesap: 1

--- once okuma, sonra yazma ---
  oku genislik(kart 1)
  oku genislik(kart 2)
  oku genislik(kart 3)
  yaz width(kart 1)
  yaz width(kart 2)
  yaz width(kart 3)
  zorlanmis duzen hesabi: 0, kare sonundaki hesap: 1

--- kare butcesi (yenileme hizindan aritmetik) ---
 60 kare/s -> kare basina 16.67 ms
 90 kare/s -> kare basina 11.11 ms
120 kare/s -> kare basina 8.33 ms
144 kare/s -> kare basina 6.94 ms

Tablo bir ölçüm değil, bir kural kümesidir: özellikler tanımlarına göre üç gruba ayrılır. Geometriye dokunanlar yerleşimden itibaren, yalnız görünüşe dokunanlar boyamadan itibaren, kutunun ne ölçüsünü ne içeriğini değiştirenler ise yalnız bileşimden itibaren çalışır.

Sınıflandırmanın kenarında birkaç özellik vardır — süzgeç ve karışım kipleri gibi — ve bunların hangi aşamadan başladığı çizim gerçekleştirimine göre değişir. Kesin bilgi tarayıcının geliştirici araçlarındaki çizim kaydından okunur; kural olarak ezberlenmemelidir.

Aynı Görünüm, Farklı Maliyet

Çıktının ikinci bloğu üç çifti karşılaştırıyor. left ile ötelemek üç aşama çalıştırır, transform ile ötelemek bir aşama. visibility ile gizlemek boyamayı yeniden çalıştırır, opacity ile soldurmak çalıştırmaz.

Buradan hareket yazımının en pratik kuralı çıkar: animasyonlar dönüşüm ve saydamlık üzerinden yazılır. Bir kutunun büyümesi gerekiyorsa width yerine scale, yer değiştirmesi gerekiyorsa left yerine translate kullanılır.

Kuralın bir bedeli vardır. scale kutunun içeriğini de ölçekler; metin, kutu büyürken büyür. width değiştirmek ise metni yeniden satırlara böler. İkisi aynı görsel sonucu vermez ve seçim, istenen sonucun hangisi olduğuna bağlıdır.

Bileşik Katman

Dönüşümün ucuz olmasının nedeni, uygulandığı yerdir. Boyanmış bir yüzey bir kez üretilir; dönüşüm ve saydamlık o yüzeye bileşim aşamasında uygulanır. Yüzey yeniden boyanmaz, yalnızca farklı yerleştirilir.

Bunun için ögenin kendi yüzeyine, yani bileşik katmana (compositing layer) ayrılmış olması gerekir. Katman ayrımını tarayıcı kendi karar vererek yapar; will-change bildirimi bu kararı önceden bildirmenin yoludur:

.olcum-kartlari > .kart:hover { will-change: transform; }

will-change bir garanti değil, bir ipucudur. Üç kuralı vardır. Katman ayırmak bellek harcar; her ögeye yazmak yararı tersine çevirir. Bildirim, hareket başlamadan kısa süre önce açılıp bittikten sonra kaldırılmalıdır — kalıcı yazılan bir ipucu kalıcı bir maliyettir. Ve will-change de yığılma bağlamı kurar; ilk dersteki uyarı burada da geçerlidir.

Zorlanmış Yerleşim Hesabı

Çıktının üçüncü ve dördüncü blokları, betikle sürülen bir hareketin sık düştüğü tuzağı bir model üzerinde sayıyor. Model şudur: bir yazma işlemi yerleşimi geçersiz kılar; sonraki bir ölçü okuma işlemi güncel değeri vermek zorunda olduğu için yerleşimi hemen yeniden hesaplatır. Buna zorlanmış yerleşim hesabı (forced synchronous layout) denir.

Okuma ve yazma iç içe yapıldığında model iki ek hesap sayıyor. Aynı işlemler önce bütün okumalar, sonra bütün yazmalar biçiminde sıralandığında ek hesap kalmıyor; kare sonunda zaten yapılacak olan tek hesap yeterli oluyor.

Kural yazımdan bağımsızdır: ölçü okuyan işlemler bir öbekte, ölçü değiştiren işlemler ayrı bir öbekte toplanır. Öbek sayısı arttıkça hesap sayısı da artar.

Kare Bütçesi

Son blok aritmetik bir dönüşümdür: saniyede 60 kare üreten bir ekranda kare başına düşen süre 1000/6016,671000 / 60 \approx 16{,}67 milisaniyedir. Bu süre, bir karenin bütün aşamalarının — betik, stil, yerleşim, boyama, bileşim — paylaştığı bütçedir.

Bütçe aşıldığında kare atlanır ve hareket takılır. Bu yüzden hareket yazarken sorulacak soru “ne kadar sürüyor” değil, “kare başına hangi aşamalar çalışıyor”dur. Aşama sayısını düşürmek, süreyi tahmin etmeye çalışmaktan daha güvenilir bir yoldur.

Yenileme hızı ekrana göre değişir; aynı animasyon farklı bütçelerle çalışır. Kare sayısına bağlı yazılmış bir hareket hızlı ekranda hızlanır. Süre üzerinden yazılan bildirimler bu sorunu taşımaz.

Boyama Alanını Sınırlamak

Bir ögenin yeniden boyanması komşularını da kapsayabilir; boyama alanı ögeden büyük olabilir. contain bildirimi bu yayılmayı sınırlar: contain: layout bir ögenin yerleşim hesabının dışarı taşmayacağını, contain: paint boyamanın kutunun dışına çıkmayacağını bildirir.

Bu bildirim bir söz verir; sözü tutmayan bir içerik bozulur. paint değeri yazılan bir kutudan taşan gölge ya da açılır menü kırpılır. Bildirim, kutunun gerçekten kendi kendine yeter olduğu yerlerde yazılır.

İstasyon Sayfasında Ölçüm

/* hareket.css — 4. adım: hareketin çizim maliyetini sınırlamak */
.olcum-kartlari > .kart {
  transform-origin: center bottom;
  transition: transform 240ms ease-out, box-shadow 240ms ease-out;
  contain: layout paint;
}

.olcum-kartlari > .kart:hover,
.olcum-kartlari > .kart:focus-within {
  transform: translateY(-8px) scale(1.04);
  transition-duration: 160ms;
}

.istasyon-durumu[data-durum="bekliyor"] .gosterge {
  animation: nabiz 1400ms 200ms infinite alternate;
  will-change: transform, opacity;
}

Kart hareketi yalnız dönüşüm üzerinden yazıldı; contain bildirimi kartın yerleşim ve boyama etkisini kendi kutusuyla sınırlıyor. Gösterge sürekli hareket ettiği için will-change bildirimi orada kalıcı yazıldı — kalıcı hareket, kalıcı katmanı haklı çıkaran ender durumlardan biridir.

Gölge geçişi bir ödünleşimdir: box-shadow boyamayı yeniden çalıştırır. Aynı görünüm, kartın arkasına konan ayrı bir sözde elemanın saydamlığıyla da elde edilebilir ve o yazım yalnız bileşim çalıştırır.

Özet

  • Çizim hattı sıralıdır: yerleşim çalışırsa boyama ve bileşim de çalışır, ama bileşim tek başına çalışabilir.
  • Geometriye dokunan özellikler yerleşimden, yalnız görünüşe dokunanlar boyamadan, dönüşüm ve saydamlık ise bileşimden itibaren çalışır.
  • Hareket dönüşüm ve saydamlık üzerinden yazıldığında aşama sayısı düşer; ölçü değiştiren yazımlar aynı görünümü daha çok işle üretir.
  • Bileşik katman dönüşümün ucuz olmasının nedenidir; will-change bu ayrımı bildiren bir ipucudur ve bellek harcadığı için geçici kullanılır.
  • Ölçü okuma ile ölçü yazma işlemleri iç içe yapıldığında zorlanmış yerleşim hesapları oluşur; okumaları ve yazmaları öbeklemek bunu ortadan kaldırır.

Sonraki Adım

Buraya kadar hareketin nasıl yazıldığı ve neye mal olduğu kuruldu. Bir soru dışarıda kaldı: hareket her kullanıcı için aynı şey midir? Bazı kullanıcılar için ekrandaki hareket rahatsızlık, baş dönmesi ya da bulantı üretir ve işletim sistemi bu tercihi bildirir. Sonraki ders, hareket azaltma tercihinin ne anlama geldiğini ve hangi hareketin kaldırılıp hangisinin korunacağını ele alır.

İ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