Cross-document view transitions ile MPA'larda SPA hissi
Bir e-ticaret sitesinde ürün listesinden ürün detayına geçtiğinde kart resminin büyüyerek yerini almasını, geri tuşuna basınca tam tersini görmek istiyorsun. Eskiden bu SPA'sız yapılamıyordu. Tarayıcı sayfayı yeniden yüklüyordu, DOM yok oluyordu, animasyon şansı sıfırdı. CSS View Transitions API'nin Level 2 spesifikasyonu bu durumu değiştirdi: artık farklı HTML belgeleri arasında (cross-document) geçiş animasyonları tanımlayabilirsin.
Temel mekanizma
Cross-document view transitions, aynı origin içindeki iki sayfa arasında çalışır. Tarayıcı navigasyonu yakaladığında eski sayfanın anlık görüntüsünü (snapshot) alır, yeni sayfayı render eder ve iki snapshot arasında CSS ile kontrol edilen bir animasyon oynatır. Bunu etkinleştirmek için her iki sayfada da bir CSS at-kuralı yeterli:
/* Her iki sayfanın CSS'inde bulunmalı */
@view-transition {
navigation: auto;
}navigation: auto değeri, tarayıcının same-origin navigasyonlarında otomatik olarak view transition başlatmasını sağlar. Bu tek satır bile varsayılan cross-fade animasyonunu devreye sokar.
Tarayıcı arka planda şunu yapar: eski sayfada ::view-transition-old(root) pseudo-elemanını oluşturur (eski sayfanın ekran görüntüsü), yeni sayfada ::view-transition-new(root) pseudo-elemanını oluşturur ve ikisi arasında opacity geçişi uygular. Hiçbir JavaScript yazmadan çalışan bir sayfa geçişi elde edersin.
view-transition-name ile eleman eşleştirme
Varsayılan cross-fade güzel bir başlangıç ama asıl güç, iki sayfa arasında aynı view-transition-name değerini paylaşan elemanları eşleştirmekte yatıyor. Tarayıcı bu elemanların konumunu, boyutunu ve görünümünü karşılaştırıp aralarında morph animasyonu oluşturur.
Ürün listesi sayfasında:
/* liste.html */
.product-card img {
view-transition-name: product-hero;
}Ürün detay sayfasında:
/* detay.html */
.product-detail img {
view-transition-name: product-hero;
}Bu iki eleman aynı view-transition-name değerini taşıdığı için tarayıcı küçük karttan büyük görsele doğru bir boyut ve konum animasyonu üretir. Geri navigasyonda ise tam tersi gerçekleşir.
Bir kural var: aynı sayfada aynı view-transition-name değerine sahip birden fazla eleman bulunamaz. Listede birden fazla kart varken bu sorun yaratır. Çözüm, her karta benzersiz bir isim atamak:
.product-card:nth-child(1) img {
view-transition-name: product-1;
}
.product-card:nth-child(2) img {
view-transition-name: product-2;
}Bu statik yaklaşım ölçeklenmez. Dinamik içerik için HTML'de inline style kullanmak daha pratik:
<img
src="/images/product-42.jpg"
style="view-transition-name: product-42"
alt="Ürün 42"
/>Detay sayfasında da aynı product-42 değerini vermen yeterli. Server-side rendering yapıyorsan şablon motorunda ürün ID'sini doğrudan CSS değerine basabilirsin.
view-transition-class ile toplu stil kontrolü
Her elemana benzersiz bir view-transition-name verdiğinde, animasyonları özelleştirmek için her birine ayrı pseudo-eleman seçici yazmak gerekir. On ürün varsa on tane ::view-transition-group(product-1), ::view-transition-group(product-2) kuralı yazmak istemezsin.
view-transition-class bu sorunu çözer. Elemanlara ortak bir sınıf verirsin, animasyon kurallarını bu sınıf üzerinden yazarsın:
.product-card img {
view-transition-class: card-image;
}Şimdi tüm kart görselleri için tek bir kural yeterli:
::view-transition-group(.card-image) {
animation-duration: 350ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}Buradaki nokta seçici: ::view-transition-group(.card-image) sözdizimi, sınıf seçicisi gibi çalışır. Birden fazla sınıf da atayabilirsin: view-transition-class: card-image hero.
Animasyonları özelleştirme
Varsayılan animasyonlar (cross-fade ve morph) çoğu durumda iş görür. Daha fazla kontrol istediğinde dört pseudo-eleman devreye girer:
::view-transition-group(name): Konum ve boyut geçişinin konteynerı::view-transition-image-pair(name): Eski ve yeni snapshot'ı saran çift::view-transition-old(name): Eski sayfadaki snapshot::view-transition-new(name): Yeni sayfadaki snapshot
Bir slide animasyonu örneği:
@keyframes slide-out {
to {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes slide-in {
from {
transform: translateX(100%);
opacity: 0;
}
}
::view-transition-old(root) {
animation: slide-out 300ms ease-in both;
}
::view-transition-new(root) {
animation: slide-in 300ms ease-out both;
}Bu kural sayfanın tamamını (root) sola kaydırıp yeni sayfayı sağdan getirir. Ama bir sorun var: geri navigasyonda da aynı yöne kayar. Kullanıcı geri tuşuna bastığında sayfanın sağa kayması gerekir.
JS types ile yön kontrolü
View Transitions API, pageswap ve pagereveal event'leri aracılığıyla geçiş anında JavaScript müdahalesine izin verir. Bu event'lerin viewTransition özelliği üzerinden types set'ine değer ekleyebilirsin. CSS tarafında bu tiplere göre farklı animasyonlar tanımlarsın.
// Her iki sayfada da çalışacak script
window.addEventListener("pagereveal", (event) => {
if (!event.viewTransition) return;
const navigationType = performance.getEntriesByType("navigation")[0]?.type;
if (navigationType === "back_forward") {
event.viewTransition.types.add("backwards");
} else {
event.viewTransition.types.add("forwards");
}
});pageswap event'i eski sayfada, pagereveal ise yeni sayfada tetiklenir. Her ikisinde de viewTransition.types set'ine erişebilirsin. Yukarıdaki kodda Navigation Timing API'den back_forward bilgisini alıp geçiş tipini ayarlıyoruz.
CSS tarafında bu tiplere göre animasyonları koşullu hale getirmek için :active-view-transition-type() pseudo-class'ını kullanırsın:
/* İleri navigasyon */
:active-view-transition-type(forwards) {
&::view-transition-old(root) {
animation: slide-out-left 300ms ease-in both;
}
&::view-transition-new(root) {
animation: slide-in-right 300ms ease-out both;
}
}
/* Geri navigasyon */
:active-view-transition-type(backwards) {
&::view-transition-old(root) {
animation: slide-out-right 300ms ease-in both;
}
&::view-transition-new(root) {
animation: slide-in-left 300ms ease-out both;
}
}
@keyframes slide-out-left {
to { transform: translateX(-100%); }
}
@keyframes slide-in-right {
from { transform: translateX(100%); }
}
@keyframes slide-out-right {
to { transform: translateX(100%); }
}
@keyframes slide-in-left {
from { transform: translateX(-100%); }
}Artık ileri giderken sayfa sola, geri giderken sağa kayar. Tip sistemi sadece yönle sınırlı değil. Farklı sayfa türlerine göre farklı geçişler de tanımlayabilirsin: event.viewTransition.types.add("category-to-detail") gibi bir tip ekleyip CSS'te buna özel bir animasyon yazabilirsin.
CSS @view-transition içinde types tanımlama
JavaScript kullanmadan, CSS düzeyinde de tip belirleyebilirsin:
@view-transition {
navigation: auto;
types: slide, forwards;
}Bu yöntem statik. Yön gibi dinamik bilgilere ihtiyaç duyduğunda JavaScript gerekli oluyor. İki yaklaşımı birleştirebilirsin: CSS'te varsayılan tipi tanımla, JavaScript'te koşula göre override et.
pageswap'ta URL bilgisi ile hedef bazlı geçiş
pageswap event'i eski sayfada tetiklenir ve event.activation özelliği üzerinden navigasyon hedefine erişim sağlar:
window.addEventListener("pageswap", (event) => {
if (!event.viewTransition) return;
const targetURL = new URL(event.activation.entry.url);
if (targetURL.pathname.startsWith("/urun/")) {
event.viewTransition.types.add("to-detail");
}
});Bu sayede hedef URL'ye göre farklı animasyon tipleri atayabilirsin. Liste sayfasından detaya giderken kart morph'u, detaydan listeye dönerken fade-out gibi ayrımlar yapılabilir.
Erişilebilirlik ve performans
prefers-reduced-motion medya sorgusunu mutlaka göz önünde bulundur. Animasyonları tamamen devre dışı bırakmak yerine süresini kısaltmak daha iyi bir deneyim sunar:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation-duration: 0.01ms !important;
}
}Performans açısından tarayıcı snapshot alırken bir kompozit katman oluşturur. Büyük sayfalarda bu bellek tüketir. view-transition-name verdiğin her eleman ayrı bir katman olarak ele alınır; gereksiz yere onlarca elemana isim verme.
Tarayıcı desteği
Same-document view transitions (Level 1) Chrome 111'den beri destekleniyor. Cross-document view transitions (Level 2) Chrome 126 ile geldi. Firefox 132 ile same-document desteği ekledi ancak cross-document desteği henüz yok (Haziran 2025 itibarıyla Nightly'de flag arkasında). Safari 18.2 ile same-document desteğini getirdi; cross-document desteği Safari Technology Preview'da test ediliyor.
Progressive enhancement yaklaşımı burada doğal: @view-transition kuralını desteklemeyen tarayıcılar onu görmezden gelir ve klasik sayfa yüklemesine devam eder. Hiçbir işlevsellik kaybı olmaz, sadece animasyon eksik kalır.
Astro ve MPA framework'lerle kullanım
Cross-document view transitions, framework'den bağımsız olarak çalışır çünkü tarayıcı düzeyinde gerçekleşir. Ancak Astro 7 bu API'yi ilk benimseyen framework'lerden biri oldu. Astro'nun <ViewTransitions /> bileşeni, client-side routing ile aynı işi yapıyordu; artık native API ile daha az JavaScript gerekiyor.
HTMX gibi MPA odaklı araçlarla da uyumlu. Tarayıcı navigasyonu gerçekleştiği sürece (tam sayfa yüklemesi) @view-transition kuralı devreye girer.
React tabanlı SPA'larda same-document view transitions farklı bir API (document.startViewTransition) kullanır. React'in kendi ViewTransition bileşeni bu tarafı ele alır. Cross-document variant ise tam sayfa navigasyonu gerektirdiğinden, React Router ile client-side routing yapan uygulamalarda değil, SSR/MPA modundaki uygulamalarda (Next.js'in full page reload yaptığı senaryolar gibi) anlamlı olur.
Ne zaman cross-document, ne zaman same-document
SPA mimarisindeysen ve client-side routing kullanıyorsan document.startViewTransition (same-document) API'si doğru seçim. MPA mimarisindeysen, statik site oluşturuyorsan, ya da framework kullanmıyorsan cross-document view transitions tam ihtiyacın olan şey. İkisi birbirinin alternatifi değil, farklı mimari tercihlere karşılık geliyor. Doğru soru "hangisi daha iyi" değil, "sayfalarım gerçekten yeniden yükleniyor mu" sorusu. Yükleniyorsa @view-transition { navigation: auto; } yaz, birkaç view-transition-name ekle, tipler ile yönü kontrol et. Birkaç satır CSS ve minimal JavaScript ile kullanıcıya SPA hissini verebilirsin, üstelik SPA'nın JavaScript yükü olmadan.