---
title: 'Anahtar Kare Animasyonları'
source: 'https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/anahtar-kare-animasyonlari'
course: 'Düzen Sistemleri ve Duyarlı Tasarım'
language: tr
updated: '2026-08-17T18:09:27+00:00'
license: 'CC BY-SA 4.0'
---

# 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ı.

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.

```css
@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.

```js
// 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{,}6$; bu değer üçüncü kareye tam denk geldiği için çift `%60-%100`
olarak okunuyor ve değerler o karenin bildirimleridir.

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

```css
/* 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?
