React ViewTransition Bileşeni ile Native Sayfa Geçiş Animasyonları
Web uygulamalarında sayfa geçişleri her zaman tartışmalı bir konu olmuştur. Kullanıcılar mobil uygulamalardaki o akıcı, doğal hissettiren geçişleri web'de de beklerken, geliştiriciler Framer Motion, React Transition Group veya GSAP gibi ağır kütüphanelerle bu deneyimi taklit etmeye çalışıyordu. Artık bu dönem sona eriyor.
React'in deneysel sürümlerinde sunulan ve React 19 ekosistemiyle olgunlaşan <ViewTransition> bileşeni, tarayıcının native View Transitions API'sini doğrudan React bileşen ağacınızla entegre ediyor. Bu yazıda, bu bileşenin nasıl çalıştığını, gerçek dünya kullanım senaryolarını ve adım adım uygulama örneklerini derinlemesine inceleyeceğiz.
View Transitions API Nedir?
<ViewTransition> bileşenini anlamadan önce, temelinde yatan tarayıcı API'sini kavramak gerekiyor. View Transitions API, tarayıcının DOM değişikliklerini otomatik olarak animasyonlu hale getirmesini sağlayan bir Web API'sidir.
Çalışma prensibi şöyledir:
- Tarayıcı mevcut durumun bir ekran görüntüsünü (snapshot) alır
- DOM güncellenmesi gerçekleşir
- Yeni durumun ekran görüntüsü alınır
- İki snapshot arasında crossfade animasyonu uygulanır
Bu süreç tamamen tarayıcı tarafında, GPU hızlandırmalı olarak gerçekleşir. JavaScript ile piksel piksel animasyon hesaplamaya kıyasla çok daha performanslıdır.
// Vanilla JavaScript ile View Transitions API kullanımı
document.startViewTransition(() => {
// DOM güncellemesi
document.querySelector('.content').innerHTML = newContent;
});Ancak bu API, React'in deklaratif yapısıyla doğrudan uyumlu değildi — ta ki <ViewTransition> bileşeni gelene kadar.
React ViewTransition Bileşeni
React'in <ViewTransition> bileşeni, View Transitions API'sini React'in state yönetimi, concurrent rendering ve Suspense mekanizmalarıyla sorunsuz biçimde entegre eder. startTransition ile tetiklenen state güncellemeleri sırasında otomatik olarak tarayıcının view transition mekanizmasını devreye sokar.
Temel Kullanım
import { ViewTransition, startTransition } from 'react';
import { useState } from 'react';
function App() {
const [currentPage, setCurrentPage] = useState('home');
const navigateTo = (page) => {
startTransition(() => {
setCurrentPage(page);
});
};
return (
<div className="app">
<nav>
<button onClick={() => navigateTo('home')}>Ana Sayfa</button>
<button onClick={() => navigateTo('about')}>Hakkında</button>
<button onClick={() => navigateTo('contact')}>İletişim</button>
</nav>
<ViewTransition>
{currentPage === 'home' && <HomePage />}
{currentPage === 'about' && <AboutPage />}
{currentPage === 'contact' && <ContactPage />}
</ViewTransition>
</div>
);
}Bu kadar basit. <ViewTransition> içindeki içerik startTransition aracılığıyla değiştiğinde, tarayıcı otomatik olarak eski ve yeni içerik arasında crossfade animasyonu uygular.
Kritik Nokta: startTransition Zorunluluğu
<ViewTransition> bileşeninin animasyon tetiklemesi için state güncellemesinin mutlaka startTransition içinde yapılması gerekir. Normal bir setState çağrısı animasyonu tetiklemez:
// ❌ Animasyon tetiklenmez
const handleClick = () => {
setCurrentPage('about');
};
// ✅ Animasyon tetiklenir
const handleClick = () => {
startTransition(() => {
setCurrentPage('about');
});
};CSS ile Animasyonları Özelleştirme
Varsayılan crossfade animasyonu birçok senaryo için yeterli olsa da, gerçek projelerde daha sofistike geçişler isteyeceksiniz. <ViewTransition> bileşeni, CSS view-transition-name ve ::view-transition pseudo-elementleri ile tam uyumlu çalışır.
Slide (Kaydırma) Animasyonu
/* Çıkan sayfa sola kayarak kaybolsun */
::view-transition-old(page-content) {
animation: slide-out-left 0.3s ease-in both;
}
/* Gelen sayfa sağdan kayarak gelsin */
::view-transition-new(page-content) {
animation: slide-in-right 0.3s ease-out both;
}
@keyframes slide-out-left {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(-100%); opacity: 0; }
}
@keyframes slide-in-right {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}function App() {
const [page, setPage] = useState('home');
return (
<ViewTransition name="page-content">
<main key={page} style={{ viewTransitionName: 'page-content' }}>
{page === 'home' ? <HomePage /> : <AboutPage />}
</main>
</ViewTransition>
);
}name Prop'u ile Birden Fazla Geçiş
Sayfanın farklı bölümlerini bağımsız olarak animasyonlandırabilirsiniz:
function ProductPage({ product }) {
return (
<div className="product-page">
<ViewTransition name="product-image">
<img
src={product.image}
alt={product.name}
style={{ viewTransitionName: 'product-image' }}
/>
</ViewTransition>
<ViewTransition name="product-details">
<div style={{ viewTransitionName: 'product-details' }}>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span className="price">{product.price} TL</span>
</div>
</ViewTransition>
</div>
);
}/* Ürün görseli yumuşak bir scale animasyonuyla değişsin */
::view-transition-old(product-image) {
animation: scale-down 0.4s ease-in-out both;
}
::view-transition-new(product-image) {
animation: scale-up 0.4s ease-in-out both;
}
@keyframes scale-down {
from { transform: scale(1); opacity: 1; }
to { transform: scale(0.8); opacity: 0; }
}
@keyframes scale-up {
from { transform: scale(0.8); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* Detaylar crossfade ile geçiş yapsın */
::view-transition-old(product-details) {
animation: fade-out 0.25s ease-out both;
}
::view-transition-new(product-details) {
animation: fade-in 0.25s ease-in 0.15s both;
}React Router ile Entegrasyon
Gerçek dünyada sayfa geçişleri genellikle router ile yönetilir. React Router v7 ve sonrası, <ViewTransition> bileşeniyle doğal bir entegrasyon sunar.
React Router v7+ ile Kullanım
import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';
import { ViewTransition, startTransition } from 'react';
function Layout() {
const navigate = useNavigate();
const handleNavigation = (path) => {
startTransition(() => {
navigate(path);
});
};
return (
<div>
<nav>
<button onClick={() => handleNavigation('/')}>Ana Sayfa</button>
<button onClick={() => handleNavigation('/blog')}>Blog</button>
<button onClick={() => handleNavigation('/portfolio')}>Portföy</button>
</nav>
<ViewTransition>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/blog" element={<BlogPage />} />
<Route path="/portfolio" element={<PortfolioPage />} />
</Routes>
</ViewTransition>
</div>
);
}Shared Element Transitions (Paylaşılan Eleman Geçişleri)
En etkileyici kullanım senaryolarından biri, iki sayfa arasında aynı öğenin (örneğin bir ürün kartındaki görselin) doğal bir şekilde yeni konumuna hareket etmesidir. Bu, mobil uygulamalarda "hero animation" olarak bilinen tekniktir.
// Ürün listesi sayfası
function ProductList({ products }) {
const navigate = useNavigate();
return (
<div className="product-grid">
{products.map((product) => (
<ViewTransition key={product.id} name={`product-${product.id}`}>
<div
className="product-card"
style={{ viewTransitionName: `product-${product.id}` }}
onClick={() => {
startTransition(() => {
navigate(`/product/${product.id}`);
});
}}
>
<img src={product.thumbnail} alt={product.name} />
<h3>{product.name}</h3>
</div>
</ViewTransition>
))}
</div>
);
}
// Ürün detay sayfası
function ProductDetail({ product }) {
return (
<ViewTransition name={`product-${product.id}`}>
<div
className="product-detail"
style={{ viewTransitionName: `product-${product.id}` }}
>
<img src={product.fullImage} alt={product.name} />
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
</ViewTransition>
);
}Aynı viewTransitionName değerine sahip iki eleman arasında tarayıcı otomatik olarak pozisyon, boyut ve görünüm geçişi uygular. Sonuç: karttan detay sayfasına uçan bir görsel.
Suspense ile Entegrasyon
<ViewTransition> bileşeninin en güçlü yanlarından biri Suspense ile birlikte çalışabilmesidir. Lazy-loaded bileşenler yüklenirken bile animasyonlar düzgün çalışır:
import { lazy, Suspense, startTransition } from 'react';
import { ViewTransition } from 'react';
const HeavyDashboard = lazy(() => import('./HeavyDashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
const [view, setView] = useState('dashboard');
const switchView = (newView) => {
startTransition(() => {
setView(newView);
});
};
return (
<ViewTransition>
<Suspense fallback={<LoadingSkeleton />}>
{view === 'dashboard' ? <HeavyDashboard /> : <Settings />}
</Suspense>
</ViewTransition>
);
}React, Suspense sınırı çözümlenene kadar eski içeriğin snapshot'ını korur ve yeni içerik hazır olduğunda animasyonlu geçişi başlatır. Kullanıcı asla boş bir ekran görmez.
Performans İpuçları ve Best Practices
1. Animasyon Süresini Kısa Tutun
/* ✅ İdeal: 200-400ms arası */
::view-transition-old(*) {
animation-duration: 0.25s;
}
/* ❌ Çok uzun, kullanıcıyı bekletir */
::view-transition-old(*) {
animation-duration: 1.5s;
}2. Reduced Motion Tercihine Saygı Gösterin
@media (prefers-reduced-motion: reduce) {
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}3. Benzersiz Transition Name'ler Kullanın
Aynı anda görünen iki element asla aynı viewTransitionName değerine sahip olmamalıdır. Bu, tarayıcının hangi elementlerin eşleştiğini karıştırmasına neden olur.
4. Fallback Stratejisi Uygulayın
View Transitions API henüz tüm tarayıcılarda desteklenmediğinden, graceful degradation sağlayın:
function AnimatedContainer({ children, name }) {
// ViewTransition desteklenmiyorsa sadece children'ı render et
if (typeof document !== 'undefined' && !document.startViewTransition) {
return <>{children}</>;
}
return <ViewTransition name={name}>{children}</ViewTransition>;
}5. Büyük DOM Ağaçlarından Kaçının
ViewTransition içinde mümkün olduğunca küçük ve hedefli DOM parçaları bulundurun. Tüm sayfayı tek bir ViewTransition ile sarmak yerine, değişen bölümleri ayrı ayrı sarın.
Tarayıcı Desteği
View Transitions API'nin tarayıcı desteği giderek genişliyor:
| Tarayıcı | Destek Durumu |
|---|---|
| Chrome 111+ | ✅ Tam destek |
| Edge 111+ | ✅ Tam destek |
| Safari 18+ | ✅ Tam destek |
| Firefox 135+ | ✅ Tam destek |
| Opera 97+ | ✅ Tam destek |
2025 ortası itibarıyla tüm major tarayıcılar bu API'yi desteklemektedir. Yine de eski tarayıcı kullanıcıları için fallback stratejisi uygulamanız tavsiye edilir.
Sonuç
React <ViewTransition> bileşeni, web uygulamalarında sayfa geçiş animasyonları konusunda bir paradigma değişikliği yaratıyor. Özetlemek gerekirse:
- Sıfır JavaScript animasyon kodu: Tarayıcı GPU'su işi üstleniyor
- Deklaratif API: React'in bileşen modeliyle doğal entegrasyon
- Suspense uyumluluğu: Lazy loading ile sorunsuz çalışma
- Shared element transitions: Mobil uygulama kalitesinde geçişler
- Minimal bundle boyutu: Harici animasyon kütüphanelerine elveda
- CSS ile tam kontrol: Pseudo-elementler aracılığıyla sonsuz özelleştirme
Framer Motion'ın bundle boyutundan (~40KB) veya karmaşık animasyon orkestrasyonlarından bıktıysanız, <ViewTransition> tam aradığınız çözüm. Tarayıcının native yeteneklerini kullanarak daha az kodla daha performanslı ve daha akıcı kullanıcı deneyimleri oluşturabilirsiniz. Bugün bir yan projede denemeye başlayın — farkı ilk geçişte hissedeceksiniz.