Tek sayfalık uygulamalar için aynı doküman görünümü geçişleri

Görüntüleme geçişi tek bir dokümanda çalıştırıldığında, buna aynı doküman görüntüleme geçişi adı verilir. Bu durum genellikle, DOM'yi güncellemek için JavaScript'in kullanıldığı tek sayfalık uygulamalarda (SPA'lar) geçerlidir. Aynı doküman görüntüleme geçişleri Chrome 111 sürümünden itibaren Chrome'da desteklenmektedir.

Aynı doküman görüntüleme geçişini tetiklemek için document.startViewTransition numaralı telefonu arayın:

function handleClick(e) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow();
    return;
  }

  // With a View Transition:
  document.startViewTransition(() => updateTheDOMSomehow());
}

Çağrılduğunda, tarayıcı, üzerinde view-transition-name CSS özelliği tanımlanmış tüm öğelerin anlık görüntülerini otomatik olarak yakalar.

Ardından, iletilen geri çağırmayı yürüterek DOM'u günceller ve ardından yeni durumun anlık görüntülerini alır.

Daha sonra bu anlık görüntüler, yapay öğelerden oluşan bir ağaçta düzenlenir ve CSS animasyonlarının gücü kullanılarak canlandırılır. Eski ve yeni durumdaki anlık görüntüler çiftler eski konumlarından ve boyutlarından yeni konumlarına sorunsuz bir şekilde geçiş yaparken içerikleri değişir. İsterseniz animasyonları özelleştirmek için CSS'yi kullanabilirsiniz.


Varsayılan geçiş: Çapraz geçiş

Varsayılan görünüm geçişi çapraz geçiştir; bu nedenle API'yi tanıtmak için iyi bir giriş işlevi görür:

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // With a transition:
  document.startViewTransition(() => updateTheDOMSomehow(data));
}

Burada updateTheDOMSomehow, DOM'u yeni duruma değiştirir. Bunu istediğiniz gibi yapabilirsiniz. Örneğin, öğe ekleyip kaldırabilir, sınıf adlarını ya da stilleri değiştirebilirsiniz.

Bu şekilde, sayfalar çapraz olarak şeffaflaştırılır:

Varsayılan çapraz geçiş. Minimal demo. Kaynak.

Pekala, çapraz geçiş o kadar da etkileyici değil. Neyse ki geçişler özelleştirilebilir ama öncelikle bu temel çapraz geçişinin nasıl çalıştığını anlamanız gerekir.


Bu geçişlerin işleyiş şekli

Önceki kod örneğini güncelleyelim.

document.startViewTransition(() => updateTheDOMSomehow(data));

.startViewTransition() çağrıldığında, API sayfanın mevcut durumunu yakalar. Fotoğraf çekmek de buna dahildir.

Tamamlandığında, .startViewTransition() öğesine iletilen geri çağırma çağrılır. DOM burada değiştirilir. Ardından, API, sayfanın yeni durumunu yakalar.

Yeni durum yakalandıktan sonra API, aşağıdaki gibi bir yapay öğe ağacı oluşturur:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

::view-transition öğesi bir yer paylaşımında, sayfadaki diğer her şeyin üzerine yerleştirilir. Bu, geçiş için bir arka plan rengi ayarlamak istediğinizde yararlı olur.

::view-transition-old(root) eski görünümün ekran görüntüsü, ::view-transition-new(root) ise yeni görünümün canlı temsilidir. Her ikisi de CSS'nin "değiştirilmiş içerik" olarak (<img> gibi) oluşturulur.

Eski görünüm opacity: 1 ile opacity: 0 arasında, yeni görünüm ise opacity: 0 ile opacity: 1 arasında animasyonlarla bir çapraz geçiş oluşturur.

Animasyonun tamamı CSS animasyonları kullanılarak gerçekleştirilir, böylece CSS ile özelleştirilebilir.

Geçişi özelleştirme

Görünüm geçişi sözde öğelerinin tümü CSS ile hedeflenebilir ve animasyonlar CSS kullanılarak tanımlandığından, mevcut CSS animasyon özelliklerini kullanarak bunları değiştirebilirsiniz. Örneğin:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 5s;
}

Bu tek değişiklikle birlikte, bu geçiş artık gerçekten yavaşlamış:

Uzun çapraz geçiş. Minimal demo. Kaynak.

Tamam, bu hâlâ etkileyici değil. Bunun yerine, aşağıdaki kod Materyal Tasarım'ın paylaşılan eksen geçişini uygular:

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes slide-from-right {
  from { transform: translateX(30px); }
}

@keyframes slide-to-left {
  to { transform: translateX(-30px); }
}

::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

Sonuç şöyle olur:

Paylaşılan eksen geçişi. Minimal demo. Kaynak.

Birden çok öğeyi geçirme

Önceki demoda, paylaşılan eksen geçişinde tüm sayfa yer alır. Bu yöntem sayfanın büyük bir kısmında işe yarar, ancak başlık tekrar kayarak aşağı kaydığı için başlık için pek doğru sayılmaz.

Bunu önlemek için başlığı sayfanın geri kalanından çıkartıp ayrı olarak canlandırabilirsiniz. Bu işlem, öğeye bir view-transition-name atanarak yapılır.

.main-header {
  view-transition-name: main-header;
}

view-transition-name değeri istediğiniz gibi olabilir (none dışında, geçiş adı olmadığı anlamına gelir). Öğeyi geçiş boyunca benzersiz bir şekilde tanımlamak için kullanılır.

Bunun sonucu:

Sabit başlıklı paylaşılan eksen geçişi. Minimal demo. Kaynak.

Böylece başlık yerinde kalır ve yanarak geçiş yapar.

Bu CSS bildirimi, sözde öğe ağacının değişmesine neden oldu:

::view-transition
├─ ::view-transition-group(root)
│  └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
   └─ ::view-transition-image-pair(main-header)
      ├─ ::view-transition-old(main-header)
      └─ ::view-transition-new(main-header)

Şu anda iki geçiş grubu vardır. Biri başlık, diğeri de geri kalan kısım için. Bunlar CSS ile bağımsız olarak hedeflenebilir ve farklı geçişler verilir. Yine de bu örnekte, çapraz geçiş olan varsayılan geçişle main-header kalmıştır.

Tamamdır, varsayılan geçiş sadece çapraz geçişi değil, ::view-transition-group aynı zamanda geçiş yapar:

  • Konumlandırma ve dönüştürme (transform kullanarak)
  • Genişlik
  • Boy

Başlık aynı boyutta olduğundan ve DOM değişikliğinin her iki tarafında da konumlandığından, bu şimdiye kadar önemli değildi. Ancak, başlıktaki metni de çıkarabilirsiniz:

.main-header-text {
  view-transition-name: main-header-text;
  width: fit-content;
}

Öğenin kalan genişliğe uzatmak yerine, metnin boyutu olması için fit-content kullanılır. Bu olmadan, geri ok simgesi her iki sayfada aynı boyut yerine başlık metin öğesinin boyutunu küçültür.

Şimdi üzerinde oynayacağımız üç bölüm var:

::view-transition
├─ ::view-transition-group(root)
│  └─ …
├─ ::view-transition-group(main-header)
│  └─ …
└─ ::view-transition-group(main-header-text)
   └─ …

Ama yine de varsayılanlara devam edelim:

Kayan başlık metni. Minimal demo. Kaynak.

Şimdi başlık metni, geri düğmesine yer açmak için aşağı doğru küçük bir kaydırma yapıyor.


Birden fazla sözde öğeye view-transition-class ile aynı şekilde animasyon uygulama

Tarayıcı Desteği

  • 125
  • 125
  • x
  • x

Birçok kartın yanı sıra sayfada bir başlık içeren bir görüntüleme geçişiniz olduğunu varsayalım. Başlık hariç tüm kartları canlandırmak için her bir kartı hedefleyen bir seçici yazmanız gerekir.

h1 {
    view-transition-name: title;
}
::view-transition-group(title) {
    animation-timing-function: ease-in-out;
}

#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
…
#card20 { view-transition-name: card20; }

::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),
…
::view-transition-group(card20) {
    animation-timing-function: var(--bounce);
}

20 öğeniz var mı? Yazmanız gereken 20 seçici var. Yeni bir öğe mi ekliyorsunuz? Daha sonra, animasyon stillerini uygulayan seçiciyi de büyütmeniz gerekir. Tam olarak ölçeklenebilir değildir.

view-transition-class, aynı stil kuralını uygulamak için görünüm geçişi sözde öğelerinde kullanılabilir.

#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
#card5 { view-transition-name: card5; }
…
#card20 { view-transition-name: card20; }

#cards-wrapper > div {
  view-transition-class: card;
}
html::view-transition-group(.card) {
  animation-timing-function: var(--bounce);
}

Aşağıdaki kartlar örneğinde önceki CSS snippet'inden yararlanılmıştır. Yeni eklenenler de dahil tüm kartlara tek bir seçiciyle aynı zamanlama uygulanır: html::view-transition-group(.card).

Kartlar demosunun kaydı. view-transition-class kullanıldığında, aynı animation-timing-function politikası, eklenen veya kaldırılanlar hariç tüm kartlara uygulanır.

Hata ayıklama geçişleri

Görünüm geçişleri CSS animasyonlarına dayalı olarak oluşturulduğundan, Chrome Geliştirici Araçları'ndaki Animasyonlar paneli, geçişlerde hata ayıklamak için idealdir.

Animasyonlar panelini kullanarak bir sonraki animasyonu duraklatabilir ve ardından animasyonda ileri geri sarabilirsiniz. Bu işlem sırasında, sözde geçiş öğeleri Öğeler panelinde bulunabilir.

Chrome Geliştirici Araçları ile görünüm geçişlerinde hata ayıklama.

Geçiş öğelerinin aynı DOM öğesi olması gerekmez

Şimdiye kadar başlık ve başlık metni için ayrı geçiş öğeleri oluşturmak üzere view-transition-name kodunu kullandık. Bunlar, DOM değişikliğinden önceki ve sonraki kavramsal olarak aynı öğelerdir, ancak istemediğiniz durumlarda geçişler oluşturabilirsiniz.

Örneğin, ana video yerleştirmesine view-transition-name verilebilir:

.full-embed {
  view-transition-name: full-embed;
}

Daha sonra, küçük resim tıklandığında yalnızca geçiş süresince aynı view-transition-name değeri verilebilir:

thumbnail.onclick = async () => {
  thumbnail.style.viewTransitionName = 'full-embed';

  document.startViewTransition(() => {
    thumbnail.style.viewTransitionName = '';
    updateTheDOMSomehow();
  });
};

Sonuç:

Bir öğe diğerine geçiş yapıyor. Minimal demo. Kaynak.

Küçük resim şimdi ana resme geçiş yapar. Bunlar kavramsal olarak (ve kelime anlamıyla) farklı öğeler olsa da, aynı view-transition-name öğesini paylaştıklarından geçiş API'si bunları aynı şey olarak ele alır.

Bu geçişin gerçek kodu, önceki örnekten biraz daha karmaşıktır. Çünkü bu kod, küçük resim sayfasına geri geçişi de gerçekleştirir. Eksiksiz uygulama için kaynağa bakın.


Özel giriş ve çıkış geçişleri

Şu örneğe bakın:

Kenar çubuğuna girme ve kenar çubuğundan çıkma. Minimal demo. Kaynak.

Kenar çubuğu geçişin bir parçasıdır:

.sidebar {
  view-transition-name: sidebar;
}

Ancak önceki örnekteki başlığın aksine, kenar çubuğu tüm sayfalarda görünmez. Her iki durumda da kenar çubuğu varsa, geçiş sözde öğeleri şöyle görünür:

::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
   └─ ::view-transition-image-pair(sidebar)
      ├─ ::view-transition-old(sidebar)
      └─ ::view-transition-new(sidebar)

Bununla birlikte, kenar çubuğu yalnızca yeni sayfadaysa ::view-transition-old(sidebar) sözde öğesi burada yer almaz. Kenar çubuğu için 'eski' resim olmadığından, resim çiftinde yalnızca ::view-transition-new(sidebar) bulunur. Benzer şekilde, kenar çubuğu yalnızca eski sayfadaysa resim çiftinde yalnızca bir ::view-transition-old(sidebar) bulunur.

Önceki demoda kenar çubuğunun geçişi, her iki durumda da giriş, çıkış veya her iki durumda da yapılmasına bağlı olarak değişir. Sağdan içeri kayarak girip yavaşça girer, sağa doğru kayarak çıkar ve her iki durumda da sabit kalır.

Belirli giriş ve çıkış geçişleri oluşturmak amacıyla, resim çiftindeki tek alt öğe olduğunda eski veya yeni sözde öğeleri hedeflemek için :only-child sözde sınıfı kullanabilirsiniz:

/* Entry transition */
::view-transition-new(sidebar):only-child {
  animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Exit transition */
::view-transition-old(sidebar):only-child {
  animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}

Bu durumda, varsayılan ayar mükemmel olduğundan kenar çubuğu her iki durumda da mevcut olduğunda belirli bir geçiş yoktur.

Eş zamansız DOM güncellemeleri ve içerik bekleniyor

.startViewTransition() öğesine iletilen geri çağırma, eşzamansız DOM güncellemelerine olanak tanıyan ve önemli içeriğin hazır olmasını bekleyen bir söz verebilir.

document.startViewTransition(async () => {
  await something;
  await updateTheDOMSomehow();
  await somethingElse;
});

Sözü yerine getirilene kadar geçiş başlamaz. Bu süre zarfında sayfa donduğundan, buradaki gecikmeler en az düzeyde tutulmalıdır. Özellikle ağ getirme işlemleri, .startViewTransition() geri çağırma işleminin bir parçası olarak yapılmak yerine sayfa hâlâ tamamen etkileşimliyken .startViewTransition() çağrılmadan önce yapılmalıdır.

Resimlerin veya yazı tiplerinin hazır olmasını beklemeye karar verirseniz, agresif bir zaman aşımı kullandığınızdan emin olun:

const wait = ms => new Promise(r => setTimeout(r, ms));

document.startViewTransition(async () => {
  updateTheDOMSomehow();

  // Pause for up to 100ms for fonts to be ready:
  await Promise.race([document.fonts.ready, wait(100)]);
});

Ancak bazı durumlarda, gecikmeyi tamamen önlemek ve halihazırda sahip olduğunuz içeriği kullanmak daha iyidir.


Mevcut içerikten en iyi şekilde yararlanın

Küçük resmin daha büyük bir resme dönüştüğü durumlarda:

Küçük resimden daha büyük bir resme geçiş. Demo sitesini deneyin.

Varsayılan geçiş, çapraz geçiştir. Bu, küçük resmin henüz yüklenmemiş tam bir resimle çapraz olarak şeffaflaşabileceği anlamına gelir.

Bunu halletmenin bir yolu, geçişi başlatmadan önce resmin tamamının yüklenmesini beklemektir. İdeal olarak bu, .startViewTransition() çağrısından önce yapılır. Böylece, sayfa etkileşimli durumda kalır ve kullanıcıya öğelerin yüklenmekte olduğunu belirten bir döner simge gösterilebilir. Ancak bu durumda daha iyi bir yol var:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
}

Şimdi küçük resim kaybolmuyor, yalnızca tam resmin altında duruyor. Yani, yeni görünüm yüklenmediyse küçük resim geçiş boyunca görünür. Bu, geçişin hemen başlayabileceği ve kendi kendine tam resmin yüklenebileceği anlamına gelir.

Yeni görünümde şeffaflık öne çıkarılıyorsa bu işe yaramaz. Ancak bu örnekte bunun böyle olmadığını biliyoruz ve dolayısıyla bu optimizasyonu yapabiliriz.

En boy oranındaki değişiklikleri işleme

Kolay bir şekilde, şimdiye kadarki tüm geçişler aynı en boy oranına sahip öğelerde gerçekleşmiştir, ancak bu her zaman yaşanmayabilir. Küçük resim 1:1, ana resim 16:9 ise ne olur?

En boy oranı değiştiğinde bir öğe diğerine geçiş yapıyor. Minimal demo. Kaynak.

Varsayılan geçişte grup, önceki boyuttan sonraki boyuta animasyonu yapar. Eski ve yeni görünümler, grubun% 100 genişliğindedir ve otomatik yüksekliktir. Yani en boy oranlarını grubun boyutundan bağımsız olarak korurlar.

Bu iyi bir varsayılan ayardır ancak bu durumda istenen şey değildir. Bu durumda:

::view-transition-old(full-embed),
::view-transition-new(full-embed) {
  /* Prevent the default animation,
  so both views remain opacity:1 throughout the transition */
  animation: none;
  /* Use normal blending,
  so the new view sits on top and obscures the old view */
  mix-blend-mode: normal;
  /* Make the height the same as the group,
  meaning the view size might not match its aspect-ratio. */
  height: 100%;
  /* Clip any overflow of the view */
  overflow: clip;
}

/* The old view is the thumbnail */
::view-transition-old(full-embed) {
  /* Maintain the aspect ratio of the view,
  by shrinking it to fit within the bounds of the element */
  object-fit: contain;
}

/* The new view is the full image */
::view-transition-new(full-embed) {
  /* Maintain the aspect ratio of the view,
  by growing it to cover the bounds of the element */
  object-fit: cover;
}

Yani, küçük resim genişlik genişledikçe öğenin ortasında kalır, ancak tam resim 1:1'den 16:9'a geçerken "kırpmayı kaldırır".

Daha ayrıntılı bilgi için (Geçişleri görüntüleme: En boy oranı değişikliklerini işleme)(https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/) başlıklı makaleyi inceleyin


Farklı cihaz durumlarının geçişlerini değiştirmek için medya sorgularını kullanma

Mobilde ve masaüstünde farklı geçişler kullanmak isteyebilirsiniz. Örneğin, mobil cihazda yandan tam bir slayt, masaüstünde ise daha ince bir slayt gösterilen bu örnek:

Bir öğe diğerine geçiş yapıyor. Minimal demo. Kaynak.

Bu, normal medya sorguları kullanılarak elde edilebilir:

/* Transitions for mobile */
::view-transition-old(root) {
  animation: 300ms ease-out both full-slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out both full-slide-from-right;
}

@media (min-width: 500px) {
  /* Overrides for larger displays.
  This is the shared axis transition from earlier in the article. */
  ::view-transition-old(root) {
    animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
  }

  ::view-transition-new(root) {
    animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
      300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
  }
}

Eşleşen medya sorgularına bağlı olarak view-transition-name atayacağınız öğeleri değiştirmek de isteyebilirsiniz.


"Azaltılmış hareket" tercihine tepki verme

Kullanıcılar, işletim sistemlerinde daha az hareket tercih ettiklerini ve bu tercihin CSS'de gösterildiğini belirtebilir.

Şu kullanıcıların geçiş yapmasını engelleyebilirsiniz:

@media (prefers-reduced-motion) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Bununla birlikte, "azaltılmış hareket" tercihi, kullanıcının hareketsiz olduğu anlamına gelmez. Önceki snippet yerine, öğeler ve veri akışı arasındaki ilişkiyi ifade eden, daha sade bir animasyon seçebilirsiniz.


Görünüm geçiş türleriyle birden çok görünüm geçiş stilini yönetme

Bazen bir görünümden diğerine geçişin özel olarak uyarlanmış bir geçişi olmalıdır. Örneğin, sayfalandırma sırasında sonraki veya önceki sayfaya giderken içeriği farklı bir yönde kaydırmak isteyebilirsiniz. Bu durum, sıralamadan daha üst veya daha alt bir sayfaya gitmenize bağlıdır.

Sayfalara ayırma demosunun kaydı. Gittiğiniz sayfaya bağlı olarak farklı geçişler kullanır.

Bunun için, bir etkin görünüm geçişine bir veya daha fazla tür atamanıza olanak tanıyan görünüm geçiş türlerini kullanabilirsiniz. Örneğin, sayfalandırma sırasında daha yüksek bir sayfaya geçiş yaparken forwards türünü, daha düşük bir sayfaya giderken backwards türünü kullanın. Bu türler yalnızca bir geçiş yakalanırken veya gerçekleştirilirken etkindir ve her tür, farklı animasyonlar kullanacak şekilde CSS ile özelleştirilebilir.

Aynı doküman görüntüleme geçişinde türleri kullanmak için types yöntemini startViewTransition yöntemine geçirirsiniz. Buna izin vermek için document.startViewTransition, bir nesneyi de kabul eder: update, DOM'u güncelleyen geri çağırma işlevi, types ise türlere sahip bir dizidir.

const direction = determineBackwardsOrForwards();

const t = document.startViewTransition({
  update: updateTheDOMSomehow,
  types: ['slide', direction],
});

Bu türlere yanıt vermek için :active-view-transition-type() seçiciyi kullanın. Hedeflemek istediğiniz type öğesini seçiciye iletin. Böylece, birden fazla görünüm geçişinin stillerini, bir tanesinin bildirimleri diğerinin bildirimlerine müdahale etmeden, birbirinden ayrı tutabilirsiniz.

Türler yalnızca geçiş yakalanırken veya gerçekleştirilirken geçerli olduğundan, bir öğede yalnızca bu tür görünüm geçişinde view-transition-name ayarlamak (veya ayarı kaldırmak) için seçiciyi kullanabilirsiniz.

/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
  :root {
    view-transition-name: none;
  }
  article {
    view-transition-name: content;
  }
  .pagination {
    view-transition-name: pagination;
  }
}

/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-left;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-right;
  }
}

/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-right;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-left;
  }
}

/* Animation styles for reload type only (using the default root snapshot) */
html:active-view-transition-type(reload) {
  &::view-transition-old(root) {
    animation-name: fade-out, scale-down;
  }
  &::view-transition-new(root) {
    animation-delay: 0.25s;
    animation-name: fade-in, scale-up;
  }
}

Aşağıdaki sayfalara ayırma demosunda, sayfa içerikleri, gittiğiniz sayfa numarasına göre ileriye veya geriye doğru kaydırılır. Türler, tıklanarak document.startViewTransition ürününe iletilecekleri şekilde belirlenir.

Türü ne olursa olsun herhangi bir aktif görüntüleme geçişini hedeflemek için bunun yerine :active-view-transition sözde sınıf seçiciyi kullanabilirsiniz.

html:active-view-transition {
    …
}

Görünüm geçiş kökünde bir sınıf adıyla birden çok görünüm geçiş stilini işleyin

Bazen belirli bir görünüm türünden diğerine geçişin özel olarak uyarlanmış bir geçişi olmalıdır. Ya da 'geri' gezinme, 'ileri' gezinmeden farklı olmalıdır.

"Geri" giderken farklı geçişler. Minimal demo. Kaynak.

Geçiş türlerinden önce, bu destek yazışmalarını ele almak için geçici olarak geçiş köküne bir sınıf adı belirlemek gerekiyordu. document.startViewTransition çağrılırken bu geçiş kökü, JavaScript'te document.documentElement kullanılarak erişilebilen <html> öğesidir:

if (isBackNavigation) {
  document.documentElement.classList.add('back-transition');
}

const transition = document.startViewTransition(() =>
  updateTheDOMSomehow(data)
);

try {
  await transition.finished;
} finally {
  document.documentElement.classList.remove('back-transition');
}

Bu örnekte, geçiş tamamlandıktan sonra sınıfların kaldırılması için transition.finished kullanılmıştır. Bu, geçiş, son durumuna ulaştığında kesinleşen bir vaadidir. Bu nesnenin diğer özellikleri API referansında ele alınmıştır.

Artık geçişi değiştirmek için bu sınıf adını CSS'de kullanabilirsiniz:

/* 'Forward' transitions */
::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
      cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
  animation-name: fade-out, slide-to-right;
}

.back-transition::view-transition-new(root) {
  animation-name: fade-in, slide-from-left;
}

Medya sorgularında olduğu gibi bu sınıfların varlığı, hangi öğelerin view-transition-name alacağını değiştirmek için de kullanılabilir.


Geçişleri diğer animasyonları dondurmadan çalıştırma

Video geçiş pozisyonunu gösteren bu demoya göz atın:

Video geçişi. Minimal demo. Kaynak.

Videoda yanlış bir şey gördünüz mü? Görmediyseniz de endişelenmeyin. Burada yavaşlamış:

Video geçişi, daha yavaş. Minimal demo. Kaynak.

Geçiş sırasında video donar, ardından videonun oynatılan sürümü kaybolur. Bunun nedeni, ::view-transition-old(video) öğesinin eski görünümün ekran görüntüsü, ::view-transition-new(video) biriminin ise yeni görünümün canlı bir resmi olmasıdır.

Bunu düzeltebilirsiniz, ancak önce kendinize bunun düzeltmeye değip değmeyeceğini sorun. Geçiş normal hızında gerçekleşirken 'sorun' sorununu görmediyseniz, onu değiştirmem gerekmez.

Sorunu gerçekten düzeltmek istiyorsanız ::view-transition-old(video) göstermeyin; doğrudan ::view-transition-new(video) öğesine geçin. Bunu, varsayılan stilleri ve animasyonları geçersiz kılarak yapabilirsiniz:

::view-transition-old(video) {
  /* Don't show the frozen old view */
  display: none;
}

::view-transition-new(video) {
  /* Don't fade the new view in */
  animation: none;
}

Hepsi bu kadar!

Video geçişi, daha yavaş. Minimal demo. Kaynak.

Video şimdi geçiş boyunca oynatılır.


JavaScript ile animasyon uygulama

Şu ana kadar tüm geçişler CSS kullanılarak tanımlanmıştır, ancak bazen CSS yeterli olmayabilir:

Daire geçişi. Minimal demo. Kaynak.

Bu geçişin bazı kısmenleri tek başına CSS ile gerçekleştirilemez:

  • Animasyon, tıklama konumundan başlar.
  • Animasyon, dairenin en uzak köşeye kadar bir yarıçapına sahip olmasıyla sona erer. Yine de bunun gelecekte CSS ile mümkün olacağını umuyoruz.

Neyse ki Web Animation API'yi kullanarak geçişler oluşturabilirsiniz.

let lastClick;
addEventListener('click', event => (lastClick = event));

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // Get the click position, or fallback to the middle of the screen
  const x = lastClick?.clientX ?? innerWidth / 2;
  const y = lastClick?.clientY ?? innerHeight / 2;
  // Get the distance to the furthest corner
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  );

  // With a transition:
  const transition = document.startViewTransition(() => {
    updateTheDOMSomehow(data);
  });

  // Wait for the pseudo-elements to be created:
  transition.ready.then(() => {
    // Animate the root's new view
    document.documentElement.animate(
      {
        clipPath: [
          `circle(0 at ${x}px ${y}px)`,
          `circle(${endRadius}px at ${x}px ${y}px)`,
        ],
      },
      {
        duration: 500,
        easing: 'ease-in',
        // Specify which pseudo-element to animate
        pseudoElement: '::view-transition-new(root)',
      }
    );
  });
}

Bu örnekte, sözde geçiş öğeleri başarıyla oluşturulduktan sonra çözümlenen transition.ready vaadi kullanılmıştır. Bu nesnenin diğer özellikleri API referansında ele alınmıştır.


Bir geliştirme olarak geçişler

View Transition API, bir DOM değişikliğini "sarmalamak" ve bununla ilgili bir geçiş oluşturmak için tasarlanmıştır. Bununla birlikte, DOM değişikliği başarılı olsa da geçiş başarısız olursa geçiş, bir geliştirme olarak kabul edilmelidir. İdeal olarak geçiş başarısız olmamalıdır, ancak böyle bir durumda kullanıcı deneyiminin geri kalanı bozulmaz.

Geçişleri bir geliştirme olarak değerlendirmek için geçiş sözlerini, geçişin başarısız olması durumunda uygulamanızın iptal etmesine yol açacak şekilde kullanmamaya dikkat edin.

Yapılmaması gerekenler
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

  const transition = document.startViewTransition(async () => {
    await updateTheDOM(data);
  });

  await transition.ready;

  document.documentElement.animate(
    {
      clipPath: [`inset(50%)`, `inset(0)`],
    },
    {
      duration: 500,
      easing: 'ease-in',
      pseudoElement: '::view-transition-new(root)',
    }
  );
}

Bu örnekteki sorun, geçişin ready durumuna ulaşamaması durumunda switchView() ürününün reddedileceğidir, ancak bu, görünümün değiştirilemediği anlamına gelmez. DOM başarıyla güncellenmiş olabilir ancak kopya view-transition-name'ler olduğu için geçiş atlandı.

Bunun yerine:

Yapmanız gerekenler:
async function switchView(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    await updateTheDOM(data);
    return;
  }

  const transition = document.startViewTransition(async () => {
    await updateTheDOM(data);
  });

  animateFromMiddle(transition);

  await transition.updateCallbackDone;
}

async function animateFromMiddle(transition) {
  try {
    await transition.ready;

    document.documentElement.animate(
      {
        clipPath: [`inset(50%)`, `inset(0)`],
      },
      {
        duration: 500,
        easing: 'ease-in',
        pseudoElement: '::view-transition-new(root)',
      }
    );
  } catch (err) {
    // You might want to log this error, but it shouldn't break the app
  }
}

Bu örnekte, DOM güncellemesini beklemek ve güncelleme başarısız olursa reddetmek için transition.updateCallbackDone kullanılır. Geçiş başarısız olursa switchView artık reddetmez. DOM güncellemesi tamamlandığında çözümlenir, başarısız olursa reddedilir.

Yeni görünüm "yerleştirildiğinde" switchView tarafından çözümlenmesini istiyorsanız (örneğin, tüm animasyonlu geçişler tamamlandığında veya sonuna atlandığında) transition.updateCallbackDone değerini transition.finished ile değiştirin.


Çoklu dolgu değil ama...

Bu, çoklu dolgu için kolay bir özellik değildir. Ancak bu yardımcı işlev, görünüm geçişlerini desteklemeyen tarayıcılarda işleri çok daha kolay hale getirir:

function transitionHelper({
  skipTransition = false,
  types = [],
  update,
}) {

  const unsupported = (error) => {
    const updateCallbackDone = Promise.resolve(update()).then(() => {});

    return {
      ready: Promise.reject(Error(error)),
      updateCallbackDone,
      finished: updateCallbackDone,
      skipTransition: () => {},
      types,
    };
  }

  if (skipTransition || !document.startViewTransition) {
    return unsupported('View Transitions are not supported in this browser');
  }

  try {
    const transition = document.startViewTransition({
      update,
      types,
    });

    return transition;
  } catch (e) {
    return unsupported('View Transitions with types are not supported in this browser');
  }
}

Ve şu şekilde kullanılabilir:

function spaNavigate(data) {
  const types = isBackNavigation ? ['back-transition'] : [];

  const transition = transitionHelper({
    update() {
      updateTheDOMSomehow(data);
    },
    types,
  });

  // …
}

Görüntüleme geçişlerini desteklemeyen tarayıcılarda, updateDOM çağrılacaktır, ancak animasyonlu bir geçiş olmayacaktır.

Geçiş sırasında <html> ürününe eklemek için classNames sağlayarak gezinme türüne bağlı olarak geçişi değiştirmeyi kolaylaştırabilirsiniz.

Ayrıca, görüntüleme geçişlerini destekleyen tarayıcılarda bile animasyon istemiyorsanız true öğesini skipTransition öğesine de iletebilirsiniz. Sitenizde geçişlerin devre dışı bırakılması yönünde bir kullanıcı tercihi varsa bu yararlı olur.


Çerçevelerle çalışma

DOM değişikliklerini soyutlayan bir kitaplık veya çerçeveyle çalışıyorsanız işin zor kısmı, DOM değişikliğinin ne zaman tamamlandığını bilmektir. Yukarıdaki yardımcıyı çeşitli çerçevelerde kullanan bir dizi örneği burada bulabilirsiniz.

  • Tepki: Buradaki anahtar, bir dizi durum değişikliğini eşzamanlı olarak uygulayan flushSync'tır. Evet, söz konusu API'nın kullanımıyla ilgili önemli bir uyarı var ama Dan Abramov bu durumda uygun olacağı konusunda bana güvence veriyor. React ve eşzamansız kodda her zaman olduğu gibi, startViewTransition tarafından döndürülen çeşitli vaatleri kullanırken kodunuzun doğru durumda çalıştığından emin olun.
  • Vue.js: Burada anahtar olan nextTick, DOM güncellendikten sonra yerine getirilir.
  • Svelte: Vue'ya çok benzer, ancak bir sonraki değişikliği bekleme yöntemi tick.
  • Lit: Buradaki anahtar, bileşenlerde this.updateComplete sözü verilendir ve DOM güncellendikten sonra yerine getirilir.
  • Açısal: Buradaki anahtar, bekleyen DOM değişikliklerini temizleyen applicationRef.tick'tir. Angular sürüm 17'den itibaren @angular/router ile birlikte gelen withViewTransitions özelliğini kullanabilirsiniz.

API referansı

const viewTransition = document.startViewTransition(update)

Yeni bir ViewTransition başlatın.

update, belgenin mevcut durumu yakalandıktan sonra çağrılan bir işlevdir.

Ardından, updateCallback tarafından verilen söz yerine getirildiğinde geçiş işlemi bir sonraki karede başlar. updateCallback tarafından verilen söz reddedilirse geçiş iptal edilir.

const viewTransition = document.startViewTransition({ update, types })

Belirtilen türlerle yeni bir ViewTransition başlatın

Belgenin mevcut durumu yakalandıktan sonra update çağrılır.

types, geçişi yakalarken veya gerçekleştirirken kullanılacak etkin geçiş türlerini ayarlar. Başlangıçta boştur. Daha fazla bilgi için daha aşağıda bulunan viewTransition.types konusuna bakın.

ViewTransition örneğinin üyeleri:

viewTransition.updateCallbackDone

updateCallback tarafından verilen söz yerine getirildiğinde söz konusu vaat yerine getirilir veya reddedildiğinde söz konusu taahhüt reddedilir.

View Transition API, bir DOM değişikliğini sarmalar ve bir geçiş oluşturur. Ancak bazen geçiş animasyonunun başarısı veya başarısızlığı önemsenmez. Tek yapmanız gereken DOM değişikliğinin olup olmadığını ve ne zaman olacağını bilmektir. updateCallbackDone bu kullanım alanı içindir.

viewTransition.ready

Geçişe ilişkin sözde öğeler oluşturulduğunda ve animasyon başlamak üzereyken yerine getirilecek bir taahhüt.

Geçişin başlayamazsa bunu reddeder. Bunun nedeni, yinelenen view-transition-name'ler gibi yanlış yapılandırma veya updateCallback ürününün reddedilen bir söz döndürmesi olabilir.

Bu, JavaScript ile geçiş sözde öğelerinin animasyonunu yaparken işe yarar.

viewTransition.finished

Son durum tamamen görünür hale geldiğinde ve kullanıcı tarafından etkileşimli olarak sunulduğunda yerine getirilen bir taahhüt.

Yalnızca updateCallback reddedilen bir sözü döndürürse reddeder. Bu durum, bitiş durumunun oluşturulmadığını gösterir.

Aksi halde, bir geçiş başlamazsa veya geçiş sırasında atlanırsa bitiş durumuna yine de ulaşılmış olur. Dolayısıyla finished, isteği karşılar.

viewTransition.types

Aktif görüntüleme geçiş türlerini barındıran Set benzeri bir nesne. Girişleri değiştirmek için örnek yöntemlerini kullanın: clear(), add() ve delete().

CSS'de belirli bir türe yanıt vermek için geçiş kökündeki :active-view-transition-type(type) sözde sınıf seçiciyi kullanın.

Görünüm geçişi tamamlandığında türler otomatik olarak temizlenir.

viewTransition.skipTransition()

Geçişin animasyon kısmını atlayın.

DOM değişikliği geçişten ayrı olduğu için bu işlem updateCallback çağrısını atlamaz.


Varsayılan stil ve geçiş referansı

::view-transition
Görüntü alanını dolduran ve her bir ::view-transition-group öğesini içeren kök sözde öğe.
::view-transition-group

Kesinlikle konumlandı.

"Önce" ve "sonra" durumları arasında width ve height geçişlerini yapar.

transform adlı "önce" ve "sonra" görüntü alanı-boşluk dörtlüsü arasında geçiş yapar.

::view-transition-image-pair

Mutlaka grubu dolduracak biçimde konumlandırılmış.

mix-blend-mode öğesinin eski ve yeni görünümler üzerindeki etkisini sınırlandırmak için isolation: isolate özelliği vardır.

::view-transition-new ve ::view-transition-old

Mutlaka, sarmalayıcının sol üst tarafına konumlandırılır.

Grup genişliğinin% 100'ünü doldurur, ancak otomatik bir yüksekliğe sahip olduğundan grubu doldurmak yerine en boy oranını korur.

Gerçek çapraz geçişe olanak tanımak için mix-blend-mode: plus-lighter özelliği vardır.

Eski görünüm opacity: 1 ürününden opacity: 0 görünümüne geçiş yapar. Yeni görünüm opacity: 0 ürününden opacity: 1 görünümüne geçer.


Geri bildirim

Geliştiricilerin geri bildirimleri her zaman çok değerlidir. Bunu yapmak için öneriler ve sorularla GitHub'da CSS Çalışma Grubu'na sorun bildirin. Sorununuzun önüne [css-view-transitions] ekleyin.

Bir hatayla karşılaşırsanız bunun yerine Chromium hatası bildiriminde bulunun.