स्कोप वाले कॉम्पोनेंट ट्रांज़िशन

document.startViewTransition() के साथ व्यू ट्रांज़िशन शुरू करने पर, ::view-transition ओवरले :root एलिमेंट से अटैच हो जाता है. इससे पेज के बाकी हिस्से पर पॉइंटर इवेंट ब्लॉक हो जाते हैं, एक साथ होने वाले ट्रांज़िशन रुक जाते हैं, और position: fixed हेडर या खुले हुए [popover] एलिमेंट छिप जाते हैं. ऐसा तब भी होता है, जब ओवरफ़्लो ब्लीडिंग को रोकने के लिए, नेस्ट किए गए व्यू ट्रांज़िशन ग्रुप का इस्तेमाल किया जाता है.

किसी कॉम्पोनेंट रूट पर element.startViewTransition() को कॉल करने से, ट्रांज़िशन को उस डीओएम सबट्री तक सीमित कर दिया जाता है: ::view-transition सूडो-एलिमेंट ट्री को सीधे एलिमेंट में इंजेक्ट किया जाता है. इससे बाहरी स्टैकिंग कॉन्टेक्स्ट और पेज इंटरैक्टिविटी को सुरक्षित रखा जाता है. साथ ही, कई कॉम्पोनेंट को एक साथ ट्रांज़िशन करने की अनुमति मिलती है.

लागू करना

1. स्कोप वाले ट्रांज़िशन को ट्रिगर करना

document.startViewTransition() के बजाय, कॉम्पोनेंट के रूट कंटेनर एलिमेंट पर element.startViewTransition() को कॉल करें. जिस एलिमेंट पर startViewTransition() को कॉल किया जाता है वह ट्रांज़िशन का स्कोप (स्कोप एलिमेंट) बन जाता है: सिर्फ़ उसके सबट्री में मौजूद एलिमेंट कैप्चर किए जाते हैं. साथ ही, अपडेट कॉलबैक के दौरान सिर्फ़ स्कोप की रेंडरिंग रुकती है. इसके अलावा, मिलने वाला ::view-transition ओवरले, स्कोप एलिमेंट के साइज़, शेप, और स्टैकिंग कॉन्टेक्स्ट से मैच करता है.

2. हिस्सा लेने वाले एलिमेंट के नाम

स्कोप में मौजूद उन चाइल्ड एलिमेंट को view-transition-name असाइन करें जिन्हें ऐनिमेट करना है. डाइनैमिक सूची के आइटम पर view-transition-name: match-element का इस्तेमाल किया जा सकता है. इससे हर एलिमेंट के ट्रांज़िशन का नाम, उसके डीओएम नोड आइडेंटिटी से अपने-आप जनरेट हो जाता है. इसके अलावा, साफ़ तौर पर नाम असाइन किए जा सकते हैं. हालांकि, ये नाम उस स्कोप में यूनीक होने चाहिए.

नामों को हर स्कोप में अलग-अलग रखा जाता है. इससे डीओएम के अलग-अलग हिस्सों में मौजूद कॉम्पोनेंट के बीच टकराव नहीं होता. हालांकि, नेस्ट किए गए स्कोप वाले व्यू ट्रांज़िशन में, view-transition-names के नाम आपस में टकरा सकते हैं. इससे बचने के लिए, चाइल्ड स्कोप एलिमेंट में view-transition-scope: all जोड़ें.

3. सेल्फ़-पार्टिसिपेशन और ओवरफ़्लो क्लिपिंग

स्कोप एलिमेंट, अपने-आप व्यू ट्रांज़िशन में शामिल हो जाता है. ऐसा इसलिए होता है, क्योंकि उपयोगकर्ता-एजेंट की स्टाइलशीट, उस पर view-transition-name: root और view-transition-group: contain लागू करती है. इस तरह, जब डीओएम में किए गए बदलाव से स्कोप की ज्यामिति पर असर पड़ता है, तो यह व्यू ट्रांज़िशन के हिस्से के तौर पर, अपने चाइल्ड एलिमेंट के साथ-साथ अपनी ज्यामिति को भी आसानी से ऐनिमेट करता है.

जब स्कोप अपने ओवरफ़्लो (overflow: hidden, scroll या clip) को क्लिप करता है, तो ::view-transition-group-children(root) अपने-आप overflow: clip लागू हो जाता है. इससे ट्रांज़िशन करने वाले चाइल्ड कॉम्पोनेंट, कॉम्पोनेंट की सीमाओं से बाहर नहीं जाते.

4. एक साथ और नेस्ट किए गए ट्रांज़िशन

अलग-अलग कॉम्पोनेंट सबट्री, एक-दूसरे को स्किप किए बिना element.startViewTransition() को एक साथ चला सकती हैं. यूए स्टाइलशीट में, चालू स्कोप को अपने-आप view-transition-scope: all मिल जाता है. इससे, अंदरूनी view-transition-name वैल्यू, बाहरी ट्रांज़िशन में लीक नहीं होती हैं. बार-बार इस्तेमाल होने वाले कॉम्पोनेंट कंटेनर पर, सीएसएस में view-transition-scope: all लागू करें. इससे, कॉम्पोनेंट के इस्तेमाल में न होने पर भी, बाहरी ट्रांज़िशन को अंदरूनी नाम कैप्चर करने से रोका जा सकता है.

अगर किसी बाहरी ट्रांज़िशन को किसी कॉम्पोनेंट को मूव करना है और उसका अंदरूनी ट्रांज़िशन चालू है, तो अंदरूनी स्कोप को रैप करने वाले एलिमेंट को view-transition-name असाइन करें. इसके बाद, बाहरी ट्रांज़िशन, इनर स्कोप एलिमेंट के बजाय सिर्फ़ रैपर को कैप्चर और ऐनिमेट करता है. इससे इनर ट्रांज़िशन को पूरा होने में मदद मिलती है.

उदाहरण

स्कोप तय करने के लिए, स्कोप एलिमेंट (.card या .card ul) पर किसी सीएसएस की ज़रूरत नहीं होती. स्कोप तय करने के लिए, एलिमेंट पर document के बजाय startViewTransition() को कॉल किया जाता है. अगर कॉम्पोनेंट को किसी बाहरी व्यू ट्रांज़िशन के अंदर नेस्ट किया जाएगा, तो view-transition-scope: all के लिए बाहरी ट्रांज़िशन चलाना देखें.

/* 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();
    }
  });
}

बाहरी ट्रांज़िशन चलाना

तय किए गए स्कोप से बाहर व्यू ट्रांज़िशन शुरू करने पर, बाहरी ट्रांज़िशन, स्कोप एलिमेंट और उसके अंदर मौजूद हर चीज़ को कैप्चर करेगा और उसे ऐनिमेट करेगा. उदाहरण के लिए, पैरंट कॉम्पोनेंट से या पेज-लेवल ट्रांज़िशन के लिए दस्तावेज़ से.

ऐसा होने से रोकने के लिए, स्कोप एलिमेंट को उसके view-transition-name के साथ एक अलग रैपर एलिमेंट में रैप करें. साथ ही, अंदरूनी स्कोप एलिमेंट पर view-transition-scope: all लागू करें. इससे रैपर को कैप्चर किया जा सकता है और बाहरी ट्रांज़िशन से ऐनिमेट किया जा सकता है. साथ ही, इनर स्कोप के कॉन्टेंट को कैप्चर होने से रोका जा सकता है.

<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;
}

इसके अलावा, आउटर ट्रांज़िशन को चाइल्ड एलिमेंट कैप्चर करने की अनुमति दी जा सकती है. क्लिपिंग इफ़ेक्ट को बनाए रखने के लिए, नेस्ट किए गए व्यू ट्रांज़िशन ग्रुप का इस्तेमाल करें. इससे कैप्चर किए गए चाइल्ड एलिमेंट को उनके रैपर की सीमाओं पर क्लिप किया जा सकेगा. हालांकि, ऐसे मामले में आपको ::view-transition-group-children() स्यूडो के नतीजे पर, clip को मैन्युअल तरीके से लागू करना होगा.

.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;
}

पाबंदियां और सुलभता

  • ज़रूरी: document के बजाय कॉम्पोनेंट के रूट एलिमेंट पर startViewTransition() को कॉल करें, ताकि बाहरी position: fixed एलिमेंट और [popover] ओवरले सबसे ऊपर लेयर में बने रहें और पेज का बाकी हिस्सा इंटरैक्टिव बना रहे.
  • ज़रूरी: view-transition-name: match-element का इस्तेमाल करते समय, मौजूदा डीओएम नोड को सुरक्षित रखें और उन्हें मूव करें. उन्हें innerHTML की मदद से फिर से न बनाएं, क्योंकि match-element, डीओएम नोड की पहचान को ट्रैक करता है.
  • ज़रूरी: @media (prefers-reduced-motion: reduce) का इस्तेमाल करके, कम मोशन के लिए उपयोगकर्ता की प्राथमिकताओं का सम्मान करें. इसके लिए, ::view-transition-group(*), ::view-transition-old(*), और ::view-transition-new(*) पर animation: none !important सेट करें.
  • ज़रूरी: ट्रांज़िशन के दौरान फ़ोकस किए गए एलिमेंट को हटाते समय, कीबोर्ड फ़ोकस को बनाए रखें. इसके लिए, अपडेट कॉलबैक में फ़ोकस को बगल वाले आइटम या कंट्रोल पर शिफ़्ट करें.
  • क्या करें: नेस्ट किए गए व्यू ट्रांज़िशन ग्रुप (::view-transition-group-children() पर view-transition-group: contain और overflow: clip) का इस्तेमाल तब करें, जब स्कोप के अंदर कैप्चर किया गया एलिमेंट अपने ओवरफ़्लो को क्लिप करता हो और उसमें नेस्ट किए गए कैप्चर किए गए एलिमेंट शामिल हों.
  • क्या करें: जब तेज़ी से होने वाले इंटरैक्शन, एक ही स्कोप एलिमेंट पर element.startViewTransition() को फिर से ट्रिगर कर सकते हैं, तो transition.ready.catch(() => {}) अटैच करें.
  • यह करें: अगर किसी बाहरी ट्रांज़िशन को कॉम्पोनेंट की पोज़िशन को ऐनिमेट करना है, तो इनर स्कोप एलिमेंट को अलग से नाम वाले रैपर एलिमेंट में रैप करें. ऐसा तब करें, जब इनर ट्रांज़िशन चल रहा हो.
  • ऐसा न करें: view-transition-name: none को ऐसे स्कोप एलिमेंट पर सेट न करें जो ओवरफ़्लो (overflow: hidden, scroll या clip) को क्लिप करता है या अपनी ज्यामिति बदलता है. ऐसा इसलिए, क्योंकि खुद के शामिल होने से ऑप्ट आउट करने पर, ओवरफ़्लो की अपने-आप होने वाली क्लिपिंग बंद हो जाती है.

फ़ॉलबैक रणनीतियां

अगर आपके बेसलाइन टारगेट में, एलिमेंट के स्कोप वाले व्यू ट्रांज़िशन काम नहीं करते हैं, तो बिना ऐनिमेशन के डीओएम अपडेट कॉलबैक को तुरंत चलाकर, व्यू ट्रांज़िशन को बेहतर बनाएं. हालांकि, आपके पास document.startViewTransition() पर वापस जाने का विकल्प है, लेकिन अलग-अलग कॉम्पोनेंट के अपडेट के लिए ऐसा करने का सुझाव नहीं दिया जाता. इसकी वजह यह है कि दस्तावेज़ के दायरे में आने वाला ट्रांज़िशन, एक साथ होने वाले कॉम्पोनेंट ट्रांज़िशन को ब्लॉक करता है. साथ ही, टॉप लेयर में मौजूद position: fixed और [popover] एलिमेंट को छिपा देता है.

// 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() का इस्तेमाल करके फ़ॉलबैक की ज़रूरत है, तो आपको यह पक्का करना होगा कि कंटेनर के क्लिपिंग इफ़ेक्ट सुरक्षित रहें. इसके बारे में ज़्यादा जानकारी, बाहरी ट्रांज़िशन चलाना सेक्शन में दी गई है. ध्यान दें कि फ़ॉलबैक का यह तरीका, एक साथ कई व्यू ट्रांज़िशन चलाने की सुविधा को बंद कर देता है. साथ ही, टॉप लेयर में मौजूद position: fixed और [popover] एलिमेंट को छिपा देता है.