Aynı Doküman Geçişleri

Web özellikleri: Görüntüleme geçişleri

Sorun

Web siteleri genellikle bir nesnenin birden fazla görünümünü (ör. ürün listesi ve ardından her ürün için bir ayrıntılar sayfası) sunar. İki görünüm arasında gezinmek genellikle kopuk bir deneyim sunar. Kullanıcı, ayrıntılarını görüntülemek için bir ürün küçük resmini tıkladığında küçük resim kaybolur ve ekranın başka bir yerinde anında yeni ve daha büyük bir resim görünür. Bu süreklilik eksikliği, kullanıcıların öğeler arasındaki ilişkileri takip etmesini zorlaştırır.

Çözüm

View Transitions API, geçişten önce ve sonra farklı durumlarda bulunan öğe çiftlerini belirtmenize olanak tanır. Tek sayfada gezinme (SPA) sırasında document.startViewTransition() ile geçiş tetiklendiğinde tarayıcı, bu paylaşılan öğeleri benzersiz view-transition-name değerlerine göre tanımlar. Ardından, öğelerin konum, boyut ve stil farkını otomatik olarak hesaplar ve eski durumdan yeni duruma sorunsuz bir şekilde animasyon uygular. Bu geçiş, yüksek z-index değerlerine sahip öğelerin bile üzerinde, en üst katmanda gerçekleşir.

Uygulama Kılavuzu

1. adım: Durum değişikliklerini startViewTransition ile sarmalayın

Tek sayfalık uygulamalar (SPA) veya basit durum değişiklikleri için DOM'u güncelleyen mantığı document.startViewTransition içine alın. Tarayıcı, mevcut durumun anlık görüntüsünü alır, güncellemeyi çalıştırır ve ardından yeni durumun anlık görüntüsünü alır.

function navigate(view) {
  // MANDATORY: Wrap the update in startViewTransition
  document.startViewTransition(() => updateDOM(view));
}

2. adım: Paylaşılan geçiş adları atama

Tarayıcıya hangi öğelerin dönüştürülmesi gerektiğini bildirmek için view-transition-name CSS özelliğini kullanın. Ad, herhangi bir şey olabilir (none hariç). ZORUNLU: Belirli bir view-transition-name değerine sahip en fazla 1 öğe önce ve sonra bulunmalıdır. Belirli bir view-transition-name değerine sahip 2 veya daha fazla öğe varsa DOM, geçiş olmadan hemen yeni duruma güncellenir.

Birden fazla öğe çiftini dönüştürmek için birden fazla view-transition-name kullanabilirsiniz. Örneğin, hem ürün resmini hem de başlığı ayrı geçişlerle geçirmek isteyebilirsiniz.

Liste görünümünde birden fazla öğe olduğundan hepsine aynı view-transition-name değerini veremezsiniz. Bu sorun, SPA'da iki şekilde çözülebilir.

  1. Dinamik ayrıntı sayfası: Liste sayfasındaki her öğeye benzersiz bir view-transition-name atayın ve ardından, liste öğesi seçildiğinde bu adı ayrıntı sayfasındaki eşleşen öğeye dinamik olarak uygulayın (burada gösterildiği gibi).

    /* In the list view, give each */
    #product-1 { view-transition-name: p1 }
    #product-2 { view-transition-name: p2 }
    #product-3 { view-transition-name: p3 }
    
    function updateDOM(clickedTransitionName){
      const hero = document.getElementById("hero");
      hero.style.viewTransitionName = clickedTransitionName;
    }
    
  2. Dinamik liste öğesi: Ayrıntılar sayfasındaki öğeye bir view-transition-name atayın ve seçildiğinde bu adı liste sayfasındaki öğeye uygulayın. Liste sayfasına geri dönerken liste sayfasındaki öğeden view-transition-name öğesini kaldırın.

Ayrıntılar sayfasındaki #hero öğesi ile liste sayfasındaki seçili .thumbnail öğesi aynı view-transition-name değerine sahip.

#hero{
  view-transition-name: hero;
}
.thumbnail.selected {
  view-transition-name: hero;
}

Bir küçük resim tıklandığında, geçişi başlatmadan önce .selected sınıf seçicisini kullanarak view-transition-name öğesini atayarak liste görünümünü hazırlamamız ve DOM'da değişiklik yapmamız gerekir.

Ardından, document.startViewTransition() yöntemini çağırabilir ve sayfayı ayrıntı görünümünden liste görünümüne geçirmek için değişiklikleri uygulayabilirsiniz.

Liste görünümüne geri döndükten sonra, bir sonraki gezinmenin hata vermesini önlemek için görünüm geçiş sınıflarını temizlemeniz gerekir. Bu temizleme işlemini, geçişin finished vaadi çözüldükten sonra gerçekleştirebilirsiniz.

// Function called when a thumbnail is clicked
function goFromListToDetail(e){
  e.currentTarget.classList.add("selected");
  const hero = document.getElementById("hero");
  const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
  hero.style.background = bgColor;

  // Trigger the transition, checking for support
  if (!document.startViewTransition) {
    document.body.classList.add("detail");
    // MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
    document.getElementById("detail-heading")?.focus();
    return; // MANDATORY: End function execution if view transitions are not supported.  
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.add("detail");
  });
  // MANDATORY Accessibility Routing: Route focus after the view transition resolves
  transition.finished.finally(() => {
    document.getElementById("detail-heading")?.focus();
  });
}

// Function called when navigating from detail back to list view
function goFromDetailToList() {
  if (!document.startViewTransition) {
    document.body.classList.remove("detail");
    document.getElementById("list-heading")?.focus();
    return;
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.remove("detail");
  });
  // Clean up the list view and route focus
  transition.finished.finally(() => {
    // Route focus back to list view
    document.getElementById("list-heading")?.focus();
    // Remove selected classList to remove view-transition-names
    document.querySelectorAll(".selected").forEach(
      (element) => {
        element.classList.remove("selected");
      },
    );
  });
}

Seçeceğiniz yöntem, kullanım alanına bağlıdır. Dinamik liste öğesi daha az tekrarlanan CSS gerektirir ancak daha fazla manuel JavaScript temizliği gerektirir.

3. adım: En boy oranı "esnetme" sorununu düzeltme

Varsayılan olarak tarayıcı, her ikisine de uyacak şekilde genişleyen bir grup içindeki eski ve yeni anlık görüntüleri çapraz geçişle birleştirir. Metin geçişi yapıyorsanız geçiş yapılan öğenin en boy oranının sabit olması için metin öğesinin genişliğini hem eski hem de yeni görünümlerde fit-content olarak ayarlayın.

#list-page .title {
  width: fit-content;
}

#detail-page #title {
  width: fit-content;
}

En boy oranını değiştiren öğeleri geçiriyorsanız eski ve yeni sözde öğelerin yüksekliğini ::view-transition-pair() sözde öğesinin% 100'üne ayarlamanız gerekebilir.

::view-transition-old(hero),
::view-transition-new(hero){
  height: 100%;
}

Sözde öğeler, canlı öğelerin anlık görüntüleridir. Bu nedenle, geçiş efekti üzerinde daha fazla kontrol sahibi olmak için object-fit ve object-position bildirimlerini de kullanabilirsiniz.

En İyi Uygulamalar

  • Çok fazla geçiş BELİRTMEYİN. Paylaşılan öğeleri yalnızca kullanıcının aktif olarak takip ettiği birincil içerikler (ör. hero resimler, başlıklar) için kullanın.
  • Gelecekteki geçişlerde yan etkilerden kaçınmak için geçiş tamamlandıktan sonra geçici view-transition-name değerlerini KALDIRIN.
  • Etkin animasyonları olan öğeleri GEÇİŞ YAPMAYIN. Görüntüleme geçişleri anlık görüntüler üzerinde çalıştığından, görüntüleme geçişi sırasında tüm animasyonlar duraklatılmış gibi görünür.
  • DO prefers-reduced-motion medya sorgusunu kullanarak kullanıcıların daha az hareket tercihine saygı gösterin.
  • ZORUNLU Erişilebilirlik Yönlendirmesi: Görüntüleme geçişleri, sayfa düzenlerini dinamik olarak değiştirir ancak programatik odağı yönetmez. Geçiş sırasında gizlenen veya kaldırılan bir öğe odaklanmış olarak kalırsa odak terk edilir ve klavye ile yardımcı teknoloji kullanıcıları bağlamdan mahrum kalır. DOM güncellemelerinden hemen sonra veya görünüm geçişinin finished vaadi çözümlendiğinde odağı programatik olarak güncellenmiş bir sayfa başlığına veya görünüm kapsayıcısına (tabindex="-1" kullanarak) kaydırın.
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Yedek Stratejiler

View Transitions API, aşamalı geliştirme için tasarlanmıştır. Bu özelliği desteklemeyen tarayıcılar, DOM güncellemesini animasyon olmadan hemen yürütür.

function navigate(){
  if (!document.startViewTransition) {
    // Fallback: Just update the DOM
    updateDOM();
  } else {
    document.startViewTransition(() => updateDOM());
  }
}