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

Tek bir dokümanda görüntüleme geçişinin çalıştırılmasına 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ünümü geçişleri, Chrome 111'den itibaren Chrome'da desteklenmektedir.

Aynı doküman görüntüleme geçişini tetiklemek için document.startViewTransition komutunu çağırı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ı kurulduğunda, tarayıcı üzerinde view-transition-name CSS özelliği belirtilmiş tüm öğelerin anlık görüntülerini otomatik olarak yakalar.

Ardından, DOM'yi güncelleyen, iletilen geri çağırmayı yürütür 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 durumlardan alınan anlık görüntüler, eski konumlarından ve boyutlarından yeni konumlarına sorunsuz bir şekilde geçiş yapar. Bu süreçteki içerikler kesiş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'ye güzel bir giriş niteliğindedir:

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'yi yeni duruma değiştirir. Bunu istediğiniz gibi yapabilirsiniz. Örneğin, öğe ekleyip kaldırabilir, sınıf adlarını veya stilleri değiştirebilirsiniz.

Tam da bu şekilde sayfalar çapraz geçiş yapıyor:

Varsayılan çapraz geçiş. En az demo. Kaynak.

Tamam, çapraz geçiş o kadar da etkileyici değil. Neyse ki geçişler özelleştirilebilir, ancak önce bu temel çapraz geçişin 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. Buna anlık görüntü almak da dahildir.

İşlem tamamlandığında, .startViewTransition() işlevine 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 sözde öğ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, sayfadaki diğer her şeyin üzerinde bir yer paylaşımında yer alır. Bu, geçiş için bir arka plan rengi ayarlamak isterseniz 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ı bir temsilidir. Her ikisi de CSS "değiştirilen içerik" olarak oluşturulur (<img> gibi).

Eski görünüm opacity: 1 ile opacity: 0 aralığında animasyonlu olarak gösterilirken, yeni görünüm opacity: 0 ile opacity: 1 aralığında animasyon ekleyerek çapraz geçiş oluşturuyor.

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, bunları mevcut CSS animasyon özelliklerini kullanarak değiştirebilirsiniz. Örneğin:

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

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

Uzun çapraz geçiş. En az demo. Kaynak.

Tamam, yine de 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. En az demo. Kaynak.

Birden fazla öğenin geçişini yapma

Önceki demoda, paylaşılan eksen geçişinde sayfanın tamamı yer almaktadır. Bu yöntem, sayfanın büyük bir kısmında işe yarar, ancak başlık tekrar içeri kayarak uzaklaştığından, başlık için pek doğru görünmüyor.

Bu durumu önlemek için sayfanın geri kalanından üstbilgiyi ayıklayabilir, böylece 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ı yoktur). Öğeyi geçiş boyunca benzersiz bir şekilde tanımlamak için kullanılır.

Bunun sonucu da şudur:

Sabit başlığa sahip paylaşılan eksen geçişi. En az demo. Kaynak.

Şimdi başlık yerinde duruyor ve çapraz geçiş yapıyor.

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)

Artık iki geçiş grubu vardır. Biri başlık, diğeri geri kalanı için. Bunlar CSS ile bağımsız olarak hedeflenebilir ve farklı geçişlere tabi tutulabilir. Ancak bu örnekte main-header, çapraz geçiş olan varsayılan geçişte kalmıştır.

Tamam. Varsayılan geçiş yalnızca çapraz geçişten ibaret değildir. ::view-transition-group ürününde de geçiş yapılır:

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

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

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

fit-content kullanılır. Böylece öğe, kalan genişliğe uzatmak yerine metnin boyutuyla kapsanır. Bu olmadan, geri oku her iki sayfada aynı boyuta sahip olmak yerine başlık metin öğesinin boyutunu küçültür.

Şimdi üzerinde çalışacağımız üç bölüm var:

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

Yine varsayılan değerleri kullanın:

Kayan başlık metni. En az demo. Kaynak.

Şimdi başlık metni, geri düğmesine yer açmak için kaydırılıyor.


view-transition-class ile birden fazla sözde öğeyi aynı şekilde hareket ettirin

Tarayıcı Desteği

  • 125
  • 125
  • x
  • x

Birçok kartın olduğu bir görüntüleme geçişinizin olduğunu ve aynı zamanda sayfada bir başlık bulunduğunu varsayalım. Başlık hariç tüm kartlara animasyon eklemek 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 mi var? Bu, yazmanız gereken 20 seçici demektir. Yeni bir öğe mi ekliyorsunuz? Ardından, animasyon stillerini uygulayan seçiciyi büyütmeniz gerekir. Tam olarak ölçeklenebilir değil.

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ği, önceki CSS snippet'inden yararlanır. Yeni eklenenler de dahil olmak üzere tüm kartlara aynı zamanlama tek bir seçiciyle uygulanır: html::view-transition-group(.card).

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

Hata ayıklama geçişleri

Görüntüleme geçişleri CSS animasyonlarının üzerine oluşturulduğundan Chrome Geliştirici Araçları'ndaki Animasyonlar paneli, geçişlerde hata ayıklamak için mükemmel bir seçenektir.

Animasyonlar panelini kullanarak bir sonraki animasyonu duraklatabilir, ardından animasyonda ileri geri oynatabilirsiniz. Bu işlem sırasında, geçiş sözde öğ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ıktaki metin için ayrı geçiş öğeleri oluşturmak üzere view-transition-name işlevini kullandık. Bunlar, kavram olarak DOM değişikliğinden önce ve sonra aynı öğedir ancak böyle olmadığı durumlarda geçişler oluşturabilirsiniz.

Örneğin, yerleştirilmiş ana video öğesine view-transition-name verilebilir:

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

Ardından, 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. En az demo. Kaynak.

Küçük resim, ana resme geçiş yapar. Kavramsal (ve kelimenin anlamıyla) farklı öğeler olsalar da aynı view-transition-name öğesini paylaştıkları için geçiş API'si bunları aynı şekilde 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ği inceleyin:

Kenar çubuğuna girme ve kenar çubuğundan çıkılıyor. En az demo. Kaynak.

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

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

Ancak, önceki örnekte verilen üstbilgiden farklı olarak 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 orada bulunmaz. 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) olacaktır.

Önceki demoda kenar çubuğu, her iki durumda da giriş, çıkış veya mevcut olmasına bağlı olarak farklı şekilde geçiş yapar. Sağdan içeri doğru kayarak girer, içeri doğru sönerek girer, sağa doğru kaydıp sönerek çıkar ve her iki durumda da bulunduğu yerde 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 özelliğini 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() işlevine 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 döndürebilir.

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

Geçiş, taahhüt yerine getirilene kadar başlamaz. Bu süre zarfında sayfa donduğu için buradaki gecikmeler minimum düzeyde tutulmalıdır. Ağ getirme işlemleri, .startViewTransition() geri çağırma işleminin bir parçası olarak değil, sayfa hâlâ tamamen etkileşimli durumdayken .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 gecikmeden tamamen kaçınmak ve sahip olduğunuz içeriği kullanmak daha iyidir.


Elinizdeki içeriklerden en iyi şekilde yararlanın

Küçük resmin daha büyük bir resme geçirilmesi durumunda:

Daha büyük bir görsele geçiş yapan küçük resim. Demo sitesini deneyin.

Varsayılan geçiş çapraz geçiştir. Diğer bir deyişle, küçük resim henüz yüklenmemiş tam resimle çapraz geçiş yapabilir.

Bunu çözmenin bir yolu, geçişi başlatmadan önce resmin tamamının yüklenmesini beklemektir. İdeal olarak bu, .startViewTransition() çağrılmadan önce yapılır. Böylece sayfa etkileşimli kalır ve kullanıcıya öğelerin yüklendiğini göstermek için bir döner simge gösterilebilir. Ancak bu durumda daha iyi bir yol vardır:

::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;
}

Artık küçük resim kaybolmaz, tam resmin altında kalır. Bu, yeni görünüm yüklenmediyse geçiş boyunca küçük resmin görünür olacağı anlamına gelir. Bu, geçişin hemen başlayabileceği ve resmin tamamının kendi süresinde yükleneceği anlamına gelir.

Yeni görünümde şeffaflık olsa bu yöntem işe yaramazdı. Ancak bu durumda öyle olmadığını biliyoruz. Dolayısıyla bu optimizasyonu yapabiliriz.

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

Şimdiye kadarki tüm geçişler aynı en boy oranına sahip öğelerde yapıldı, ancak bu her zaman geçerli olmayabilir. Küçük resim 1:1 ve ana resim 16:9 ise ne olur?

En boy oranı değişen bir öğe diğer öğeye geçiş yapıyor. En az demo. Kaynak.

Varsayılan geçişte, grup önceki boyuttan sonraki boyuta geçiş yapar. Eski ve yeni görünümler, grubun% 100 genişliği ve otomatik yüksekliktir. Yani grubun boyutu ne olursa olsun en boy oranlarını korurlar.

Bu iyi bir varsayılan değerdir ancak bu durumda istenen bu 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;
}

Bu, genişlik genişledikçe küçük resmin öğenin ortasında kalacağı, ancak 1:1'den 16:9'a geçiş yaparken resmin "kıvrılmasını kaldır" anlamına gelir.

Daha ayrıntılı bilgi için Geçişleri görüntüleme: En boy oranı değişikliklerini işleme başlıklı makaleyi inceleyin.


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

Mobilde ve masaüstünde farklı geçişler kullanmak isteyebilirsiniz. Örneğin, mobilde tam slaytın yan tarafından tam slayt gösterildiği, masaüstünde ise daha küçük bir slaytın gösterildiği şu örnekteki gibi:

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

Bu, normal medya sorguları kullanılarak yapılabilir:

/* 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 atadığınız öğeleri değiştirmek de isteyebilirsiniz.


"Azaltılmış hareket" tercihine tepki ver

Kullanıcılar, işletim sistemlerinde daha az hareketi tercih ettiklerini belirtebilir ve bu tercih CSS'de gösterilir.

Bu kullanıcıların geçişlerini önlemeyi seçebilirsiniz:

@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 hareket görmek istemediği anlamına gelmez. Önceki snippet yerine, öğeler arasındaki ilişkiyi ve veri akışını yansıtan, daha incelikli bir animasyon seçebilirsiniz.


Görünüm geçiş türleriyle birden fazla görünüm geçiş stilini işleme

Bazen belirli bir görünümden diğerine geçişin özel olarak uyarlanmış bir geçişi olmalıdır. Örneğin, sayfalara ayırma sırasına göre sonraki veya önceki sayfaya giderken, dizideki daha üst veya daha alt bir sayfaya mı gittiğinize bağlı olarak içerikleri farklı bir yönde kaydırmak isteyebilirsiniz.

Sayfaları numaralandırma demosu kaydı. Gittiğiniz sayfaya bağlı olarak farklı geçişler kullanır.

Bunun için, aktif görüntüleme 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, sayfalara ayırma sırasında daha üst bir sayfaya geçerken forwards türünü, daha alttaki 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 kullanmak için CSS aracılığıyla özelleştirilebilir.

Aynı doküman görünümü 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'yi güncelleyen geri çağırma işlevidir, types ise türlere sahip bir dizidir.

const direction = determineBackwardsOrForwards();

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

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

Türler yalnızca geçiş yakalanırken veya gerçekleştirilirken geçerli olduğundan, yalnızca bu türe sahip görüntüleme geçişinde bir öğede view-transition-name değerini ayarlamak (veya ayarını 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 sayfalandırma demosunda sayfa içerikleri, ziyaret ettiğiniz sayfa numarasına göre ileriye veya geriye doğru kaydırılır. Türler, document.startViewTransition içine geçirildikleri tıklamayla belirlenir.

Türden bağımsız olarak herhangi bir aktif görüntüleme geçişini hedeflemek için :active-view-transition sözde sınıf seçiciyi kullanabilirsiniz.

html:active-view-transition {
    …
}

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

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. En az demo. Kaynak.

Geçiş türlerinden önce, bu destek kayıtları yalnızca geçiş kökü için geçici bir sınıf adı belirlemekteydi. 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ı kaldırmak için transition.finished kullanılmıştır. Geçiş, bitiş durumuna ulaştığında sona erer. Bu nesnenin diğer özellikleri API referansında ele alınmıştır.

Artık bu sınıf adını CSS'nizde kullanarak geçişi değiştirebilirsiniz:

/* '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ır

Video geçiş konumuyla ilgili şu demoya göz atın:

Video geçişi. En az demo. Kaynak.

Bunda yanlış bir şey gördünüz mü? Aksi halde merak etmeyin. Burada yavaşlayabilir:

Video geçişi, daha yavaş. En az demo. Kaynak.

Geçiş sırasında video donmuş gibi görünür, ardından videonun oynatma sürümü yavaş yavaş başlar. Çünkü ::view-transition-old(video) görüntüsü eski görünümün ekran görüntüsü, ::view-transition-new(video) ise yeni görünümün canlı bir resmidir.

Bunu düzeltebilirsiniz ama önce kendinize düzeltmenin gerekip gerekmediğini sorun. Geçiş normal hızında oynarken 'sorun'u görmediyseniz, değiştirilmesi gerekmez.

Bu sorunu gerçekten düzeltmek istiyorsanız ::view-transition-old(video) öğesini 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ş. En az demo. Kaynak.

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


JavaScript ile Animasyon Oluşturma

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

Çevre geçişi. En az demo. Kaynak.

Bu geçişin bazı bölümleri, tek başına CSS ile gerçekleştirilemez:

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

Neyse ki Web Animasyonu API'sini 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, geçiş sözde öğeleri başarıyla oluşturulduktan sonra çözümlenen bir taahhüt olan transition.ready kullanılmaktadı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 bu değişiklik için bir geçiş oluşturmak üzere tasarlanmıştır. Ancak geçiş, bir geliştirme olarak değerlendirilmelidir. Örneğin, DOM değişikliği başarılı olup geçiş başarısız olursa uygulamanız "hata" durumuna geçmemelidir. İdeal olarak geçiş başarısız olmamalı, ancak başarısız olursa kullanıcı deneyiminin geri kalanını bozmamalıdır.

Geçişleri bir geliştirme olarak değerlendirmek için, geçiş vaatlerini, geçişin başarısız olması durumunda uygulamanızın bozulmasına neden olacak şekilde kullanmamaya özen gösterin.

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() öğesinin reddetmesidir. Ancak bu, görünümün değiştirilemediği anlamına gelmez. DOM başarıyla güncellenmiş olabilir ancak yinelenen view-transition-name öğeleri olduğundan 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 başarısız olursa reddetmek için transition.updateCallbackDone kullanılmıştır. switchView artık geçiş başarısız olursa reddetmez. DOM güncellemesi tamamlandığında çözümlenir ve başarısız olursa reddeder.

Yeni görünüm 'yerleştirildiğinde' (örneğin, animasyonlu geçiş tamamlandığında veya sonuna kadar atlandığında) switchView ürününün çözmesini istiyorsanız transition.updateCallbackDone öğesini transition.finished ile değiştirin.


Çoklu dolgu değil, ancak...

Bu, çoklu doldurma 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ılır, ancak animasyonlu bir geçiş olmaz.

Ayrıca, geçiş sırasında <html> öğesine eklenecek classNames sağlayabilirsiniz. Böylece, gezinme türüne bağlı olarak geçişi değiştirmeyi daha kolay hale getirebilirsiniz.

Görüntüleme geçişlerini destekleyen tarayıcılarda bile animasyon istemiyorsanız true öğesini skipTransition öğesine de iletebilirsiniz. Sitenizde geçişleri devre dışı bırakmaya yönelik 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. Aşağıda, yukarıdaki yardımcıyı kullanarak çeşitli çerçevelerde bir dizi örnek verilmiştir.


API referansı

const viewTransition = document.startViewTransition(update)

Yeni bir ViewTransition başlatın.

update, dokümanın mevcut durumu yakalandığında çağrılan bir işlevdir.

Ardından, updateCallback tarafından verilen söz yerine getirildiğinde geçiş bir sonraki karede başlar. updateCallback tarafından verilen taahhüt reddedilirse geçiş işlemi yarıda bırakılır.

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

Belirtilen türlerle yeni bir ViewTransition başlat

Dokümanın geçerli durumu yakalandığında update çağrılır.

types, geçişi kaydederken veya gerçekleştirirken geçiş için etkin türleri ayarlar. Başlangıçta boştur. Daha fazla bilgi için aşağıdaki viewTransition.types hükümlerine bakın.

ViewTransition örnek üyeleri:

viewTransition.updateCallbackDone

updateCallback tarafından verilen vaat yerine getirildiğinde yerine getirilecek veya reddedildiğinde reddedilecek bir vaattir.

View Transition API, bir DOM değişikliğini sarmalar ve bir geçiş oluşturur. Bununla birlikte, bazen geçiş animasyonunun başarılı veya başarısız olması sizin için önemli değildir. Sadece DOM değişikliğinin olup olmadığını ve ne zaman gerçekleşeceğini bilmek istersiniz. updateCallbackDone bu kullanım alanına uygundur.

viewTransition.ready

Geçişin sözde öğeleri oluşturulduğunda ve animasyon başlamak üzereyken gerçekleşen bir vaat.

Geçiş başlayamazsa işlemi reddeder. Bu durum, yinelenen view-transition-name öğeleri gibi yanlış yapılandırmadan veya updateCallback uygulamasının reddedilen bir vaadi döndürmesinden kaynaklanabilir.

Bu, JavaScript ile geçiş sözde öğelerini canlandırmak için yararlıdır.

viewTransition.finished

Son durum kullanıcı için tamamen görünür ve etkileşimli hale geldiğinde yerine getirilecek bir vaat.

Yalnızca updateCallback, son durumun oluşturulmadığını belirttiği için reddedilmiş bir taahhüt döndürürse reddedilir.

Aksi takdirde, geçiş başlayamazsa veya geçiş sırasında atlanırsa bitiş durumuna ulaşıldığı için finished isteği yerine getirir.

viewTransition.types

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

CSS'deki belirli bir türe yanıt vermek için geçiş kökünde :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 updateCallback çağrısı atlanmaz.


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

Mutlaka konumlandırıldı.

"Öncesi" ve "sonrası" durumları arasında width ve height geçişleri.

"Önce" ve "sonra" görüntü alanı-alan dörtgeni arasındaki transform geçişler.

::view-transition-image-pair

Mutlaka grubu dolduracak konumda.

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

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

Mutlaka sarmalayıcının sol üst tarafına yerleştirilmiş.

Grup genişliğinin% 100'ünü doldurur ancak otomatik yüksekliğe sahiptir, bu nedenle grubu doldurmak yerine en boy oranını korur.

Gerçek bir çapraz geçişe izin vermek için mix-blend-mode: plus-lighter içeriyor.

Eski görünüm opacity: 1 yerine opacity: 0. Yeni görünüm opacity: 0 yerine opacity: 1.


Geri bildirim

Geliştirici geri bildirimleri bizim için çok değerli. 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ı bildirin.