CSS size hareket için iki araç sunar: geçişler ve @keyframes animasyonları. Çoğu geliştirici ikisinin de var olduğunu bilir ama önce öğrendiğine — genellikle geçişlere — ulaşır ve ardından yükleme döndürücüsünün neden döngüye girmediğini veya bildirimin neden orijinal konumuna geri döndüğünü merak eder. Her birinin ne için olduğunu anladığınızda karar karmaşık değildir. Geçişler bir tepkidir: bir şey durumunu değiştirir, tarayıcı değişimi yumuşatır. @keyframes animasyonları bağımsız betiklerdir: kendi başlarına çalışırlar, isterseniz döngüye girerler ve bir durum tetikleyicisine ihtiyaç duymazlar. Bu rehber her ikisini de, en çok önem taşıyan özellikleri ve pürüzsüz 60fps animasyonları sarsıntılı olanlardan ayıran performans ayrıntılarını kapsar. Çok fazla CSS yazıyorsanız CSS Formatter'ı yer imlerinize eklemeye değer — dağınık shorthand'i hızlıca temizler.

CSS Geçişleri — Basit Durum

Bir geçiş tarayıcıya şunu söyler: "bu özellik değiştiğinde, geçişi anında yapmak yerine zamanla animasyonla". Hepsi bu. En yaygın kullanım hover efektleri içindir — renk değişiklikleri, opaklık soldurmaları, ince ölçek dönüşümleri. MDN geçiş rehberi tam spesifikasyonu kapsar, ancak önem verdiğiniz dört özellik şunlardır: transition-property, transition-duration, transition-timing-function ve transition-delay.

css
/* The shorthand: property duration easing delay */
.btn-primary {
  background-color: #4f46e5;
  color: #fff;
  padding: 0.625rem 1.25rem;
  border-radius: 6px;
  border: none;
  cursor: pointer;
  transition: background-color 200ms ease, transform 150ms ease;
}

.btn-primary:hover {
  background-color: #4338ca;
  transform: translateY(-1px);
}

.btn-primary:active {
  transform: translateY(0);
}

/* Opacity fade — a card coming into view */
.notification-card {
  opacity: 0;
  transition: opacity 300ms ease-in-out;
}

.notification-card.is-visible {
  opacity: 1;
}

Her biri kendi süresi ve easingiyle birden fazla geçişi virgülle ayırarak listeleyebilirsiniz — yukarıdaki düğme örneğinin yaptığı budur. Her özellik bağımsız olarak animasyonlanır.

transition: all kullanmayın. Evrensel bir çözüm olarak cazip görünür, ancak bu öğedeki her özellik değişikliğinin animasyonlandığı anlamına gelir — amaçlamadıklarınız dahil. Her karede tarayıcının daha fazla çalışmasına da neden olur. Geçiş yapmak istediğiniz belirli özellikleri listeleyin. MDN transition-property docs hangi özelliklerin animasyonlanabilir olduğunu açıklar.

transition-timing-function — Easing Açıklandı

Zamanlama işlevi animasyonun ivme eğrisini kontrol eder — hızlı başlayıp yavaşlayıp yavaşlamadığını, sabit bir hızda hareket edip etmediğini veya özel bir eğri üzerinde zıplayıp zıplamadığını. Yerleşik anahtar sözcükler çoğu durumu kapsar.

  • ease — yavaş başlar, hızlanır, sonunda yavaşlar. Varsayılan. Çoğu UI hareketi için iyidir.
  • linear — boyunca sabit hız. Döndürücüler ve ilerleme çubukları için iyi, çoğu şey için kötü — mekanik görünür.
  • ease-in — yavaş başlar, hızlı biter. Hız kazanan bir şey gibi hissettirür. Ekrandan çıkan öğeler için iyi.
  • ease-out — hızlı başlar, yavaş biter. Ekrana giren öğeler için doğal hissettirir (içeri kayan bir bildirim gibi).
  • ease-in-out — her iki uçta yavaş. Görünümde başlayan ve duran öğeler için parlak görünür.
  • cubic-bezier(x1, y1, x2, y2) — kendi eğrinizi tanımlayın. cubic-bezier.com gibi araçlar özel eğrileri görsel olarak oluşturmanıza ve önizlemenize olanak tanır.
css
/* Custom spring-like easing with cubic-bezier */
.drawer {
  transform: translateX(-100%);
  transition: transform 350ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.drawer.is-open {
  transform: translateX(0);
}

/* The cubic-bezier values above overshoot slightly (y > 1)
   which gives a satisfying spring effect on open */

@keyframes — Geçişler Yeterli Olmadığında

Geçişlerin sert bir sınırı vardır: iki duruma ve bir tetikleyiciye ihtiyaç duyarlar. Bir şeyin süresiz olarak döngüye girmesini, herhangi bir etkileşim olmadan sayfa yüklenmesinde çalışmasını veya ikiden fazla adım boyunca animasyonlanmasını istiyorsanız @keyframes'e ihtiyacınız var. MDN @keyframes referansı tam sözdizimini kapsar. Keyframe'leri ayrı olarak tanımlayıp ardından bunları animation özelliğiyle bir öğeye eklersiniz.

css
/* Define the animation */
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* Attach it to an element */
.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid #e5e7eb;
  border-top-color: #4f46e5;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}

/* Multi-step — more than two stops */
@keyframes pulse-ring {
  0%   { transform: scale(1);    opacity: 1; }
  50%  { transform: scale(1.15); opacity: 0.7; }
  100% { transform: scale(1);    opacity: 1; }
}

.badge-live {
  animation: pulse-ring 1.5s ease-in-out infinite;
}

animation Kısaltması — Sekiz Özelliğin Tamamı

animation kısaltması sekiz özelliği tek bir bildirime sıkıştırır. Her seferinde hepsine ihtiyacınız olmaz, ancak her birinin ne kontrol ettiğini bilmek, bir şey beklendiği gibi davranmadığında belgeleri taramaktan kurtarır.

css
/*
  animation: name | duration | timing-function | delay | iteration-count | direction | fill-mode | play-state
*/
.toast {
  animation: slide-in-right 350ms ease-out 0s 1 normal forwards running;
}

/* More commonly written as just the values you need: */
.toast {
  animation: slide-in-right 350ms ease-out forwards;
}

/* Breaking it out to individual properties for clarity: */
.confetti-piece {
  animation-name: float-down;
  animation-duration: 2s;
  animation-timing-function: ease-in;
  animation-delay: calc(var(--i) * 150ms); /* staggered via CSS custom property */
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-play-state: running;
}

/* Pausing an animation via JavaScript: toggle a class */
.spinner.is-paused {
  animation-play-state: paused;
}

animation-fill-mode — Geri Dönme Sorununu Çözme

Bu, herkesi bir noktada şaşırtan özelliktir. Varsayılan olarak, bir animasyon sona erdiğinde öğe animasyon öncesi stillerine geri döner — sanki animasyon hiç gerçekleşmemiş gibi. Sağdan kayan bir toast bildirimi animasyonlarsanız, animasyon bittiği anda ekran dışına geri fırlar. animation-fill-mode: forwards, animasyon sona erdiğinde öğeyi son keyframe durumunda tutarak bunu düzeltir.

css
@keyframes slide-in-right {
  from {
    transform: translateX(110%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Without forwards: toast slides in, then SNAPS back off-screen */
.toast-bad {
  animation: slide-in-right 350ms ease-out;
}

/* With forwards: toast slides in and STAYS in position */
.toast {
  animation: slide-in-right 350ms ease-out forwards;
}

/*
  fill-mode values:
  - none      (default) — no styles applied before or after
  - forwards  — hold the final keyframe after the animation ends
  - backwards — apply the first keyframe during the delay period
  - both      — forwards + backwards combined
*/

Gerçek Örnekler — Skeleton Yükleyici, Döndürücü, Toast, Nabız Atan Rozet

İşte sürekli kullanacağınız dört kalıp. Her biri @keyframes ve animasyon özelliklerinin belirli bir kombinasyonunu gösterir.

css
/* --- 1. Skeleton loading shimmer --- */
@keyframes shimmer {
  0%   { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite linear;
  border-radius: 4px;
}

.skeleton-title  { height: 20px; width: 60%; margin-bottom: 12px; }
.skeleton-text   { height: 14px; width: 100%; margin-bottom: 8px; }
.skeleton-text:last-child { width: 80%; }
css
/* --- 2. Spinning loader --- */
@keyframes spin {
  to { transform: rotate(360deg); }
}

.loader-spinner {
  display: inline-block;
  width: 32px;
  height: 32px;
  border: 3px solid rgba(79, 70, 229, 0.2);
  border-top-color: #4f46e5;
  border-radius: 50%;
  animation: spin 600ms linear infinite;
}
css
/* --- 3. Slide-in notification toast --- */
@keyframes toast-enter {
  from {
    transform: translateX(calc(100% + 1.5rem));
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes toast-exit {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(calc(100% + 1.5rem));
    opacity: 0;
  }
}

.toast {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  padding: 0.875rem 1.25rem;
  background: #1f2937;
  color: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
  animation: toast-enter 350ms ease-out forwards;
}

.toast.is-dismissing {
  animation: toast-exit 300ms ease-in forwards;
}
css
/* --- 4. Pulsing notification badge --- */
@keyframes badge-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.5);
  }
  50% {
    box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);
  }
}

.notification-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  border-radius: 999px;
  animation: badge-pulse 2s ease-in-out infinite;
}

Performans — GPU Kompozit vs Layout Tetikleyen Özellikler

Animasyon performansı söz konusu olduğunda tüm CSS özellikleri eşit değildir. Tarayıcı render pipeline'ının burada önem taşıyan üç aşaması vardır: layout (eleman boyutları ve konumlarını hesaplama), paint (pikselleri doldurma) ve composite (GPU'da katmanları birleştirme). Layout'u tetikleyen bir özelliği animasyonlamak, tarayıcının her karede tüm belge geometrisini yeniden hesapladığı anlamına gelir — bu pahalıdır ve sarsıntıya neden olan şeydir. web.dev animasyon performans rehberi bunu derinlemesine kapsar.

  • Animasyonlamak için güvenli (GPU kompozit): transform (translate, scale, rotate) ve opacity. Bunlar tamamen GPU compositor iş parçacığında çalışır — hiçbir zaman layout veya paint tetiklemez.
  • Paint'e neden olur (mümkünse kaçının): color, background-color, border-color, box-shadow. Bunlar layout'u atlar ama her karede repaint tetikler. Kısa geçişler (300ms altında) genellikle sorunsuz.
  • Layout'a neden olur (asla animasyonlamayın): width, height, top, left, margin, padding. Her kare tam bir layout yeniden hesaplaması tetikler — karmaşık sayfalarda garantili sarsıntı.
Pratik kural: yalnızca transform ve opacity ile animasyonlayın. Bir şeyi taşımak istiyorsanız left/top değil translate kullanın. Bir şeyi yeniden boyutlandırmak istiyorsanız width/height değil scale kullanın. CSS Triggers, her özelliğin tam olarak hangi render aşamalarını etkilediğini listeleyen bir referanstır.
css
/* Bad — animating width triggers layout on every frame */
.progress-bar-bad {
  transition: width 300ms ease;
}

/* Good — use scaleX transform instead */
.progress-bar {
  transform-origin: left center;
  transition: transform 300ms ease;
}

/* In JS, set: progressBar.style.transform = 'scaleX(0.75)' for 75% */

/* Bad — animating top/left (positions element with layout) */
.tooltip-bad {
  position: absolute;
  top: 0;
  transition: top 200ms ease;
}

/* Good — use translateY instead */
.tooltip {
  position: absolute;
  top: 0;
  transform: translateY(0);
  transition: transform 200ms ease;
}

will-change — Tutumlu Kullanın

will-change, belirli bir özelliğin animasyonlanmak üzere olduğuna dair tarayıcıya bir ipucudur; bu sayede öğeyi önceden kendi compositor katmanına yükseltmesi gerekir. Bu, düşük donanımda animasyonun en başında zaman zaman gördüğünüz kısa sarsıntı flaşını ortadan kaldırabilir. Ancak gerçek bir maliyeti vardır: her yükseltilmiş katman GPU belleği tüketir. Uygulamanızdaki her animasyonlu öğeye will-change: transform koyarsanız, çoğu cihazda performansı düşürürsünüz — istediğinizin tam tersi.

css
/* Right way — add it just before animation starts, remove after */
.modal-overlay {
  /* Don't set will-change here by default */
}

/* Add via JavaScript only when user triggers modal open */
/* overlay.style.willChange = 'opacity'; */
/* overlay.addEventListener('transitionend', () => { */
/*   overlay.style.willChange = 'auto'; */
/* }); */

/* Acceptable in CSS for elements that animate frequently
   (e.g., a persistent floating action button on scroll) */
.fab {
  will-change: transform; /* OK — this element genuinely animates on scroll */
  transition: transform 200ms ease;
}

/* Don't do this — wastes GPU memory with zero benefit */
.card {
  will-change: transform; /* BAD — card only animates on hover, not constantly */
}

prefers-reduced-motion — Atlayamayacağınız Erişilebilirlik

Kullanıcıların önemli bir kısmı vestibüler bozuklukları veya hareketin rahatsızlık ya da bulantıya yol açtığı başka durumları olan kişilerdir. prefers-reduced-motion medya sorgusu, sistem düzeyindeki "hareketi azalt" ayarına saygı göstermenizi sağlar. WCAG 2.1 kılavuzu 2.3.3 bu gereksinimi kapsar ve MDN prefers-reduced-motion referansı tarayıcı desteğini gösterir (artık evrensel). Kalıp basit: animasyonlarınızı standart medya sorgusuna sarın ve azaltılmış hareket sorgusunun içinde hareket içermeyen bir fallback sağlayın.

css
/* Define animations normally for users who are fine with motion */
@keyframes slide-in-right {
  from { transform: translateX(110%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

.toast {
  animation: slide-in-right 350ms ease-out forwards;
}

.spinner {
  animation: spin 600ms linear infinite;
}

/* Override for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  .toast {
    /* No sliding — just appear */
    animation: none;
    opacity: 1;
    transform: none;
  }

  .spinner {
    /* Slow it way down or stop it entirely */
    animation-duration: 4s;
  }

  /* Nuclear option — disable ALL animations site-wide */
  /* Use this only if you haven't audited each animation individually */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Bu kod parçasının sonundaki "nükleer seçenek", her animasyonu denetlememiş mevcut kod tabanları için yaygın bir kalıptır. Hiçbir şey yapmamaktan iyidir, ancak her animasyonu ayrı ayrı denetlemek size daha fazla kontrol sağlar — bazı animasyonlar durum aktarır (ilerleme çubuğu, yükleme döndürücüsü) ve korunmalı, sadece yavaşlatılmalıdır.

Geçişler vs @keyframes — Karar Rehberi

Hangisini seçeceğinizden emin değilseniz, oluşturduğunuz animasyon hakkında kendinize iki soru sorun:

  • Bir durum değişikliğiyle mi tetikleniyor? (hover, focus, sınıf değiştirme, onay kutusu) → Geçiş kullanın. Geçişler tam olarak bunun için tasarlanmıştır.
  • Sonsuz döngüye giriyor mu?animation-iteration-count: infinite ile @keyframes kullanın.
  • İkiden fazla adımı var mı? (yalnızca başlangıç → son değil, başlangıç → orta → son veya daha fazla) → @keyframes kullanın.
  • Herhangi bir kullanıcı etkileşimi olmadan sayfa yüklendiğinde çalışması gerekiyor mu?@keyframes kullanın.
  • Öğenin bittikten sonra son durumunda kalması gerekiyor mu?animation-fill-mode: forwards ile @keyframes kullanın.
Herkesi şaşırtan kombinasyon: bir arka plan rengini akıcı şekilde solduran bir hover efekti (geçiş) istiyorsunuz, ancak aynı öğedeki bir "yeni" rozetin de sürekli nabız atmasını istiyorsunuz (@keyframes). İkisini de aynı öğede kullanabilirsiniz — farklı özellikleri kontrol ederler ve birbirleriyle çakışmazlar.

Özet

Geçişler durum odaklı hareketi yönetir. @keyframes diğer her şeyi yönetir. Performans için her zaman transform ve opacity ile animasyonlayın — width, height, top ve left'i bunun dışında tutun. Öğenin son durumunu koruması gerektiğinde animation-fill-mode: forwards kullanın. Herhangi bir şeyi yayınlamadan önce prefers-reduced-motion geçersiz kılmasını ekleyin — gerçek kullanıcılar için çok şey ifade eden on satırlık bir eklemedir. Kompoziting modeli ve layout'u neyin tetiklediği hakkında daha fazla bilgi için web.dev render performans belgeleri mevcut en pratik kaynaktır. CSS'yi yazdıktan sonra shorthand'i okunabilir tutmak için CSS Formatter'dan geçirin veya üretime göndermeden önce boşlukları kaldırmak için CSS Minifier'dan geçirin.