Yayınlanma tarihi: 17 Ağustos 2021, Son güncelleme tarihi: 25 Eylül 2024
Tek bir dokümanda görüntü geçişi yapıldığında buna aynı doküman görüntü geçişi denir. 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ünümünde geçişi tetiklemek için document.startViewTransition
işlevini ç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());
}
Tetiklendiğinde tarayıcı, üzerinde view-transition-name
CSS mülkünün tanımlandığı 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. Dilerseniz animasyonlarınızı CSS kullanarak özelleştirebilirsiniz.
Varsayılan geçiş: Geçiş efekti
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'u yeni duruma değiştirir. Bunu istediğiniz şekilde yapabilirsiniz. Örneğin, öğe ekleyebilir veya kaldırabilir, sınıf adlarını değiştirebilir ya da stilleri değiştirebilirsiniz.
Sayfalar böylece birbirine kademeli olarak kaybolur:
Tamam, geçiş çok 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 yer alan bir yer paylaşımında bulunur. Bu seçenek, geçiş için bir arka plan rengi ayarlamak istiyorsanız kullanışlıdır.
::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 "yerine geçen içerik" olarak oluşturulur (<img>
gibi).
Eski görünüm opacity: 1
'ten opacity: 0
'e, yeni görünüm ise opacity: 0
'ten opacity: 1
'e animasyonlu olarak geçerek geçiş oluşturur.
Animasyonun tamamı CSS animasyonları kullanılarak gerçekleştirilir. Bu nedenle, CSS ile özelleştirilebilir.
Geçişi özelleştirme
Görünüm geçişi sözde öğelerinin tümü CSS ile hedeflenebiliyor. Animasyonlar CSS kullanılarak tanımlandığından, mevcut CSS animasyon özelliklerini kullanarak bunları değiştirebilirsiniz. Örneğin:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Bu tek değişiklikle, geçiş artık çok yavaş:
Tamam, bu hâlâ etkileyici değil. Bunun yerine aşağıdaki kod, Material Design'ı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:
Birden fazla öğe için geçiş yapma
Önceki demoda, ortak eksen geçişine sayfanın tamamı dahildir. Bu, sayfanın büyük bir kısmı için işe yarar ancak başlık için pek uygun değil. Çünkü başlık kaydırıldığında tekrar içeri kaydırılır.
Bunu önlemek için üstbilgiyi sayfanın geri kalanından ayırarak ayrı olarak animasyonlu hale getirebilirsiniz. Bu işlem, öğeye bir view-transition-name
atayarak 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). Geçiş boyunca öğeyi benzersiz bir şekilde tanımlamak için kullanılır.
Bunun sonucu da şudur:
Şimdi başlık yerinde kalıyor ve çapraz geçiş yapıyor.
Bu CSS beyanı, 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 ise geri kalanı içindir. Bunlar CSS ile bağımsız olarak hedeflenerek farklı geçişler verilebilir. Ancak bu örnekte main-header
, çapraz geçiş olan varsayılan geçişte kalmıştır.
Tamam, varsayılan geçiş yalnızca bir geçiş değil, ::view-transition-group
de geçiş yapar:
- 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ı boyut ve konumda olduğundan bu durum şimdiye kadar önemli değildi. Ancak üstbilgideki metni de ayıklayabilirsiniz:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
fit-content
, öğenin kalan genişliğe uyacak şekilde uzaması yerine metnin boyutunda olması için kullanılır. Bu olmadan geri ok, her iki sayfada da aynı boyut yerine başlık metin öğesinin boyutunu azaltır.
Şimdi üç bölümle çalışabiliriz:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
Ancak yine de varsayılanları kullanacağız:
Başlık metni, geri düğmesine yer açmak için ekranda biraz kaydırılıyor.
view-transition-class
ile birden fazla sözde öğeyi aynı şekilde animasyonlu hale getirme
Tarayıcı desteği
Sayfada bir grup kartın yanı sıra bir başlık içeren bir görüntü geçişiniz olduğunu varsayalım. Başlık dışındaki tüm kartları hareket ettirmek için her kartı ayrı ayrı hedefleyen bir seçici yazmanız gerekir.
h1 {
view-transition-name: title;
}
::view-transition-group(title) {
animation-timing-function: ease-in-out;
}
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
…
#card20 { view-transition-name: card20; }
::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),
…
::view-transition-group(card20) {
animation-timing-function: var(--bounce);
}
20 öğeniz var mı? Bu, yazmanız gereken 20 seçici demektir. Yeni bir öğe mi ekleyeceksiniz? 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 kart örneğinde önceki CSS snippet'inden yararlanılmıştır. Yeni eklenenler de dahil olmak üzere tüm kartlara aynı zamanlama tek bir seçiciyle uygulanır: html::view-transition-group(.card)
.
Geçişlerde hata ayıklama
Görünüm geçişleri CSS animasyonları temel alınarak oluşturulduğundan, Chrome Geliştirici Araçları'ndaki Animasyonlar paneli geçişlerde hata ayıklama için mükemmel bir seçenektir.
Animasyonlar panelini kullanarak bir sonraki animasyonu duraklatabilir, ardından animasyonda ileri ve geri gidebilirsiniz. Bu işlem sırasında, geçiş sözde öğeleri Öğeler panelinde bulunabilir.
Geçiş öğelerinin aynı DOM öğesi olması gerekmez.
Başlık ve başlıktaki metin için ayrı geçiş öğeleri oluşturmak üzere şimdiye kadar view-transition-name
kullandık. Bunlar, DOM değişikliğinden önce ve sonra kavramsal olarak aynı öğedir ancak bunun geçerli olmadığı geçişler de oluşturabilirsiniz.
Örneğin, ana video yerleştirme için bir view-transition-name
verilebilir:
.full-embed {
view-transition-name: full-embed;
}
Ardından, küçük resim tıklandığında yalnızca geçiş süresi boyunca aynı view-transition-name
verilebilir:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
Sonuç:
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 asıl kodu, küçük resim sayfasına geri geçişi de ele aldığından önceki örnekten biraz daha karmaşıktır. Eksiksiz uygulama için kaynağa bakın.
Özel giriş ve çıkış geçişleri
Aşağıdaki örneğe bakın:
Kenar çubuğu da geçişin bir parçasıdır:
.sidebar {
view-transition-name: sidebar;
}
Ancak önceki örnekteki üstbilginin 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)
Ancak kenar çubuğu yalnızca yeni sayfadaysa ::view-transition-old(sidebar)
sözde öğesi orada olmaz. 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 ::view-transition-old(sidebar)
bulunur.
Ö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 kaydırılarak ve yavaşça belirginleşerek girer, sağa kaydırılarak ve yavaşça kaybolarak çıkar ve her iki durumda da yerinde kalır.
Belirli giriş ve çıkış geçişleri oluşturmak için, resim çiftindeki tek alt öğe olduğunda eski veya yeni sözde öğeleri hedeflemek üzere :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ğu için kenar çubuğu her iki durumda da mevcut olduğunda belirli bir geçiş yoktur.
Eşzamansız DOM güncellemeleri ve içerik bekleme
.startViewTransition()
işlevine iletilen geri çağırma işlevi, bir promise döndürebilir. Bu promise, DOM'un eşzamansız olarak güncellenmesine ve önemli içeriğin hazır olmasını beklemeye olanak tanır.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
Taahhüt yerine getirilene kadar geçiş başlatılmaz. Bu süre zarfında sayfa dondurulur. Bu nedenle, gecikmelerin en aza indirilmesi gerekir. Daha açık belirtmek gerekirse, ağ getirme işlemleri .startViewTransition()
geri çağırma işlevinin bir parçası olarak değil, .startViewTransition()
çağrılmadan önce, sayfa henüz tamamen etkileşime açıkken 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 mevcut içeriği kullanmak daha iyidir.
Mevcut içeriklerinizden en iyi şekilde yararlanın
Küçük resim daha büyük bir resme geçtiğinde:
Varsayılan geçiş, geçiş efektidir. Bu, küçük resmin henüz yüklenmemiş tam resimle geçiş efekti uygulayabileceği anlamına gelir.
Bu sorunu çözmenin bir yolu, geçişi başlatmadan önce resmin tamamını yüklemesini beklemektir. İdeal olarak bu işlem, .startViewTransition()
çağrısı yapılmadan önce yapılır. Böylece sayfa etkileşimli kalır ve kullanıcıya bir şeylerin yüklendiğini göstermek için bir döndürme çubuğu 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 kaybolmuyor, tam resmin altında kalıyor. Yani yeni görünüm yüklenmemişse küçük resim geçiş boyunca görünür. Bu sayede geçiş hemen başlayabilir ve resmin tamamı kendi zamanında yüklenebilir.
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, ana resim ise 16:9 ise ne olur?
Varsayılan geçişte grup, önceki boyuttan sonraki boyuta animasyonla geçer. Eski ve yeni görünümler, grubun% 100 genişliğinde ve otomatik yüksekliktedir. Yani grup boyutundan bağımsız olarak 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;
}
Yani genişlik genişledikçe küçük resim öğenin ortasında kalır ancak tam resim 1:1'den 16:9'a geçerken "kırpılmaz".
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ında geçişleri değiştirmek için medya sorgularını kullanma
Mobil cihazlarda ve masaüstünde farklı geçişler kullanabilirsiniz. Örneğin, mobil cihazlarda yandan tam kaydırma, masaüstünde ise daha hafif bir kaydırma gerçekleştiren bu örnek:
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 hangi öğelere view-transition-name
atadığınızı da değiştirebilirsiniz.
"Düşük hareket" tercihine tepki verme
Kullanıcılar, işletim sistemleri üzerinden azaltılmış hareketi tercih ettiklerini belirtebilir ve bu tercih CSS'de gösterilir.
Bu kullanıcıların geçişini önlemeyi seçebilirsiniz:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Ancak "az hareket" tercihi, kullanıcının hareket olmaması istediği anlamına gelmez. Önceki snippet yerine, öğeler arasındaki ilişkiyi ve veri akışını ifade eden daha ince bir animasyon seçebilirsiniz.
Görüntüleme geçişi türleriyle birden fazla görüntüleme geçişi stilini işleme
Tarayıcı desteği
Bazen belirli bir görünümden diğerine geçiş için özel olarak tasarlanmış bir geçiş gerekir. Örneğin, sayfalandırma sırasına göre bir sonraki veya önceki sayfaya giderken, sıradaki daha yüksek bir sayfaya mı yoksa daha düşük bir sayfaya mı gittiğinize bağlı olarak içeriği farklı bir yönde kaydırabilirsiniz.
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 geçiş yakalarken veya gerçekleştirirken etkindir ve her tür, farklı animasyonlar kullanmak için CSS aracılığıyla özelleştirilebilir.
Aynı doküman görünümünde geçişte türleri kullanmak için types
öğesini startViewTransition
yöntemine iletirsiniz. Buna izin vermek için document.startViewTransition
bir nesneyi de kabul eder: update
, DOM'u güncelleyen geri çağırma işlevidir ve types
, türleri içeren 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. Bu sayede, bir tanesinin bildirimleri diğerinin bildirimleriyle etkileşime girmeden birden fazla görüntü 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ünüm 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 sayfalara ayırma denemesinde, sayfa içerikleri, gittiğiniz sayfa numarasına göre ileri veya geri kayar. Türler, document.startViewTransition
içine geçirildikleri tıklamayla belirlenir.
Türünden bağımsız olarak tüm etkin görünüm geçişlerini hedeflemek için bunun yerine :active-view-transition
sözde sınıf seçicisini 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. "Geri" gezinme düğmesi, "ileri" düğmesinden farklı olmalıdır.
Geçiş türleri kullanılmadan önce bu durumların üstesinden gelmek için geçiş kökünde geçici olarak bir sınıf adı ayarlamak gerekiyordu. document.startViewTransition
çağrılırken bu geçiş kökü, JavaScript'te document.documentElement
kullanılarak erişilebilen <html>
öğesidir:
if (isBackNavigation) {
document.documentElement.classList.add('back-transition');
}
const transition = document.startViewTransition(() =>
updateTheDOMSomehow(data)
);
try {
await transition.finished;
} finally {
document.documentElement.classList.remove('back-transition');
}
Geçiş tamamlandıktan sonra sınıfları kaldırmak için bu örnekte, geçiş son durumuna ulaştığında çözülen bir söz olan transition.finished
kullanılır. Bu nesnenin diğer özellikleri API referansında ele alınmıştır.
Artık geçişi değiştirmek için CSS'nizde bu sınıf adını 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
alması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:
Cihazda bir sorun gördünüz mü? Bulamadıysanız endişelenmeyin. Aşağıda, yavaşlatılmış olarak görebilirsiniz:
Geçiş sırasında video donuyormuş gibi görünür, ardından videonun oynatılan sürümü yavaş yavaş görünür hale gelir. Bunun nedeni, ::view-transition-old(video)
'ün eski görünümün ekran görüntüsü, ::view-transition-new(video)
'un ise yeni görünümün canlı görüntüsü olmasıdır.
Bu sorunu düzeltebilirsiniz ancak önce düzeltmeye değip değmeyeceğini kendinize sorun. Geçiş normal hızda oynatıldığında "sorunu" görmediyseniz değiştirmenizi önermem.
Gerçekten düzeltmek istiyorsanız ::view-transition-old(video)
simgesini göstermeyin, doğrudan ::view-transition-new(video)
simgesine 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ş boyunca oynatılır.
Navigation API (ve diğer çerçeveler) ile entegrasyon
Görüntü geçişleri, diğer çerçevelere veya kitaplıklara entegre edilebilecek şekilde belirtilir. Örneğin, tek sayfalık uygulamanız (SPA) bir yönlendirici kullanıyorsa, görüntüleme geçişi kullanarak içeriği güncellemek için yönlendiricinin güncelleme mekanizmasını ayarlayabilirsiniz.
Bu sayfalandırma demosundan alınan aşağıdaki kod snippet'inde Navigation API'nin müdahale işleyicisi, görünüm geçişleri desteklendiğinde document.startViewTransition
işlevini çağıracak şekilde ayarlanmıştır.
navigation.addEventListener("navigate", (e) => {
// Don't intercept if not needed
if (shouldNotIntercept(e)) return;
// Intercept the navigation
e.intercept({
handler: async () => {
// Fetch the new content
const newContent = await fetchNewContent(e.destination.url, {
signal: e.signal,
});
// The UA does not support View Transitions, or the UA
// already provided a Visual Transition by itself (e.g. swipe back).
// In either case, update the DOM directly
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
// Update the content using a View Transition
const t = document.startViewTransition(() => {
setContent(newContent);
});
}
});
});
Tarayıcıların tümü olmasa da bazıları, kullanıcı gezinmek için kaydırma hareketi yaptığında kendi geçişini sağlar. Bu durumda, kötü veya kafa karıştırıcı bir kullanıcı deneyimine yol açacağından kendi görünüm geçişinizi tetiklememeniz gerekir. Kullanıcı, biri tarayıcı tarafından diğeri sizin tarafınızdan sağlanan iki geçişin art arda çalıştığını görür.
Bu nedenle, tarayıcı kendi görsel geçişini sağladığında görüntü geçişinin başlatılmasının engellenmesi önerilir. Bunu yapmak için NavigateEvent
örneğinin hasUAVisualTransition
özelliğinin değerini kontrol edin. Tarayıcı bir görsel geçiş sağladığında özellik true
olarak ayarlanır. Bu hasUIVisualTransition
özelliği PopStateEvent
örneklerinde de bulunur.
Önceki snippet'te, görüntüleme geçişinin çalışıp çalışmayacağını belirleyen kontrol bu özelliği dikkate alır. Aynı doküman görünüm geçişleri desteklenmediği veya tarayıcı zaten kendi geçişini sağladığı zaman görünüm geçişi atlanır.
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
Aşağıdaki kayıtta kullanıcı, önceki sayfaya geri gitmek için ekranı kaydırmaktadır. Soldaki yakalamada hasUAVisualTransition
işareti yoktur. Sağdaki kayıtta onay işareti yer alır. Bu nedenle, tarayıcı görsel bir geçiş sağladığı için manuel görüntüleme geçişi atlanır.
JavaScript ile animasyon
Şimdiye kadar tüm geçişler CSS kullanılarak tanımlandı ancak bazen CSS yeterli değildir:
Bu geçişin birkaç kısmı yalnızca CSS ile yapılamaz:
- Animasyon, tıklama konumundan başlar.
- Animasyon, en uzak köşeye kadar yarıçapı olan çemberle sona erer. Ancak gelecekte CSS ile bu mümkün olacaktır.
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.
Geçişler bir iyileştirme olarak
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 iyileştirme olarak ele alınmalıdır. Yani DOM değişikliği başarılı olsa bile geçiş başarısız olursa uygulamanız "hata" durumuna girmemelidir. İdeal olarak geçiş başarısız olmamalıdır ancak başarısız olursa kullanıcı deneyiminin geri kalanı etkilenmemelidir.
Geçişleri bir geliştirme olarak ele almak için geçiş taahhütlerini, geçiş başarısız olursa uygulamanızın hata vermesine neden olacak ş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ş ready
durumuna ulaşamazsa switchView()
'ün reddetmesidir. Ancak bu, görünümün geçiş yapamadığı anlamına gelmez. DOM başarıyla güncellenmiş olabilir ancak yinelenen view-transition-name
öğeleri olduğundan 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 başarısız olursa reddetmek için transition.updateCallbackDone
kullanılmaktadır. switchView
artık geçiş başarısız olursa reddetmiyor. DOM güncellemesi tamamlandığında çözülüyor ve başarısız olursa reddediyor.
switchView
'ün, yeni görünüm "yerleştiği" zaman (ör. animasyonlu geçiş tamamlandığında veya sona atlandığında) çözülmesini istiyorsanız transition.updateCallbackDone
'ü transition.finished
ile değiştirin.
Bir polyfill değil ancak…
Bu, polyfill yapılması 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 kolaylaştırır:
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');
}
}
Bu işlev ş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ılmaya devam eder ancak animasyonlu 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.
Animasyon istemiyorsanız görünüm geçişlerini destekleyen tarayıcılarda bile true
değerini skipTransition
olarak da iletebilirsiniz. Bu, sitenizde geçişleri devre dışı bırakmak için kullanıcı tercihi varsa kullanışlıdır.
Çerçevelerle çalışma
DOM değişikliklerini soyutlayan bir kitaplık veya çerçeveyle çalışıyorsanız en zor kısım, DOM değişikliğinin ne zaman tamamlandığını bilmektir. Çeşitli çerçevelerde yukarıdaki yardımcıyı kullanan bir dizi örnek aşağıda verilmiştir.
- React: Buradaki anahtar, bir dizi durum değişikliğini eşzamanlı olarak uygulayan
flushSync
işlevidir. Evet, bu API'nin kullanımıyla ilgili önemli bir uyarı var ancak Dan Abramov bu durumda uygun olduğunu söyledi. React ve asynkron kodda olduğu gibi,startViewTransition
tarafından döndürülen çeşitli promise'leri kullanırken kodunuzun doğru durumda çalıştığından emin olun. - Vue.js: Buradaki anahtar
nextTick
'tır. Bu anahtar, DOM güncellendikten sonra doldurulur. - Svelte: Vue'ya çok benzer ancak bir sonraki değişikliği bekleme yöntemi
tick
. - Lit: Buradaki anahtar, DOM güncellendikten sonra yerine getirilen bileşenlerdeki
this.updateComplete
vaadidir. - Angular: Buradaki anahtar, bekleyen DOM değişikliklerini temizleyen
applicationRef.tick
'tır. Angular 17 sürümü itibarıyla@angular/router
ile birlikte gelenwithViewTransitions
'ü kullanabilirsiniz.
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şlatmaDokümanın geçerli durumu yakalandığında
update
çağrılır.types
, geçişi yakalarken veya gerçekleştirirken geçiş için etkin türleri ayarlar. Bu klasör başlangıçta boştur. Daha fazla bilgi için aşağıya göz atınviewTransition.types
.
ViewTransition
örnek üyeleri:
viewTransition.updateCallbackDone
updateCallback
tarafından verilen vaat yerine getirildiğinde karşılayan veya reddettiğinde reddeden bir vaattir.Görüntüleme Geçişi API'si, bir DOM değişikliğini sarmalayarak geçiş oluşturur. Ancak bazen geçiş animasyonunun başarılı olup olmadığını değil, DOM değişikliğinin olup olmadığını ve ne zaman gerçekleştiğini bilmek istersiniz.
updateCallbackDone
bu kullanım alanına uygundur.viewTransition.ready
Geçiş için sözde öğeler oluşturulduktan ve animasyon başlamak üzereyken yerine getirilen bir söz.
Geçiş başlayamazsa işlemi reddeder. Bunun nedeni, yinelenen
view-transition-name
'ler gibi yanlış yapılandırma veyaupdateCallback
'un reddedilen bir promise döndürmesi olabilir.Bu, geçiş sözde öğelerini JavaScript ile canlandırmak için kullanışlıdır.
viewTransition.finished
Son durum kullanıcı tarafından tamamen görünür ve etkileşimli hale geldiğinde yerine getirilen bir söz.
Yalnızca
updateCallback
reddedilen bir söz döndürürse reddeder. Bu, son durumun oluşturulmadığını gösterir.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çinclear()
,add()
vedelete()
örnek yöntemlerini kullanın.CSS'de belirli bir türe yanıt vermek için geçiş kökündeki
:active-view-transition-type(type)
sözde sınıf seçicisini 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ğundan bu durumda
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
Kesin konumlandırma.
"Önce" ve "sonra" durumları arasında
width
veheight
geçişleri."Önce" ve "sonra" görüntü alanı-uzay dörtgeni arasında geçişler
transform
.::view-transition-image-pair
Grubu dolduracak şekilde konumlandırılmış.
mix-blend-mode
öğesinin eski ve yeni görünümler üzerindeki etkisini sınırlandırmak içinisolation: isolate
kullanabilir.::view-transition-new
ve::view-transition-old
Sarmalayıcının sol üst tarafına mutlak olarak yerleştirilir.
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
'tenopacity: 0
'a geçer. Yeni görünüm,opacity: 0
'tenopacity: 1
'e geçiş yapar.
Geri bildirim
Geliştirici geri bildirimleri bizim için çok değerli. Bunun için GitHub'daki CSS Çalışma Grubu'na öneri ve sorularla ilgili bir sorun bildirin. Sorununuza [css-view-transitions]
öneki ekleyin.
Bir hatayla karşılaşırsanız bunun yerine Chromium hatası bildirin.