Kapsamlı Bileşen Geçişleri

document.startViewTransition() ile bir görünüm geçişi başlatıldığında ::view-transition yer paylaşımı :root öğesine eklenir. Bu, sayfanın geri kalanındaki işaretçi etkinliklerini engeller, eşzamanlı geçişleri önler ve position: fixed başlıklarını veya açık [popover] öğelerini gizler (taşma sızıntısını önlemek için iç içe yerleştirilmiş görünüm geçiş grupları kullanılsa bile).

Bileşen kökünde element.startViewTransition() çağrısı yapıldığında geçiş, ilgili DOM alt ağacıyla sınırlandırılır. ::view-transition sözde öğe ağacı doğrudan öğeye yerleştirilir. Böylece, harici yığınlama bağlamları ve sayfa etkileşimliliği korunurken birden fazla bileşenin aynı anda geçiş yapmasına olanak tanınır.

Uygulama

1. Kapsamlı geçişi tetikleme

Bileşenin kök kapsayıcı öğesinde document.startViewTransition() yerine element.startViewTransition() yöntemini çağırın. startViewTransition() yöntemini çağırdığınız öğe, geçişin kapsamı (kapsam öğesi) olur: Yalnızca alt ağacındaki öğeler yakalanır, güncelleme geri çağırma sırasında yalnızca kapsamın oluşturulması durdurulur ve elde edilen ::view-transition yer paylaşımı, kapsam öğesinin boyutuna, şekline ve yığınlama bağlamına uyar.

2. Katılımcı Öğeleri Adlandırma

Kapsam içindeki animasyonlu olması gereken alt öğelere bir view-transition-name atayın. Dinamik liste öğelerinde view-transition-name: match-element kullanarak her öğenin geçiş adını DOM düğüm kimliğinden otomatik olarak türetebilir veya açık adlar atayabilirsiniz (yalnızca bu kapsamda benzersiz olması gerekir).

Adlar her kapsamda izole edilir. Böylece DOM'un farklı bölümlerindeki bileşenler arasında çakışmalar önlenir. Ancak view-transition-name, iç içe yerleştirilmiş kapsamlı görünüm geçişlerindeki adlarla çakışabilir. Bunu önlemek için alt kapsam öğesine view-transition-scope: all ekleyin.

3. Kendi Kendine Katılım ve Taşma Kırpma

Kapsam öğesi, kullanıcı aracısı stil sayfası view-transition-name: root ve view-transition-group: contain uyguladığı için kendi görünüm geçişine otomatik olarak katılır. Bu sayede, kapsamın geometrisi DOM'da yaptığınız değişiklikten etkilendiğinde, görüntüleme geçişinin bir parçası olarak kendi geometrisini alt öğeleriyle birlikte sorunsuz bir şekilde canlandırır.

Kapsam, taşmasını kırptığında (overflow: hidden, scroll veya clip) ::view-transition-group-children(root) otomatik olarak overflow: clip uygular ve geçiş yapan alt öğelerin bileşen sınırlarının dışına taşmasını önler.

4. Eşzamanlı ve İç İçe Geçişler

Ayrı bileşen alt ağaçları, birbirini atlamadan eşzamanlı olarak element.startViewTransition() çalıştırılabilir. Etkin kapsamlar, UA stil sayfasında otomatik olarak view-transition-scope: all alır. Böylece dahili view-transition-name değerleri dış geçişlere sızmaz. Bileşen boşta olduğunda bile dış geçişlerin dahili adları yakalamasını önlemek için yinelenen bileşen kapsayıcılarında CSS'de view-transition-scope: all uygulayın.

Dış geçişin, iç geçişi etkinken bir bileşeni taşıması gerekiyorsa iç kapsamı sarmalayan bir öğeye view-transition-name atayın. Ardından dış geçiş, iç kapsam öğesi yerine yalnızca sarmalayıcıyı yakalar ve animasyonlu hale getirir. Böylece iç geçiş tamamlanana kadar çalışır.

Örnek

Kapsamı oluşturmak için kapsam öğesinin kendisinde (.card veya .card ul) CSS gerekmez. Kapsam, document yerine öğede startViewTransition() çağrılarak belirlenir (Bileşen, dış görünüm geçişinin içine yerleştirilecekse view-transition-scope: all için Dış geçiş çalıştırma bölümüne bakın).

/* 1. Name participating child items */
.card ul li {
  view-transition-name: match-element;
}

/* 2. OPTIONAL: Customize transition timing (300ms is an example duration) */
::view-transition-group(*) {
  animation-duration: 300ms;
  animation-timing-function: ease-in-out;
}

/* 3. MANDATORY: Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
// 1. Trigger a transition scoped to a single component subtree
function runScopedTransition(scopeEl, updateDOM) {
  // MANDATORY: Call startViewTransition on the component element, not document
  const transition = scopeEl.startViewTransition(() => {
    updateDOM();
  });

  // Re-triggering startViewTransition() on the same element skips the active transition
  // and rejects `ready`. Catch the rejection to avoid unhandled promise rejection errors.
  transition.ready.catch(() => {});
}

// 2. Reorder items by moving existing DOM nodes (required for `match-element`)
// Scope to `listEl` since only the list order changes, keeping controls outside `listEl` interactive
function reorderItems(listEl, orderedIds) {
  const existing = new Map(
    Array.from(listEl.children, (li) => [li.dataset.id, li]),
  );

  runScopedTransition(listEl, () => {
    for (const id of orderedIds) {
      const li = existing.get(id);
      if (li) listEl.appendChild(li);
    }
  });
}

// 3. Remove an item while preserving exit animation and keyboard focus
// Scope to the outer `cardEl` so the card itself also transitions its height change
function removeItem(cardEl, itemEl) {
  const hadFocus = itemEl.contains(document.activeElement);
  const nextFocusTarget =
    itemEl.nextElementSibling?.querySelector('button') ??
    itemEl.previousElementSibling?.querySelector('button') ??
    cardEl.querySelector('button');

  runScopedTransition(cardEl, () => {
    itemEl.remove();
    if (hadFocus) {
      nextFocusTarget?.focus();
    }
  });
}

Dış geçiş çalıştırma

Amaçlanan kapsamın dışında bir görünüm geçişi başlatıldığında (ör. bir üst bileşenden veya sayfa düzeyinde geçiş için dokümandan) dış geçiş, kapsam öğesini ve içindeki her şeyi yakalayıp canlandırır.

Bunu önlemek için kapsam öğesini kendi view-transition-name değerine sahip ayrı bir sarmalayıcı öğeye sarmalayın ve iç kapsam öğesine view-transition-scope: all uygulayın. Bu sayede, sarmalayıcının kendisi dış geçiş tarafından yakalanıp animasyonlu hale getirilebilirken iç kapsamın içeriğinin yakalanması engellenir.

<div class="page" id="page">
  <div class="card-wrapper">
    <section class="card" id="left">
      <h2>Card A</h2>
      <ul id="left-list">
        <li>…</li>
      </ul>
    </section>
  </div>
  <div class="card-wrapper">
    <section class="card" id="right">
      <h2>Card B</h2>
      <ul id="right-list">
        <li>…</li>
      </ul>
    </section>
  </div>
</div>
document.querySelector('#page').startViewTransition(() => {
  // do your DOM updates here...
});
/* Apply a view-transition-name on the wrapper */
.card-wrapper {
  view-transition-name: match-element;
}

/* Prevent the contents of the card being captured by an outer transition */
.card {
  view-transition-scope: all;
}

.card ul li {
  view-transition-name: match-element;
}

Alternatif olarak, dış geçişin alt öğeleri yakalamasına izin verebilirsiniz. Kırpma efektlerini korumak için iç içe yerleştirilmiş görünüm geçiş gruplarını kullanarak yakalanan alt öğeleri sarmalayıcılarının sınırlarında kırpın. Ancak bu durumda, clip öğesini sonuçta elde edilen ::view-transition-group-children() sözde öğesine manuel olarak uygulamanız gerekir.

.card-wrapper {
  overflow: clip;
  view-transition-name: match-element; /* Capture me as part of the outer VT */
  view-transition-group: contain; /* Nest children that also have a view-transition-name */
  view-transition-class: card; /* For targeting purposes */
}

/* Manually copy back the clip onto the ::view-transition-group-children pseudo */
::view-transition-group-children(.card) {
  overflow: clip;
}

.card ul li {
  view-transition-name: match-element;
}

Kısıtlamalar ve erişilebilirlik

  • ZORUNLU: document yerine bileşen kök öğesinde startViewTransition() yöntemini çağırın. Böylece harici position: fixed öğeleri ve [popover] yer paylaşımları üstte katmanlı olarak kalır ve sayfanın geri kalanı etkileşimli kalır.
  • ZORUNLU: view-transition-name: match-element kullanırken mevcut DOM düğümlerini koruyun ve taşıyın (innerHTML ile yeniden oluşturmak yerine). Çünkü match-element, DOM düğümü kimliğini izler.
  • ZORUNLU: ::view-transition-group(*), ::view-transition-old(*) ve ::view-transition-new(*) öğelerinde animation: none !important değerini ayarlayarak @media (prefers-reduced-motion: reduce) kullanan kullanıcıların azaltılmış hareket tercihlerine uyun.
  • ZORUNLU: Geçiş sırasında odaklanılan bir öğeyi kaldırırken odağı güncelleme geri çağırma işlevindeki bitişik bir öğeye veya denetime kaydırarak klavye odağını koruyun.
  • DO: Kapsam içindeki yakalanan bir öğe kendi taşmasını kırpıyor ve iç içe yerleştirilmiş yakalanan öğeler içeriyorsa iç içe yerleştirilmiş görünüm geçiş gruplarını (::view-transition-group-children() üzerinde view-transition-group: contain ve overflow: clip) kullanın.
  • DO: Attach transition.ready.catch(() => {}) when rapid interactions can re-trigger element.startViewTransition() on the same scope element.
  • DO: İç geçiş çalışırken dış geçişin bileşenin konumunu canlandırması gerekiyorsa iç kapsam öğesini ayrı bir adlandırılmış sarmalayıcı öğeye sarmalayın.
  • YAPMAYIN: Taşmasını kırpan (overflow: hidden, scroll veya clip) ya da kendi geometrisini değiştiren bir kapsam öğesinde view-transition-name: none değerini ayarlamayın. Kendi kendine katılım devre dışı bırakıldığında otomatik taşma kırpma da devre dışı bırakılır.

Yedek stratejiler

Baseline hedefiniz öğe kapsamlı görünüm geçişlerini desteklemiyorsa DOM güncelleme geri çağırma işlevini animasyon olmadan hemen çalıştırarak sorunsuz bir şekilde geçiş yapın. document.startViewTransition() yöntemini kullanabilirsiniz ancak bu yöntem, izole bileşen güncellemeleri için önerilmez. Bunun nedeni, doküman kapsamlı bir geçişin eşzamanlı bileşen geçişlerini engellemesi ve üst katmandaki position: fixed ve [popover] öğelerini gizlemesidir.

// Feature detect specifically on Element.prototype (not document.startViewTransition)
const supportsScopedTransition = 'startViewTransition' in Element.prototype;

function runWithTransition(scopeEl, updateDOM) {
  if (!supportsScopedTransition) {
    // Fallback: apply the DOM update immediately without animation
    updateDOM();
    return;
  }

  const transition = scopeEl.startViewTransition(() => {
    updateDOM();
  });

  transition.ready.catch(() => {});
}

document.startViewTransition() kullanarak bir yedek oluşturmanız gerekiyorsa Dış geçiş çalıştırma bölümünde ayrıntılı olarak açıklandığı gibi, kapsayıcıların kırpma efektlerinin korunduğundan emin olmanız gerekir. Bu yedek yaklaşımın, birden fazla görüntüleme geçişini aynı anda çalıştırma özelliğini bozduğunu ve üst katmandaki position: fixed ve [popover] öğelerini gizleyeceğini unutmayın.