CSS view transitions (görünüm geçişleri), bir sayfanın iki durumu arasında, hatta aynı sitedeki iki sayfa arasında, tarayıcının eski ve yeni görünümün anlık görüntülerini alıp aralarında animasyon yapmasını sağlar. Çok sayfalı bir sitede her iki sayfanın CSS dosyasına @view-transition { navigation: auto; } yazmak, aynı kökenli sayfalar arasında gezinirken varsayılan olarak yumuşak bir çapraz solma (cross-fade) elde etmeye yeter. Belirli öğelere view-transition-name vererek onları ayrıca canlandırabilir, ::view-transition-old() ve ::view-transition-new() sözde öğeleriyle animasyonu özelleştirebilirsiniz.

Bu yazıda CSS tarafını, yani @view-transition kuralını, view-transition-name, view-transition-class ve view-transition-scope özelliklerini, görünüm geçişi sözde öğe ağacını ve geçiş türlerine göre stil vermeyi MDN belgelerine dayanarak anlatıyoruz. Aynı sayfa içindeki geçişleri başlatan JavaScript tarafı (document.startViewTransition()) bu yazının kapsamı dışında; yalnızca adını anıyoruz. Sözde öğelerin içinde kullanılan animasyon sözdizimi için CSS animation ve @keyframes yazısına bakabilirsiniz.

Görünüm geçişi nasıl çalışır?

MDN'ye göre CSS view transitions modülü, View Transition API'nin davranışını tanımlar ve belge içindeki farklı durumlar arasında ya da belgeler arasında animasyonlu geçiş yapılmasına izin verir. Geçiş sırasında tarayıcı değişen arayüzün eski ve yeni halinin anlık görüntülerini alır. Varsayılan olarak geçiş sırasında olan tüm değişiklikler tek bir anlık görüntü grubunda, root grubunda, toplanır ve birlikte yumuşak bir çapraz solma ile canlanır.

İki tür geçiş vardır. Aynı belge içindeki (tek sayfa uygulaması, SPA) geçişler JavaScript ile startViewTransition() çağrılarak başlatılır. Belgeler arası (çok sayfalı uygulama, MPA) geçişler ise tamamen CSS ile, @view-transition kuralıyla etkinleştirilir. İki durumda da animasyonun kendisi CSS animasyonlarıyla, özel sözde öğeler üzerinden yönetilir.

@view-transition ile sayfalar arası geçiş

@view-transition kuralı, bir gezinme sırasında mevcut ve hedef belgenin görünüm geçişine katılmasını sağlar. MDN, geçişin çalışması için iki belgenin aynı kökende (same-origin) olması gerektiğini vurgular ve kuralın her iki belgenin CSS dosyasında yer almasını ister.

css
@view-transition {
  navigation: auto;
}

navigation tanımlayıcısı iki değer alır. auto değerinde belge, gezinme aynı kökenliyse, kökenler arası yönlendirme içermiyorsa ve gezinme türü traverse, push ya da replace ise geçişe katılır. push ve replace için ek bir koşul vardır: Gezinme, tarayıcı arayüzünden değil sayfa içeriğiyle etkileşimden (örneğin bir bağlantıya tıklama) başlamalıdır. none değeri geçişi kapatır.

İkinci tanımlayıcı types, geçişe bir ya da daha fazla tür adı atar (örneğin types: slide;). Türler, aynı sitenin farklı geçişleri için farklı animasyonlar yazmanıza izin verir; bunlara aşağıda ayrıca değineceğiz.

Kural tek başına çapraz solma verir. Animasyonu değiştirmek için kök grubun eski ve yeni görüntülerini hedeflersiniz. MDN'nin çok sayfalı demo örneği eski sayfayı yukarı kaydırıp yeni sayfayı aşağıdan getirir:

css
@view-transition {
  navigation: auto;
}

@keyframes disari {
  from {
    transform: translateY(0%);
  }
  to {
    transform: translateY(-100%);
  }
}

@keyframes iceri {
  from {
    transform: translateY(100%);
  }
  to {
    transform: translateY(0%);
  }
}

::view-transition-old(root) {
  animation: 0.4s ease-in both disari;
}

::view-transition-new(root) {
  animation: 0.4s ease-in both iceri;
}
/* sonuç: eski sayfa yukarı çıkar, yeni sayfa alttan gelir */

Sözde öğe ağacı

Sözde öğe ağacı: ::view-transition, ::view-transition-group(), ::view-transition-image-pair(), ::view-transition-old()

Geçiş sırasında tarayıcı, sayfanın tüm içeriğinin üzerinde duran geçici bir sözde öğe ağacı oluşturur. Her düğümün belirli bir görevi ve tarayıcının varsayılan stil sayfasında tanımlanmış başlangıç stilleri vardır. Ağacın yapısı şöyledir:

css
/* geçiş sırasında oluşan ağaç (stil kuralı değil, yapı şeması):

::view-transition
  ::view-transition-group(root)
    ::view-transition-image-pair(root)
      ::view-transition-old(root)
      ::view-transition-new(root)
  ::view-transition-group(baslik)
    ::view-transition-image-pair(baslik)
      ::view-transition-old(baslik)
      ::view-transition-new(baslik)
*/
Sözde öğeGöreviMDN'deki varsayılan stil özeti
::view-transitionTüm geçiş katmanının kökü, sayfanın üstünde dururposition: fixed; inset: 0
::view-transition-group()Tek bir anlık görüntü grubuposition: absolute; top: 0; left: 0; animation-duration: 0.25s; animation-fill-mode: both
::view-transition-image-pair()Eski ve yeni görüntünün kapsayıcısıposition: absolute; inset: 0; süre, dolgu ve gecikmeyi kalıtır; isolation: isolate
::view-transition-old()Eski görünümün durağan anlık görüntüsüVarsayılan olarak solarak kaybolur
::view-transition-new()Yeni görünümün canlı temsiliVarsayılan olarak solarak belirir

Bu tablo birkaç pratik sonuç doğurur. Varsayılan geçiş süresi grup düzeyinde 0.25s olarak tanımlıdır ve alt düğümler bu süreyi kalıtır. Bu yüzden tüm geçişleri yavaşlatmak için tek bir kural yeterlidir: ::view-transition-group(*) { animation-duration: 0.5s; }. Grup sözde öğesi ayrıca, hem eski hem yeni durum varsa genişliğini ve yüksekliğini eski durumun kenarlık kutusu boyutundan yeninin boyutuna, konumunu da eski ekran konumundan yeni ekran konumuna doğru canlandırır. MDN bu stillerin geçiş başladığında dinamik olarak üretildiğini belirtir.

::view-transition-old() ve ::view-transition-new() yerine konan (replaced) öğelerdir; bu nedenle object-fit ve object-position ile şekillendirilebilirler ve doğal boyutları içeriğin boyutuna eşittir. Varsayılan stilde ikisi de grubun genişliğini kaplar, yükseklikleri auto olur. İki görüntü üst üste bindiğinde plus-lighter karışım moduyla birleştirilir; görüntü çifti üzerindeki isolation: isolate bu karışımın sayfanın geri kalanını etkilemesini önler.

Parametre olarak * tüm grupları, root sayfanın varsayılan grubunu, bir ad ise o adla işaretlenmiş grubu seçer. Bir sınıf adı nokta ile yazılır (.kart). Özgüllük açısından MDN, adlı görünüm geçişi sözde öğesinin bir tür seçicisi kadar özgüllüğe sahip olduğunu, parametre * ise özgüllüğün sıfır olduğunu belirtir. Özgüllük hesabı specificity yazısında anlatılıyor.

view-transition-name: öğeyi ayrı canlandırmak

view-transition-name: öğeyi ayrı canlandırmak

Bir öğenin kök gruptan farklı canlanmasını istiyorsanız ona bir view-transition-name verirsiniz. Öğe artık kendi anlık görüntü grubunda yer alır ve sözde öğelerde bu adla seçilebilir. Ad bir custom-ident olmalıdır; auto, match-element, none ya da CSS genelinde geçerli anahtar sözcükler (inherit gibi) ad olarak kullanılamaz.

css
figcaption {
  view-transition-name: resim-yazisi;
}

@keyframes daral {
  from {
    transform: scaleX(1);
  }
  to {
    transform: scaleX(0);
  }
}

@keyframes genisle {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

::view-transition-group(resim-yazisi) {
  height: auto;
  right: 0;
  left: auto;
  transform-origin: right center;
}

::view-transition-old(resim-yazisi) {
  animation: 0.25s linear both daral;
}

::view-transition-new(resim-yazisi) {
  animation: 0.25s 0.25s linear both genisle;
  /* 0.25s gecikme: yeni yazı, eskisi kapandıktan sonra açılır */
}

Bu örnek MDN'nin tek sayfalık galeri demosundan uyarlanmıştır. Sayfanın geri kalanı varsayılan çapraz solmayla değişirken resim yazısı önce yatayda daralır, ardından yenisi genişleyerek gelir. Grup üzerindeki ek stiller iki görüntünün aynı yerde kalmasını ve varsayılan stillerin özel animasyona karışmamasını sağlar. Zamanlamayı somutlaştıralım: Eski yazı 0 ile 0,25 saniye arasında kapanır; yeni yazı 0,25 saniye gecikmeyle başlar ve 0,25 + 0,25 = 0,5 saniyede tamamlanır.

Adın benzersiz olma kuralı en sık yapılan hatadır. MDN'ye göre geçişe katılan her çizilmiş öğenin adı benzersiz olmalıdır. Aynı anda iki öğe aynı view-transition-name değerine sahipse geçiş atlanır. Bu yüzden bir listedeki tüm öğelere aynı adı veren li { view-transition-name: oge; } gibi bir kural çalışmaz.

Liste gibi çok sayıda öğeyi ayrı ayrı canlandırmak için her birine li:nth-child(1), li:nth-child(2) diye ad yazmak yerine match-element değerini kullanabilirsiniz. Bu değer tarayıcının her seçilen öğeye kendi içinde benzersiz bir ad atamasını sağlar; ad DOM üzerinden okunamaz. MDN, adlar öğe kimliğine göre atandığı için match-element değerinin yalnızca aynı belge içindeki geçişlerde kullanılabileceğini, sayfalar arası geçişlerde çalışmayacağını belirtir.

css
li {
  view-transition-name: match-element;
  /* her liste öğesi kendi grubunda canlanır */
}

.canlanmasin {
  view-transition-name: none;
}

none değeri öğeyi ayrı bir anlık görüntüye almaz; ancak ebeveyninde bir ad varsa öğe o ebeveynin görüntüsünün parçası olarak yakalanır.

view-transition-class: ortak stil kancası

Birçok öğeye benzer animasyon vermek istediğinizde her adı tek tek sözde öğe seçicisinde yazmak yorucudur. view-transition-class bu iş için bir veya birden fazla sınıf adı tanımlar. MDN'nin vurguladığı fark şudur: Sınıf benzersiz olmak zorunda değildir ve öğeyi yakalamaya işaretlemez. Her öğenin yine kendi benzersiz view-transition-name değerine ihtiyacı vardır; sınıf yalnızca zaten adı olan öğeleri ortak biçimde stillemenin ek bir yoludur.

css
.kart-1 {
  view-transition-name: kart-1;
  view-transition-class: kart;
}

.kart-2 {
  view-transition-name: kart-2;
  view-transition-class: kart;
}

::view-transition-group(.kart) {
  animation-duration: 0.4s;
  /* iki kartın grubu da 0.4 saniye sürer */
}

MDN, view-transition-class desteği görünüm geçişlerini destekleyen tüm tarayıcılarda tam olana kadar her öğe için ayrıca adla yazılmış bir ::view-transition-group() kuralı eklenmesini önerir. Böylece sınıf desteği olmayan bir tarayıcıda da öğe, adıyla yazılmış kurala göre stil alır.

Geçiş türleri ve :active-view-transition

Bir galeride ileri ve geri gitmek ya da öğe eklemek ve silmek için farklı animasyonlar isteyebilirsiniz. Görünüm geçişi türleri bunun içindir. Sayfalar arası geçişlerde tür @view-transition içindeki types tanımlayıcısıyla, aynı belge içindeki geçişlerde startViewTransition() seçenekleriyle atanır. CSS tarafında :active-view-transition-type() sözde sınıfı, belirtilen türlerden biri etkin bir geçişte varsa kök öğeyle eşleşir.

css
@view-transition {
  navigation: auto;
  types: kaydir;
}

html:active-view-transition-type(kaydir)::view-transition-old(root) {
  animation: 0.4s ease-in both disari;
}

html:active-view-transition-type(kaydir)::view-transition-new(root) {
  animation: 0.4s ease-in both iceri;
}
/* disari ve iceri, yukarıdaki @keyframes tanımlarıdır */

Sözde sınıfın içine virgülle birden fazla tür yazmak "veya" anlamına gelir: html:active-view-transition-type(ileri, geri) geçişte bu türlerden biri ya da ikisi varsa eşleşir. "Ve" davranışı için sözde sınıfları zincirlersiniz: html:active-view-transition-type(kaydir):active-view-transition-type(ileri) yalnızca iki tür birlikte atanmışsa eşleşir. Türden bağımsız olarak :active-view-transition sözde sınıfı herhangi bir geçiş sürerken kök öğeyle eşleşir ve geçiş bitince eşleşmeyi bırakır; geçiş sırasında geçici bir stil uygulamak için kullanılabilir. Bu kuralları iç içe yazımla gruplamak isterseniz CSS nesting yazısına bakabilirsiniz; burada açıklık için tam seçiciler kullandık.

view-transition-scope: ad çakışmalarını sınırlamak

Bir sayfada farklı kaynaklardan gelen bileşenler aynı view-transition-name değerini kullanırsa ad çakışması yaşanır. MDN, match-element değerinin bu sorunu çözebildiğini ama kontrol etmediğiniz bileşenlerde yine çakışma olabileceğini belirtir. view-transition-scope: all, anlık görüntüsü alınacak öğelerin aranmasını bu özelliğin verildiği öğenin alt ağacıyla sınırlar. Varsayılan değer none, yani arama herhangi bir alt ağaçla sınırlı değildir. MDN, öğe kapsamlı bir görünüm geçişi başlatıldığında tarayıcının geçiş kök öğesine bu değeri kendiliğinden verdiğini de not eder.

Erişilebilirlik: hareketi azaltma tercihi

Görünüm geçişleri de birer animasyondur ve büyük alanların kayması ya da ölçeklenmesi hareket hassasiyeti olan kullanıcılarda rahatsızlık yaratabilir. MDN'nin animasyon sayfaları prefers-reduced-motion tercihine saygı gösterilmesini önerir. Görünüm geçişlerinde basit bir yaklaşım, kayma gibi büyük hareketleri yalnızca kullanıcı bir tercih bildirmediğinde tanımlamak ve tercih açıkken varsayılan çapraz solmayı bırakmaktır:

css
/* kayma animasyonlarını yalnızca kullanıcı hareket azaltma
   tercihi bildirmediyse tanımlayın; aksi halde varsayılan solma kalır */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: 0.4s ease-in both disari;
  }
  ::view-transition-new(root) {
    animation: 0.4s ease-in both iceri;
  }
}

prefers-reduced-motion medya özelliği reduce ve no-preference değerlerini alır; MDN no-preference değerinin yanlış olarak değerlendirildiğini, @media (prefers-reduced-motion) yazmanın reduce ile eşdeğer olduğunu belirtir. Medya sorgusu ayrıntıları @media yazısında.

Sık yapılan hatalar

Sık yapılan hatalar: Kuralı tek sayfaya yazmak, Farklı kökenler, Tekrarlanan ad, match-element ile sayfalar arası geçiş beklemek
  • Kuralı tek sayfaya yazmak: Sayfalar arası geçiş için @view-transition hem çıkılan hem gidilen belgede bulunmalıdır.
  • Farklı kökenler: Belgeler aynı kökende değilse ya da gezinme kökenler arası bir yönlendirme içeriyorsa geçiş olmaz.
  • Tekrarlanan ad: Aynı anda iki öğe aynı view-transition-name değerini taşırsa geçiş atlanır.
  • match-element ile sayfalar arası geçiş beklemek: Bu değer yalnızca aynı belge içinde çalışır.
  • Sınıfı ad yerine kullanmak: view-transition-class tek başına öğeyi yakalamaz; ad yine gereklidir.
  • Yasak ad seçmek: none, auto, match-element ya da inherit gibi değerler ad olamaz.
  • Süreyi yanlış yerde değiştirmek: Varsayılan süre grup düzeyinde tanımlıdır; alt sözde öğeler bunu kalıtır.

Sık Sorulan Sorular

Görünüm geçişi için JavaScript gerekir mi?

Aynı kökenli sayfalar arasındaki geçişler için gerekmez; @view-transition { navigation: auto; } yeterlidir. Tek sayfa uygulamalarında aynı belge içindeki durum değişikliklerini canlandırmak için ise geçişi JavaScript ile başlatmanız gerekir.

Varsayılan geçiş nasıl görünür?

Herhangi bir özelleştirme yapmazsanız tüm değişiklikler kök grupta toplanır ve eski görüntü solarken yeni görüntü belirir; MDN bunu yumuşak bir çapraz solma olarak tanımlar. Adı olan öğelerin grubu ayrıca eski konum ve boyuttan yeni konum ve boyuta doğru canlanır.

Geçiş süresini nasıl değiştiririm?

::view-transition-group(*) ya da belirli bir ad için ::view-transition-group(ad) seçicisine animation-duration verin. Görüntü çifti ve eski ile yeni görüntüler bu süreyi kalıtır.

View transitions ile CSS transition aynı şey mi?

Hayır. CSS transition tek bir öğenin bir özelliğinin değişimini canlandırır. Görünüm geçişi ise sayfanın ya da seçilen öğelerin iki durumu arasındaki anlık görüntüleri sözde öğeler aracılığıyla canlandırır.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: CSS öğrenme rehberi · CSS animation ve @keyframes · CSS transition · Scroll-driven animations · Pseudo-element'ler · CSS nesting · Core Web Vitals