व्यू ट्रांज़िशन एपीआई के साथ आसान और आसान ट्रांज़िशन

ब्राउज़र सहायता

  • 111
  • 111
  • x
  • x

सोर्स

व्यू ट्रांज़िशन एपीआई, दोनों स्थितियों के बीच ऐनिमेशन वाला ट्रांज़िशन बनाते समय, एक ही चरण में डीओएम को बदलना आसान बनाता है. यह Chrome 111 के बाद के वर्शन पर उपलब्ध है.

व्यू ट्रांज़िशन एपीआई की मदद से किए गए ट्रांज़िशन. डेमो साइट आज़माएं – इसके लिए, Chrome 111 या इसके बाद का वर्शन होना ज़रूरी है.

हमें इस सुविधा की ज़रूरत क्यों है?

पेज ट्रांज़िशन न सिर्फ़ बेहतरीन दिखते हैं, बल्कि इनसे फ़्लो की दिशा भी पता चलती है. साथ ही, यह भी पता चलता है कि कौनसे एलिमेंट एक पेज से दूसरे पेज पर जुड़े हैं. ऐसा, डेटा फ़ेच करने के दौरान भी हो सकता है, जिससे परफ़ॉर्मेंस के बारे में तेज़ी से पता चलता है.

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

सच बात तो यह है कि राज्य में बदलाव करना मुश्किल है. हमारे पास जो टूल पहले से मौजूद हैं उनका इस्तेमाल भी बहुत मुश्किल है.

यहां तक कि सिंपल क्रॉस-फ़ेड जैसी कुछ चीज़ों में एक ही समय में दोनों स्थितियां मौजूद होती हैं. इससे इस्तेमाल करने में आने वाली चुनौतियां सामने आती हैं, जैसे कि आउटगोइंग एलिमेंट पर ज़्यादा इंटरैक्शन को हैंडल करना. साथ ही, सहायक डिवाइसों का इस्तेमाल करने वाले लोगों के लिए, एक ऐसा समय होता है जिसमें एक ही समय पर DOM में पहले और बाद की, दोनों स्थिति होती है. साथ ही, चीज़ें ट्री में इस तरह से घूम सकती हैं कि वह विज़ुअल तौर पर देखने में आसान हो, लेकिन इसकी वजह से पढ़ने की पोज़िशन और फ़ोकस गायब हो सकते हैं.

जब स्क्रोल की पोज़िशन में दोनों स्थितियां अलग-अलग होती हैं, तो स्थिति में होने वाले बदलावों को हैंडल करना खास तौर पर चुनौती भरा होता है. साथ ही, अगर कोई एलिमेंट एक कंटेनर से दूसरे कंटेनर में जा रहा है, तो आपको overflow: hidden और क्लिपिंग के अन्य तरीकों में समस्याएं आ सकती हैं. इसका मतलब है कि पसंद के मुताबिक नतीजे पाने के लिए, आपको अपनी सीएसएस को फिर से तैयार करना होगा.

यह नामुमकिन नहीं है, यह बहुत ही मुश्किल है.

व्यू ट्रांज़िशन, आपको स्थितियों के बीच ओवरलैप किए बिना अपने डीओएम में बदलाव करने की सुविधा देता है. हालांकि, स्नैपशॉट वाले व्यू का इस्तेमाल करके, स्थितियों के बीच ट्रांज़िशन वाला ऐनिमेशन बनाएं.

इसके अलावा, हालांकि, मौजूदा समय में लागू किया जाने वाला, एक पेज वाले ऐप्लिकेशन (एसपीए) को टारगेट करता है, लेकिन इस सुविधा को बढ़ाया जाएगा, ताकि पूरे पेज लोड के बीच ट्रांज़िशन की अनुमति मिल सके. हालांकि, फ़िलहाल ऐसा करना मुमकिन नहीं है.

मानक तय करने की स्थिति

इस सुविधा को W3C सीएसएस वर्किंग ग्रुप में, ड्राफ़्ट की खास बातों के तौर पर डेवलप किया जा रहा है.

एपीआई के डिज़ाइन तैयार हो जाने के बाद, हम इस सुविधा को ठीक करने के लिए ज़रूरी प्रोसेस और जांच शुरू कर देंगे.

डेवलपर के सुझाव, शिकायत या राय हमारे लिए बहुत ज़रूरी हैं. इसलिए, कृपया सुझावों और सवालों के साथ GitHub पर समस्याएं दर्ज करें.

सबसे आसान ट्रांज़िशन: क्रॉस-फ़ेड

डिफ़ॉल्ट व्यू ट्रांज़िशन, क्रॉस-फ़ेड होता है, इसलिए यह एपीआई के बारे में अच्छी तरह से बताता है:

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

जहां updateTheDOMSomehow, DOM को नई स्थिति में बदल देता है. यह आपकी पसंद के मुताबिक किया जा सकता है: एलिमेंट को जोड़ना/हटाना, क्लास के नाम बदलना, स्टाइल बदलना... इससे कोई फ़र्क़ नहीं पड़ता.

और इसी तरह, पेज क्रॉस-फ़ेड हो जाते हैं:

डिफ़ॉल्ट क्रॉस-फ़ेड. कम से कम डेमो. सोर्स.

ठीक है, क्रॉस-फ़ेड आकर्षक नहीं है. अच्छी बात यह है कि ट्रांज़िशन को पसंद के मुताबिक बनाया जा सकता है. हालांकि, इस पर काम करने से पहले हमें यह समझना होगा कि यह बेसिक क्रॉस-फ़ेड कैसे काम करता है.

ये ट्रांज़िशन कैसे काम करते हैं

ऊपर से कोड सैंपल लिया जा रहा है:

document.startViewTransition(() => updateTheDOMSomehow(data));

जब .startViewTransition() को कॉल किया जाता है, तो एपीआई, पेज की मौजूदा स्थिति को कैप्चर करता है. जैसे, स्क्रीनशॉट लेना.

इसके बाद, .startViewTransition() को पास किया गया कॉलबैक कॉल किया जाता है. यहां पर DOM बदल दिया जाता है. इसके बाद, एपीआई पेज की नई स्थिति को कैप्चर करता है.

स्टेट को कैप्चर करने के बाद, एपीआई इस तरह का स्यूडो-एलिमेंट ट्री बनाता है:

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

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

::view-transition-old(root), पुराने व्यू का स्क्रीनशॉट है और ::view-transition-new(root) नए व्यू का लाइव वर्शन है. दोनों सीएसएस 'बदले गए कॉन्टेंट' के तौर पर रेंडर होती हैं (जैसे कि <img>).

पुराना व्यू, opacity: 1 से opacity: 0 पर ऐनिमेट होता है, जबकि नया व्यू opacity: 0 से opacity: 1 में ऐनिमेट होता है और क्रॉस-फ़ेड बनाता है.

सभी ऐनिमेशन सीएसएस ऐनिमेशन का इस्तेमाल करके परफ़ॉर्म किए जाते हैं, इसलिए उन्हें सीएसएस की मदद से पसंद के मुताबिक बनाया जा सकता है.

आसानी से पसंद के मुताबिक बनाने की सुविधा

ऊपर दिए गए सभी झूठे एलिमेंट को सीएसएस से टारगेट किया जा सकता है. साथ ही, ऐनिमेशन को सीएसएस का इस्तेमाल करके तय किया जाता है. इसलिए, मौजूदा सीएसएस ऐनिमेशन प्रॉपर्टी का इस्तेमाल करके उनमें बदलाव किया जा सकता है. उदाहरण के लिए:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 5s;
}

इस एक बदलाव के साथ, फ़ेड अब बहुत धीमा हो जाता है:

लॉन्ग क्रॉस-फ़ेड. कम से कम डेमो. सोर्स.

ठीक है, यह अब भी आकर्षक नहीं है. इसके बजाय, चलिए मटीरियल डिज़ाइन के शेयर किए गए ऐक्सिस ट्रांज़िशन को लागू करते हैं:

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

नतीजा यह रहा:

शेयर किए गए ऐक्सिस का ट्रांज़िशन. कम से कम डेमो. सोर्स.

एक से ज़्यादा एलिमेंट का ट्रांज़िशन

पिछले डेमो में, शेयर किए गए ऐक्सिस के ट्रांज़िशन में पूरा पेज शामिल था. यह ज़्यादातर पेज के लिए काम करता है, लेकिन यह शीर्षक के लिए ठीक नहीं लगता, क्योंकि यह फिर से पीछे स्लाइड करने के लिए स्लाइड करता है.

इससे बचने के लिए, पेज के बाकी हिस्से से हेडर को निकाला जा सकता है, ताकि उसे अलग से ऐनिमेट किया जा सके. ऐसा करने के लिए, एलिमेंट को view-transition-name असाइन किया जाता है.

.main-header {
  view-transition-name: main-header;
}

view-transition-name का मान आपकी पसंद के मुताबिक हो सकता है (none को छोड़कर, जिसका मतलब है कि कोई ट्रांज़िशन नाम नहीं है). इसका इस्तेमाल, ट्रांज़िशन में एलिमेंट की खास तरीके से पहचान करने के लिए किया जाता है.

इसका नतीजा यह होता है:

फ़िक्स्ड हेडर के साथ शेयर किए गए ऐक्सिस का ट्रांज़िशन. कम से कम डेमो. सोर्स.

अब हेडर अपनी जगह पर रहता है और क्रॉस-फ़ेड हो जाता है.

सीएसएस के इस एलान की वजह से, स्यूडो-एलिमेंट ट्री बदल गया है:

::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)

अब दो ट्रांज़िशन ग्रुप हैं. एक हेडर के लिए और दूसरा हेडर के लिए. इन्हें सीएसएस की मदद से और अलग-अलग ट्रांज़िशन के हिसाब से टारगेट किया जा सकता है. हालांकि, इस मामले में main-header पर डिफ़ॉल्ट ट्रांज़िशन बचा था, जो कि क्रॉस-फ़ेड है.

ठीक है, डिफ़ॉल्ट ट्रांज़िशन सिर्फ़ क्रॉस फ़ेड ही नहीं है, बल्कि ::view-transition-group भी ट्रांज़िशन करता है:

  • transform का इस्तेमाल करके, पोज़िशन और ट्रांसफ़ॉर्म करें
  • चौड़ाई
  • ऊंचाई

अब तक, इससे कोई फ़र्क़ नहीं पड़ता, क्योंकि DOM बदलाव के दोनों तरफ़ हेडर एक जैसा होता है और उसकी पोज़िशन एक जैसी होती है. हालांकि, हम हेडर में मौजूद टेक्स्ट को भी एक्सट्रैक्ट कर सकते हैं:

.main-header-text {
  view-transition-name: main-header-text;
  width: fit-content;
}

fit-content का इस्तेमाल इसलिए किया जाता है, ताकि एलिमेंट को बाकी चौड़ाई के बजाय टेक्स्ट का साइज़ दिखे. इसके बिना, बैक ऐरो हेडर के टेक्स्ट एलिमेंट का साइज़ कम कर देता है, जबकि हम चाहते हैं कि दोनों पेजों पर इसका साइज़ एक जैसा हो.

अब हमारे पास तीन हिस्से हैं:

::view-transition
├─ ::view-transition-group(root)
│  └─ …
├─ ::view-transition-group(main-header)
│  └─ …
└─ ::view-transition-group(main-header-text)
   └─ …

लेकिन, फिर से डिफ़ॉल्ट सेटिंग तय करें:

हेडर टेक्स्ट को स्लाइड करना. कम से कम डेमो. सोर्स.

अब हेडिंग टेक्स्ट, 'वापस जाएं' बटन के लिए जगह बनाने के लिए अच्छी तरह से स्लाइड करता है.

ट्रांज़िशन डीबग करना

व्यू ट्रांज़िशन को सीएसएस ऐनिमेशन के सबसे ऊपर बनाया गया है. इसलिए, Chrome DevTools में ऐनिमेशन पैनल, ट्रांज़िशन को डीबग करने के लिए सबसे अच्छा है.

ऐनिमेशन पैनल का इस्तेमाल करके, अगला ऐनिमेशन रोका जा सकता है. इसके बाद, ऐनिमेशन में आगे-पीछे स्क्रब किया जा सकता है. इस दौरान, ट्रांज़िशन के छद्म-एलिमेंट, एलिमेंट पैनल में देखे जा सकते हैं.

Chrome Dev टूल की मदद से, व्यू ट्रांज़िशन को डीबग करना.

ट्रांज़िशन किए जा रहे एलिमेंट का एक ही डीओएम एलिमेंट होना ज़रूरी नहीं है

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

उदाहरण के लिए, मुख्य वीडियो एम्बेड को view-transition-name दिया जा सकता है:

.full-embed {
  view-transition-name: full-embed;
}

फिर, जब थंबनेल पर क्लिक किया जाता है, तब उसे ट्रांज़िशन के दौरान वही view-transition-name दिया जा सकता है:

thumbnail.onclick = async () => {
  thumbnail.style.viewTransitionName = 'full-embed';

  document.startViewTransition(() => {
    thumbnail.style.viewTransitionName = '';
    updateTheDOMSomehow();
  });
};

नतीजा:

एक एलिमेंट का दूसरे पर ट्रांज़िशन हो रहा है. कम से कम डेमो. सोर्स.

थंबनेल अब मुख्य इमेज में बदल जाएगा. भले ही, ये सैद्धांतिक तौर पर (और वाकई) अलग-अलग एलिमेंट हैं, लेकिन ट्रांज़िशन एपीआई इन्हें एक जैसा ही मानता है, क्योंकि दोनों के साथ एक ही view-transition-name शेयर किया गया है.

इसके लिए असल कोड ऊपर दिए गए उदाहरण से थोड़ा मुश्किल है, क्योंकि यह थंबनेल पेज पर वापस होने वाले ट्रांज़िशन को भी हैंडल करता है. लागू करने की पूरी जानकारी के लिए स्रोत देखें.

कस्टम एंट्री और एग्ज़िट ट्रांज़िशन

इस उदाहरण पर एक नज़र डालें:

साइडबार में डालना और उससे बाहर निकलना. कम से कम डेमो. सोर्स.

साइडबार, ट्रांज़िशन का हिस्सा है:

.sidebar {
  view-transition-name: sidebar;
}

हालांकि, पिछले उदाहरण में दिए गए हेडर के उलट, साइडबार सभी पेजों पर नहीं दिखता है. अगर दोनों राज्यों में साइडबार मौजूद है, तो ट्रांज़िशन के छद्म-एलिमेंट इस तरह दिखेंगे:

::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
   └─ ::view-transition-image-pair(sidebar)
      ├─ ::view-transition-old(sidebar)
      └─ ::view-transition-new(sidebar)

हालांकि, अगर साइडबार सिर्फ़ नए पेज पर है, तो ::view-transition-old(sidebar) स्यूडो-एलिमेंट वहां नहीं होगा. साइडबार के लिए कोई 'पुरानी' इमेज नहीं है, इसलिए इमेज-पेयर में सिर्फ़ एक ::view-transition-new(sidebar) होगा. इसी तरह, अगर साइडबार सिर्फ़ पुराने पेज पर है, तो इमेज-पेयर में सिर्फ़ ::view-transition-old(sidebar) होगा.

ऊपर दिए गए डेमो में, साइडबार का ट्रांज़िशन अलग-अलग तरह से हो सकता है. यह इस बात पर निर्भर करता है कि यह दिख रहा है, बाहर निकल रहा है या फिर दोनों स्थितियों में मौजूद है. यह दाईं से स्लाइड करके अंदर जाता है और फ़ेड होकर बाहर निकल जाता है. ऐसा करने के लिए, वह दाईं ओर स्लाइड करता है और फ़ेड आउट होता है. हालांकि, यह दोनों स्थितियों में मौजूद होने पर अपनी जगह पर ही रहता है.

कोई खास एंट्री और एग्ज़िट ट्रांज़िशन बनाने के लिए, :only-child स्यूडो क्लास का इस्तेमाल करें. इससे पुराने/नए सूडो एलिमेंट को टारगेट किया जा सकता है. ऐसा तब किया जा सकता है, जब इमेज से जुड़ा यह इकलौता चाइल्ड एलिमेंट हो:

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

इस मामले में, दोनों स्थितियों में साइडबार के मौजूद होने पर कोई खास ट्रांज़िशन नहीं होता है. ऐसा इसलिए, क्योंकि डिफ़ॉल्ट तौर पर सेट ही सही रहेगा.

एक साथ काम नहीं करने वाले डीओएम अपडेट और कॉन्टेंट का इंतज़ार किया जा रहा है

.startViewTransition() को पास किया गया कॉलबैक, प्रॉमिस को रिटर्न कर सकता है. इससे DOM अपडेट को एक साथ सिंक किया जा सकता है और ज़रूरी कॉन्टेंट के तैयार होने का इंतज़ार किया जा सकता है.

document.startViewTransition(async () => {
  await something;
  await updateTheDOMSomehow();
  await somethingElse;
});

प्रॉमिस पूरा होने तक ट्रांज़िशन शुरू नहीं होगा. इस दौरान, पेज फ़्रीज़ हो जाता है. इसलिए, पेज लोड होने में लगने वाले समय को कम से कम पर सेट करना चाहिए. खास तौर पर, नेटवर्क फ़ेच .startViewTransition() को कॉल करने से पहले किया जाना चाहिए, जबकि पेज अब भी पूरी तरह से इंटरैक्टिव है, न कि .startViewTransition() कॉलबैक के हिस्से के तौर पर.

अगर आपने इमेज या फ़ॉन्ट के तैयार होने का इंतज़ार किया है, तो पक्का करें कि आपने टाइम आउट को ज़्यादा समय तक प्रोसेस करने का विकल्प चुना है:

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

हालांकि, कुछ मामलों में ऐसा करने से पूरी तरह बचा जा सकता है और पहले से मौजूद कॉन्टेंट का ही इस्तेमाल किया जा सकता है.

पहले से मौजूद कॉन्टेंट का ज़्यादा से ज़्यादा फ़ायदा पाना

ऐसे मामले में, जहां थंबनेल बड़ी इमेज पर बदलता है:

डिफ़ॉल्ट ट्रांज़िशन, क्रॉस-फ़ेड होता है. इसका मतलब है कि थंबनेल, अभी तक लोड न की गई पूरी इमेज के साथ क्रॉस-फ़ेड हो सकता है.

इसे करने का एक तरीका यह है कि ट्रांज़िशन शुरू करने से पहले, पूरी इमेज के लोड होने का इंतज़ार करें. आम तौर पर, यह काम .startViewTransition() को कॉल करने से पहले किया जाता है, ताकि पेज इंटरैक्टिव बना रहे. साथ ही, उपयोगकर्ता को यह बताने के लिए एक स्पिनर दिखाया जा सकता है कि चीज़ें लोड हो रही हैं. हालांकि, इस मामले में एक बेहतर तरीका है:

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

अब थंबनेल फ़ेड नहीं होता, यह पूरी इमेज के नीचे रहता है. इसका मतलब है कि अगर नया व्यू लोड नहीं होता है, तो ट्रांज़िशन के दौरान थंबनेल दिखेगा. इसका मतलब है कि ट्रांज़िशन तुरंत शुरू हो सकता है और पूरी इमेज अपने-आप लोड हो सकती है.

अगर नए व्यू में पारदर्शिता को दिखाया जाता है, तो यह काम नहीं करेगा. हालांकि, इस मामले में हम जानते हैं कि ऐसा नहीं है, इसलिए हम यह ऑप्टिमाइज़ेशन कर सकते हैं.

आसपेक्ट रेशियो में बदलाव मैनेज करना

आसानी से, अब तक के सभी ट्रांज़िशन एक जैसे आसपेक्ट रेशियो वाले एलिमेंट पर किए गए हैं. हालांकि, हमेशा ऐसा नहीं होगा. अगर थंबनेल 1:1 और मुख्य इमेज 16:9 है, तो क्या होगा?

एक एलिमेंट को दूसरे एलिमेंट में ट्रांज़िशन किया जा रहा है और आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) में बदलाव होगा. कम से कम डेमो. सोर्स.

डिफ़ॉल्ट ट्रांज़िशन में, ग्रुप का ऐनिमेशन, पहले के साइज़ से लेकर बाद के साइज़ में बदल जाता है. पुराने और नए व्यू, ग्रुप की 100% चौड़ाई और ऑटो लंबाई हैं. इसका मतलब है कि ग्रुप का साइज़ चाहे कुछ भी हो, आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) को बनाए रखा जाता है.

यह एक अच्छा डिफ़ॉल्ट तरीका है, लेकिन इस मामले में हम ऐसा नहीं करना चाहते. इसलिए:

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

इसका मतलब है कि चौड़ाई बढ़ने पर थंबनेल, एलिमेंट के बीच में दिखता है. हालांकि, 1:1 से 16:9 में बदलने पर, पूरी इमेज 'अन-क्रॉप' होती है.

डिवाइस की स्थिति के हिसाब से ट्रांज़िशन में बदलाव करना

हो सकता है कि आप मोबाइल बनाम डेस्कटॉप पर अलग-अलग ट्रांज़िशन का इस्तेमाल करना चाहें. उदाहरण के लिए, यह उदाहरण मोबाइल पर पूरी स्लाइड को साइड से पूरी तरह स्लाइड करता है, लेकिन डेस्कटॉप पर छोटी स्लाइड करता है:

एक एलिमेंट का दूसरे पर ट्रांज़िशन हो रहा है. कम से कम डेमो. सोर्स.

इसे सामान्य मीडिया क्वेरी का इस्तेमाल करके हासिल किया जा सकता है:

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

मिलती-जुलती मीडिया क्वेरी के आधार पर, आपके पास उन एलिमेंट को बदलने का भी विकल्प है जिन्हें view-transition-name असाइन किया जाता है.

'घटा हुआ मोशन' प्राथमिकता पर प्रतिक्रिया देना

उपयोगकर्ता अपने ऑपरेटिंग सिस्टम के ज़रिए यह बता सकते हैं कि उन्हें कम मोशन का इस्तेमाल करना है और यह प्राथमिकता सीएसएस के ज़रिए दिखाई जाती है.

आपके पास इन उपयोगकर्ताओं के लिए, किसी भी तरह का ट्रांज़िशन रोकने का विकल्प है:

@media (prefers-reduced-motion) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

हालांकि, 'कम मोशन' की प्राथमिकता का मतलब यह नहीं है कि उपयोगकर्ता कोई मोशन नहीं चाहता. ऊपर दिए गए तरीके के बजाय, आपके पास ज़्यादा शानदार ऐनिमेशन चुनने का विकल्प है, लेकिन ऐसा ऐनिमेशन जो अब भी एलिमेंट और डेटा के फ़्लो के बीच संबंध को दिखाता है.

नेविगेशन के टाइप के आधार पर ट्रांज़िशन में बदलाव करना

कभी-कभी एक खास तरह के पेज से दूसरे पेज पर जाने के दौरान, नेविगेशन में खास तरह से बदलाव किया जाना चाहिए. इसके अलावा, 'वापस जाएं' वाला नेविगेशन, 'फ़ॉरवर्ड' नेविगेशन से अलग होना चाहिए.

'वापस' जाने के दौरान अलग-अलग ट्रांज़िशन. कम से कम डेमो. सोर्स.

इन मामलों को हैंडल करने का सबसे अच्छा तरीका यह है कि <html> पर क्लास का नाम सेट किया जाए, जिसे दस्तावेज़ का एलिमेंट भी कहा जाता है:

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

इस उदाहरण में, transition.finished का इस्तेमाल किया गया है. यह एक प्रॉमिस है, जो ट्रांज़िशन के खत्म होने के बाद रिज़ॉल्व हो जाता है. इस ऑब्जेक्ट की अन्य प्रॉपर्टी, एपीआई के रेफ़रंस में शामिल हैं.

अब ट्रांज़िशन बदलने के लिए, अपनी सीएसएस में उस क्लास के नाम का इस्तेमाल किया जा सकता है:

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

मीडिया क्वेरी की तरह ही, इन क्लास की मौजूदगी का इस्तेमाल यह बदलने के लिए भी किया जा सकता है कि किन एलिमेंट को view-transition-name मिलेगा.

अन्य ऐनिमेशन को फ़्रीज़ किए बिना ट्रांज़िशन किया जा रहा है

वीडियो ट्रांज़िशन की पोज़िशन का यह डेमो देखें:

वीडियो ट्रांज़िशन. कम से कम डेमो. सोर्स.

क्या आपको कोई गड़बड़ी मिली? अगर आपने नहीं देखा है, तो चिंता न करें. यहां, इसकी रफ़्तार कम हो गई है:

वीडियो ट्रांज़िशन, धीमा. कम से कम डेमो. सोर्स.

ट्रांज़िशन के दौरान, वीडियो फ़्रीज़ हो जाता है. इसके बाद, वीडियो चलने वाला वर्शन धुंधला हो जाता है. ऐसा इसलिए होता है, क्योंकि ::view-transition-old(video) पुराने व्यू का स्क्रीनशॉट है, जबकि ::view-transition-new(video) नए व्यू का लाइव इमेज है.

आप इसे ठीक कर सकते हैं, लेकिन पहले खुद से पूछें कि क्या इसे ठीक करना सही होगा. जब ट्रांज़िशन अपनी सामान्य रफ़्तार पर चल रहा था, तब अगर आपको यह समस्या नहीं दिखे, तो मुझे इसे बदलने में कोई परेशानी नहीं होगी.

अगर आपको वाकई इस समस्या को ठीक करना है, तो ::view-transition-old(video) न दिखाएं. सीधे ::view-transition-new(video) पर स्विच करें. डिफ़ॉल्ट स्टाइल और ऐनिमेशन को बदलकर ऐसा किया जा सकता है:

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

बस, हो गया।

वीडियो ट्रांज़िशन, धीमा. कम से कम डेमो. सोर्स.

अब वीडियो, ट्रांज़िशन के दौरान चलता है.

JavaScript का इस्तेमाल करके ऐनिमेट करना

अब तक, सभी ट्रांज़िशन को सीएसएस का इस्तेमाल करके तय किया गया है. हालांकि, कभी-कभी सीएसएस काफ़ी नहीं है:

सर्कल ट्रांज़िशन. कम से कम डेमो. सोर्स.

इस ट्रांज़िशन के कुछ हिस्सों को सिर्फ़ सीएसएस का इस्तेमाल करके पूरा नहीं किया जा सकता:

  • ऐनिमेशन, क्लिक करने की जगह से शुरू होता है.
  • ऐनिमेशन, उस सर्कल के साथ खत्म होता है जिसमें सबसे दूर के कोने तक का दायरा होता है. हालांकि, हमें उम्मीद है कि आने वाले समय में सीएसएस के साथ ऐसा किया जा सकेगा.

अच्छी बात यह है कि आप Web Animation API का इस्तेमाल करके, ट्रांज़िशन तैयार कर सकते हैं!

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)',
      }
    );
  });
}

इस उदाहरण में, transition.ready का इस्तेमाल किया गया है. यह एक प्रॉमिस है. यह ट्रांज़िशन छद्म एलिमेंट के बनने के बाद हट जाता है. इस ऑब्जेक्ट की अन्य प्रॉपर्टी, एपीआई के रेफ़रंस में शामिल हैं.

बेहतर बनाने की सुविधा के तौर पर ट्रांज़िशन

व्यू ट्रांज़िशन एपीआई को डीओएम में हुए बदलाव को 'रैप' करने और उसके लिए ट्रांज़िशन बनाने के लिए डिज़ाइन किया गया है. हालांकि, अगर डीओएम में बदलाव हो जाता है, लेकिन ट्रांज़िशन पूरा नहीं हो पाता है, तो आपके ऐप्लिकेशन को 'गड़बड़ी' की स्थिति में नहीं डालना चाहिए. आम तौर पर, ट्रांज़िशन पूरा नहीं होता. हालांकि, अगर ऐसा होता है, तो उपयोगकर्ता अनुभव को खराब नहीं होना चाहिए.

ट्रांज़िशन को बेहतर बनाने के लिए, इस बात का ध्यान रखें कि ट्रांज़िशन के वादों का इस्तेमाल इस तरह न करें कि ट्रांज़िशन पूरा न होने पर, आपका ऐप्लिकेशन काम करना बंद कर दे.

यह न करें
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)',
    }
  );
}

इस उदाहरण में समस्या यह है कि अगर ट्रांज़िशन की स्थिति ready पर नहीं पहुंचती, तो switchView() अस्वीकार कर देगा. हालांकि, इसका मतलब यह नहीं है कि व्यू स्विच नहीं हो सका. हो सकता है कि डीओएम अपडेट हो गया हो, लेकिन डुप्लीकेट view-transition-name होने की वजह से ट्रांज़िशन स्किप कर दिया गया था.

इसके बजाय:

ऐसा करें
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
  }
}

इस उदाहरण में, DOM अपडेट का इंतज़ार करने के लिए transition.updateCallbackDone का इस्तेमाल किया गया है. अगर ऐसा नहीं होता है, तो उसे अस्वीकार किया जाता है. ट्रांज़िशन पूरा न होने पर, switchView अस्वीकार नहीं करता. DOM अपडेट पूरा होने पर यह ठीक हो जाता है और पूरा न होने पर अस्वीकार कर दिया जाता है.

अगर आपको यह सेट करना है कि नए व्यू के 'सेट किए जाने' के बाद, switchView का समाधान हो जाए, जैसे कि ऐनिमेशन वाला कोई ट्रांज़िशन पूरा हो गया हो या उसे आखिर तक स्किप कर दिया गया हो, तो transition.updateCallbackDone को transition.finished से बदलें.

पॉलीफ़िल नहीं, लेकिन...

मुझे नहीं लगता कि इस सुविधा को किसी भी तरह से पॉलीफ़िल किया जा सकता है. हालांकि, मुझे यह गलत साबित करने में खुशी होगी!

हालांकि, यह हेल्पर फ़ंक्शन उन ब्राउज़र में चीज़ों को बहुत आसान बना देता है जिनमें व्यू ट्रांज़िशन की सुविधा काम नहीं करती है:

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

और इसका इस्तेमाल इस तरह किया जा सकता है:

function spaNavigate(data) {
  const classNames = isBackNavigation ? ['back-transition'] : [];

  const transition = transitionHelper({
    classNames,
    updateDOM() {
      updateTheDOMSomehow(data);
    },
  });

  // …
}

जो ब्राउज़र व्यू ट्रांज़िशन की सुविधा के साथ काम नहीं करते उनमें भी updateDOM को कॉल किया जाएगा. हालांकि, ऐनिमेशन वाला ट्रांज़िशन नहीं होगा.

ट्रांज़िशन के दौरान, <html> में जोड़ने के लिए कुछ classNames भी दिए जा सकते हैं. इससे नेविगेशन के टाइप के हिसाब से ट्रांज़िशन को बदलना आसान हो जाएगा.

अगर आपको ऐनिमेशन नहीं चाहिए, तो true को skipTransition पर भी पास किया जा सकता है. ऐसा उन ब्राउज़र में भी किया जा सकता है जिनमें व्यू ट्रांज़िशन का इस्तेमाल किया जा सकता है. यह तरीका तब काम आता है, जब आपकी साइट पर ट्रांज़िशन बंद करने के लिए उपयोगकर्ता की पसंद मौजूद हो.

फ़्रेमवर्क के साथ काम करना

अगर किसी ऐसी लाइब्रेरी या फ़्रेमवर्क के साथ काम किया जा रहा है जो डीओएम बदलावों को हटा देता है, तो डीओएम बदलाव पूरा होने का पता लगाना मुश्किल होता है. यहां अलग-अलग फ़्रेमवर्क में ऊपर बताए गए हेल्पर का इस्तेमाल करके उदाहरणों का एक सेट दिया गया है.

  • प्रतिक्रिया—यहां सबसे अहम चीज़ flushSync है. यह स्थिति में होने वाले किसी भी बदलाव को एक साथ लागू करती है. हां, उस एपीआई का इस्तेमाल करने के बारे में एक बड़ी चेतावनी दी जाती है. हालांकि, Dan Abramov मुझे इस बात का भरोसा दिलाता है कि इस मामले में यह सही है. हमेशा की तरह, प्रतिक्रिया और एक साथ काम नहीं करने वाले कोड के साथ, startViewTransition से मिले अलग-अलग प्रॉमिस का इस्तेमाल करते समय इस बात का ध्यान रखें कि आपका कोड सही स्थिति के साथ चल रहा हो.
  • Vue.js—यहां दी गई कुंजी nextTick है, जो डीओएम के अपडेट होने के बाद काम करती है.
  • Svelte—यह Vue से काफ़ी मिलता-जुलता है, लेकिन अगले बदलाव का इंतज़ार करने का तरीका tick है.
  • Lit—यहां सबसे अहम चीज़, कॉम्पोनेंट में दिया गया this.updateComplete प्रॉमिस है. यह डीओएम के अपडेट होने के बाद पूरी होती है.
  • Angular—यहां applicationRef.tick कुंजी है, जो बचे हुए DOM बदलावों को फ़्लश करती है. Angular के वर्शन 17 में, @angular/router के साथ मिलने वाले withViewTransitions का इस्तेमाल किया जा सकता है.

एपीआई का संदर्भ

const viewTransition = document.startViewTransition(updateCallback)

नया ViewTransition शुरू करें.

दस्तावेज़ की मौजूदा स्थिति कैप्चर होने के बाद, updateCallback को कॉल किया जाता है.

इसके बाद, जब updateCallback से मिला प्रॉमिस पूरा हो जाता है, तो ट्रांज़िशन अगले फ़्रेम में शुरू होता है. अगर updateCallback से मिला प्रॉमिस अस्वीकार कर दिया जाता है, तो ट्रांज़िशन को छोड़ दिया जाता है.

ViewTransition के सदस्य:

viewTransition.updateCallbackDone

ऐसा प्रॉमिस जो updateCallback के दिए गए प्रॉमिस को पूरा कर लेता है या अस्वीकार करने पर खारिज कर देता है.

व्यू ट्रांज़िशन एपीआई, डीओएम में हुए बदलाव को रैप करता है और ट्रांज़िशन बनाता है. हालांकि, कभी-कभी ट्रांज़िशन ऐनिमेशन की सफलता/असफलता की अहमियत पर ध्यान नहीं दिया जाता. आपको सिर्फ़ यह जानना है कि डीओएम में बदलाव होता है या नहीं और ऐसा कब होता है. updateCallbackDone का इस्तेमाल इस मामले में किया जा सकता है.

viewTransition.ready

यह प्रॉमिस, ट्रांज़िशन के लिए छद्म एलिमेंट के बनने के बाद पूरा होता है. इसके बाद, ऐनिमेशन शुरू होने वाला होता है.

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

यह JavaScript की मदद से, ट्रांज़िशन के छद्म-एलिमेंट को ऐनिमेट करने के लिए फ़ायदेमंद होता है.

viewTransition.finished

ऐसा प्रॉमिस जो एंड स्टेटस के पूरा होने के बाद उपयोगकर्ता को पूरी तरह से दिखने और इंटरैक्टिव होने के बाद पूरा होता है.

यह सिर्फ़ तब अस्वीकार करता है, जब updateCallback अस्वीकार किया गया प्रॉमिस देता है. ऐसा इसलिए, क्योंकि इससे पता चलता है कि प्रॉमिस 'खत्म होने का समय' नहीं बनाया गया था.

अगर ऐसा न हो, तो अगर कोई ट्रांज़िशन शुरू नहीं होता या ट्रांज़िशन के दौरान उसे स्किप कर दिया जाता है, तो भी एंड स्थिति खत्म होती है. इसलिए, finished पूरा करता है.

viewTransition.skipTransition()

ट्रांज़िशन के ऐनिमेशन वाले हिस्से को छोड़ें.

इससे updateCallback को कॉल नहीं किया जाएगा, क्योंकि डीओएम में किया गया बदलाव, ट्रांज़िशन से अलग होता है.

डिफ़ॉल्ट स्टाइल और ट्रांज़िशन रेफ़रंस

::view-transition
रूट स्यूडो एलिमेंट, जो व्यूपोर्ट को भरता है और इसमें हर ::view-transition-group शामिल होता है.
::view-transition-group

बिलकुल सही जगह पर.

'पहले' और 'बाद' की स्थितियों के बीच width और height का ट्रांज़िशन.

व्यूपोर्ट-स्पेस क्वाड के 'पहले' और 'बाद' के बीच transform ट्रांज़िशन.

::view-transition-image-pair

ग्रुप को भरने के लिए बिलकुल तैयार है.

इसमें isolation: isolate का इस्तेमाल किया गया है, ताकि पुराने और नए व्यू पर plus-lighter ब्लेंड मोड के असर को सीमित किया जा सके.

::view-transition-new और ::view-transition-old

रैपर के सबसे ऊपर बाईं ओर मौजूद है.

ग्रुप की चौड़ाई को 100% भरता है, लेकिन इसकी लंबाई अपने-आप सेट हो जाती है. इसलिए, यह ग्रुप को भरने के बजाय, आसपेक्ट रेशियो को बनाए रखेगा.

ट्रू क्रॉस-फ़ेड की अनुमति देने के लिए mix-blend-mode: plus-lighter मौजूद है.

पुराना व्यू, opacity: 1 से opacity: 0 में बदल जाएगा. नया व्यू, opacity: 0 से opacity: 1 में बदल जाएगा.

सुझाव/राय दें या शिकायत करें

इस चरण में डेवलपर के सुझाव काफ़ी अहम होते हैं. इसलिए, कृपया सुझावों और सवालों के साथ GitHub पर समस्याएं दर्ज करें.