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.
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:
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ış:
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:
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:
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:
Ş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.
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ç:
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ğ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?
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:
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.
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:
Videoda yanlış bir şey gördünüz mü? Görmediyseniz de endişelenmeyin. Burada yavaşlamış:
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 ş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:
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.
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:
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 gelenwithViewTransitions
ö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 veyaupdateCallback
ü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
veheight
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çinisolation: 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ündenopacity: 0
görünümüne geçiş yapar. Yeni görünümopacity: 0
ürünündenopacity: 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.