View Transitions API ile sorunsuz ve basit geçişler

Jake Archibald
Jake Archibald

Tarayıcı Desteği

  • 111
  • 111
  • x
  • x

Kaynak

View Transition API'si, iki durum arasında animasyonlu bir geçiş oluştururken tek bir adımda DOM'u değiştirmeyi kolaylaştırır. Chrome 111 ve sonraki sürümlerde kullanılabilir.

View Transition API ile oluşturulan geçişler. Demo sitesini deneyin – Chrome 111 ve sonraki sürümleri gerekir.

Bu özelliğe neden ihtiyacımız var?

Sayfa geçişleri hem güzel görünmekle kalmaz hem de akış yönünü iletir ve hangi öğelerin sayfadan sayfaya ilişkili olduğunu net bir şekilde gösterir. Bu işlemler veri getirme sırasında bile gerçekleşerek performansın daha hızlı algılanmasını sağlar.

Ancak web üzerinde CSS geçişleri, CSS animasyonları ve Web Animasyonu API'si gibi animasyon araçlarımız zaten var. Öyleyse öğeleri taşımak için neden yeni bir şeye ihtiyacımız var?

Gerçek şu ki, zaten sahip olduğumuz araçlarla bile durum geçişleri zordur.

Basit bir çapraz geçiş gibi bir durum bile her iki durumun da aynı anda var olmasını gerektirir. Bu durum, giden öğede ek etkileşimlerin ele alınması gibi kullanılabilirlikle ilgili zorluklar yaratır. Ayrıca, yardımcı cihaz kullanıcıları için hem öncesi hem de sonrası durumunun aynı anda DOM'de olduğu bir dönem vardır ve öğeler görsel olarak düzgün şekilde hareket etse de okuma konumunun ve odağının kolayca kaybolmasına neden olabilir.

Kaydırma konumundaki iki durum farklı olduğunda durum değişikliklerini ele almak özellikle zordur. Ayrıca, bir öğe bir kapsayıcıdan diğerine taşınıyorsa overflow: hidden ve diğer kırpma biçimleriyle ilgili zorluklarla karşılaşabilirsiniz. Bu da, istediğiniz efekti elde etmek için CSS'nizi yeniden yapılandırmanız gerektiği anlamına gelir.

Bu imkansız değil, sadece gerçekten zor.

Görünüm Geçişleri, DOM değişikliklerinizi durumlar arasında çakışma olmadan yapmanıza ve anlık görüntüler kullanarak durumlar arasında geçiş animasyonu oluşturmanıza olanak tanıyarak size daha kolay bir yol sunar.

Ayrıca, mevcut uygulama tek sayfalık uygulamaları (SPA'lar) hedeflese de bu özellik, tam sayfa yüklemeleri arasında geçişe olanak tanıyacak şekilde genişletilecek. Bu, şu anda mümkün değildir.

Standartlaştırma durumu

Bu özellik, bir taslak spesifikasyon olarak W3C CSS Çalışma Grubu dahilinde geliştirilmektedir.

API tasarımından memnun kaldığımızda, bu özelliği kararlı sürüme göndermek için gerekli işlemleri ve kontrolleri başlatacağız.

Geliştiricilerden gelen geri bildirimler gerçekten önemlidir. Bu nedenle, lütfen önerilerinizi ve sorularınızı GitHub'da sorun bildirin.

En basit geçiş: Çapraz geçiş

Varsayılan Görüntüleme Geçişi, geçiştir ve bu nedenle API'yi tanıtmak için iyi bir giriş görevi 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. Bu işlem istediğiniz gibi yapılabilir: Öğe ekleyip kaldırma, sınıf adlarını değiştirme veya stilleri değiştirme... önemli değildir.

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, ancak buna geçmeden önce bu temel çapraz geçişinin nasıl çalıştığını anlamamız gerekiyor.

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

Kod örneğini yukarıdan aldığımızda:

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

.startViewTransition() çağrıldığında, API sayfanın mevcut durumunu yakalar. Ekran görüntüsü almak da buna dahildir.

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

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.

Basit özelleştirme

Yukarıdaki sözde öğelerin 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 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, Materyal Tasarım'ın paylaşılan eksen geçişini uygulayalım:

@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.

Tamam, 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 üzerinden)
  • 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 çıkarabiliriz:

.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 kullanılmadığı zaman geri oku, başlık metin öğesinin boyutunu küçültür, ancak her iki sayfada da aynı boyutta olmasını isteriz.

Ş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.

Geçişlerde hata ayıklama

Görünüm Geçişleri CSS animasyonları üzerine 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üntüleme 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.

Bunun gerçek kodu, yukarıdaki basit örnekten biraz daha karmaşıktır. Çünkü bu örnekte, küçük resim sayfasına geri dönüş de bulunmaktadır. 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.

Yukarıdaki demoda kenar çubuğunun geçişi, her iki durumda da giriş, çıkış veya her iki durumda da yapılmasına bağlı olarak farklılık gösterir. 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/yeni sözde öğeyi hedeflemek için :only-child sözde sınıfını 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çeriklerinizden en iyi şekilde yararlanın

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

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?

Bir öğe, en boy oranı değişikliğiyle başka bir öğeye geçiyor. 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 seçenek olmakla birlikte bu durumda istediğimiz ş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".

Cihaz durumuna bağlı olarak geçişi değiştirme

Mobil 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 aşağıdaki ö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 sistemlerini kullanarak daha az hareket tercih ettiklerini ve bu tercihi CSS aracılığıyla göstermemizi tercih edebilirler.

Ş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. Yukarıdakiler yerine, öğeler ile veri akışı arasındaki ilişkiyi hâlâ ifade eden, daha incelikli bir animasyon seçebilirsiniz.

Gezinme türüne bağlı olarak geçişi değiştirme

Bazen belirli bir sayfa 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.

Bu tür durumları yönetmenin en iyi yolu, doküman öğesi olarak da bilinen <html> üzerinde bir sınıf adı belirlemektir:

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ş bitiş durumuna ulaştığında kesinleşen transition.finished vaadi kullanılmıştır. 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.

Diğer animasyonları dondurmadan geçiş yapma

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:

Yapılması 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 özelliğin herhangi bir şekilde çoklu doldurulabileceğini düşünmüyorum ama yanlışlığımın kanıtlanmasından mutluluk duyuyorum!

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,
  classNames = [],
  updateDOM,
}) {
  if (skipTransition || !document.startViewTransition) {
    const updateCallbackDone = Promise.resolve(updateDOM()).then(() => {});

    return {
      ready: Promise.reject(Error('View transitions unsupported')),
      updateCallbackDone,
      finished: updateCallbackDone,
      skipTransition: () => {},
    };
  }

  document.documentElement.classList.add(...classNames);

  const transition = document.startViewTransition(updateDOM);

  transition.finished.finally(() =>
    document.documentElement.classList.remove(...classNames)
  );

  return transition;
}

Ve şu şekilde kullanılabilir:

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

  const transition = transitionHelper({
    classNames,
    updateDOM() {
      updateTheDOMSomehow(data);
    },
  });

  // …
}

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 işlemini skipTransition adlı kullanıcıya da 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(updateCallback)

Yeni bir ViewTransition başlatın.

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

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.

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ı/başarısızlığı sizin için önemli değildir. Tek istediğiniz 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.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ış.

plus-lighter karıştırma modunun 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

Bu aşamada geliştiricilerden gelen geri bildirimler çok önemlidir. Bu nedenle, sorunlarınızı GitHub'da belirterek önerilerinizi ve sorularınızı iletebilirsiniz.