स्क्रोल करने पर दिखने वाला पैरालैक्स इफ़ेक्ट बनाना

वेब की सुविधाएं: स्क्रोल-ड्रिवन ऐनिमेशन

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

इन्हें कैसे लागू करें

यहां बेसिक पैरालैक्स इफ़ेक्ट बनाने का तरीका बताया गया है:

  1. रैपर एलिमेंट बनाना: यह एलिमेंट, पैरालैक्स इफ़ेक्ट की सभी लेयर को एक साथ ग्रुप करता है. यह स्क्रोल किया जा सकने वाला एलिमेंट नहीं है. इसलिए, इसके ओवरफ़्लो को क्लिप किया जाना चाहिए. साथ ही, इसे ऐसी height दें जो पैरालैक्स इफ़ेक्ट की किसी एक लेयर की ऊंचाई से मेल खाती हो.

    <div class="wrapper">
      …
    </div>
    
    .wrapper {
      overflow: clip;
      height: 100vh; /* Height of one of the layers of the parallax */
    }
    
  2. लेयर तय करना: रैपर के अंदर, अलग-अलग लेयर जोड़ें. ये लेयर अलग-अलग स्पीड से मूव करेंगी.

    <div class="wrapper">
      <div class="layer">LAYER 0</div>
      <div class="layer">LAYER 1</div>
      <div class="layer">LAYER 2</div>
      …
    </div>
    
  3. ट्रांसलेट ऐनिमेशन जोड़ना: ऐसा सीएसएस ऐनिमेशन तय करें जो लेयर की transform प्रॉपर्टी को बदलता हो. पैरालैक्स इफ़ेक्ट के लिए, आम तौर पर translateY का इस्तेमाल करके लेयर को वर्टिकल तौर पर मूव किया जाता है.

    @keyframes parallax {
      from {
        transform: translateY(700px);
      }
    }
    
  4. view-timeline सेट अप करें: ऐनिमेशन को स्क्रोल पोज़िशन से लिंक करने के लिए, रैपर एलिमेंट पर view-timeline बनाएं. इसके बाद, इसे लेयर पर लागू करें.

    .wrapper {
      view-timeline: --wrapper;
    }
    
    .layer {
      animation: parallax linear both;
      animation-timeline: --wrapper;
    }
    
  5. ऐनिमेशन को अलग-अलग समय पर दिखाना: लेयर को अलग-अलग स्पीड से मूव करने के लिए, दो मुख्य तरीकों में से किसी एक का इस्तेमाल किया जा सकता है: कीफ़्रेम में अलग-अलग समय पर दिखाना या animation-range को अलग-अलग समय पर दिखाना.

    इन दोनों तरीकों में, हार्डकोड की गई वैल्यू या sibling-index()/sibling-count() लागू करने का तरीका इस्तेमाल किया जा सकता है. हार्डकोड की गई वैल्यू सबसे आसान होती हैं. साथ ही, ये तब भी काम आती हैं, जब आपके पास सिर्फ़ सीमित संख्या में लेयर हों. अगर आपके पास कई लेयर हैं, तो sibling-index()/sibling-count() को लागू करना आसान होता है.

    • कीफ़्रेम में स्टैगरिंग:

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

      .layer:nth-child(1) { --offset: 100px; }
      .layer:nth-child(2) { --offset: 200px; }
      .layer:nth-child(3) { --offset: 300px; }
      
      @keyframes parallax {
        from {
          transform: translateY(var(--offset));
        }
      }
      

      sibling-index() का इस्तेमाल करके, sibling-index() फ़ंक्शन को उसके सिबलिंग के बीच चाइल्ड एलिमेंट का इंडेक्स दिखाने दें, ताकि स्टैगर्ड इफ़ेक्ट का अपने-आप हिसाब लगाया जा सके.

      @keyframes parallax {
        from {
          transform: translateY(calc(100px * sibling-index()));
        }
      }
      
    • animation-range को अलग-अलग समय पर लॉन्च करना:

      हार्डकोड की गई वैल्यू का इस्तेमाल करके, हर लेयर पर animation-range की सीमाओं को अलग-अलग तय किया जा सकता है.

      .layer:nth-child(1) { animation-range: entry 25% exit 50%; }
      .layer:nth-child(2) { animation-range: entry 25% exit 75%; }
      .layer:nth-child(3) { animation-range: entry 25% exit 100%; }
      

      sibling-index() और sibling-count() का इस्तेमाल करके, लेयर की कुल संख्या (sibling-count()) के आधार पर रेंज का हिसाब लगाया जा सकता है.

      .layer {
        animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
      }
      

कोड का उदाहरण

@keyframes parallax {
  from {
    transform: translateY(calc(100px * sibling-index()));
  }
}

.wrapper {
  view-timeline: --wrapper;
}

.layer {
  animation: parallax linear both;
  animation-timeline: --wrapper;
}

@media (prefers-reduced-motion: reduce) {
  .layer {
    animation: none;
  }
}

इसके अलावा, animation-range प्रॉपर्टी का इस्तेमाल करके भी ऐसा ही असर पाया जा सकता है:

@keyframes parallax {
  from {
    transform: translateY(700px);
  }
}

.wrapper {
  view-timeline: --wrapper;
}

.layer {
  animation: parallax linear both;
  animation-timeline: --wrapper;
  animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
}

@media (prefers-reduced-motion: reduce) {
  .layer {
    animation: none;
  }
}

सबसे सही तरीके

स्क्रोल-ड्रिवन ऐनिमेशन का इस्तेमाल करते समय, कुछ सबसे सही तरीकों को अपनाना ज़रूरी है. इससे यह पक्का किया जा सकता है कि उपयोगकर्ताओं को बेहतर और सुलभ अनुभव मिले:

  • ज़रूर शामिल करें: सभी ब्राउज़र, स्क्रोल-ड्रिवन ऐनिमेशन के साथ काम नहीं करते. @supports ((animation-timeline: view()) and (animation-range: entry)) का इस्तेमाल करके देखें कि यह सुविधा काम करती है या नहीं. साथ ही, उन ब्राउज़र के लिए फ़ॉलबैक उपलब्ध कराएं जिन पर यह सुविधा काम नहीं करती.
    • यहां (animation-range: entry) की जांच ज़रूर शामिल की जानी चाहिए, ताकि उन ब्राउज़र को फ़िल्टर किया जा सके जो सिर्फ़ कुछ हद तक काम करते हैं.
    • फ़ॉलबैक रणनीति के लिए, scroll-timeline-polyfill पैकेज का इस्तेमाल न करें. ऐसा इसलिए, क्योंकि इसमें सभी सुविधाएं मौजूद नहीं हैं और इसमें कई समस्याएं हैं.
    • अगर ऐनिमेशन को सिर्फ़ सजावट के लिए इस्तेमाल किया जा रहा है, तो प्रोग्रेसिव एनहैंसमेंट का विकल्प चुनें और फ़ॉलबैक न दें.
  • उपयोगकर्ता की प्राथमिकताओं का ध्यान रखें: कुछ उपयोगकर्ताओं को वेब पर कम मोशन पसंद होता है. इन उपयोगकर्ताओं के लिए, ऐनिमेशन बंद करने या कम करने के लिए, prefers-reduced-motion मीडिया क्वेरी का इस्तेमाल करें.
  • सिर्फ़ अच्छी परफ़ॉर्मेंस वाली सीएसएस प्रॉपर्टी को ऐनिमेट करने की कोशिश करें: सबसे आसान ऐनिमेशन के लिए, ऐसी प्रॉपर्टी को ऐनिमेट करें जिन्हें ब्राउज़र के कंपोज़िटर थ्रेड से मैनेज किया जा सकता है. जैसे, transform और opacity. width या height जैसी अन्य प्रॉपर्टी को ऐनिमेट करने से, परफ़ॉर्मेंस से जुड़ी समस्याएं हो सकती हैं.
  • DO डिक्लेरेशन के सही क्रम का इस्तेमाल करें: animation शॉर्टहैंड प्रॉपर्टी का इस्तेमाल करते समय, animation-timeline और animation-range को इसके बाद डिक्लेयर करें. इससे शॉर्टहैंड को टाइमलाइन रीसेट करने से रोका जा सकेगा.

animation-range सेट करने के लिए:

  • क्या करें: सभी लेयर के लिए, एक ही स्टार्ट ऑफ़सेट दें. उदाहरण के लिए, entry 25%
  • सभी लेयर को अलग-अलग एंड ऑफ़सेट दें. इसके लिए, sibling-count() और sibling-index() का इस्तेमाल करके ऑफ़सेट को डिस्ट्रिब्यूट करें. उदाहरण के लिए, exit calc(100% / sibling-count() * sibling-index()).

ब्राउज़र से जुड़ी सहायता और फ़ॉलबैक रणनीतियां

इसलिए, आम तौर पर फ़ॉलबैक रणनीति की ज़रूरत होती है.

स्क्रोल-ड्रिवन ऐनिमेशन की सुविधा काम न करने वाले ब्राउज़र के लिए, फ़ॉलबैक का इस्तेमाल करके विज़ुअल इफ़ेक्ट फिर से बनाए जा सकते हैं. फ़ॉलबैक आम तौर पर, स्क्रोल लिसनर (ScrollTimeline इफ़ेक्ट के लिए) या IntersectionObserver API (ViewTimeline इफ़ेक्ट के लिए) की मदद से बनाए जाते हैं.

जिन ब्राउज़र में स्क्रोल-ड्रिवन ऐनिमेशन की सुविधा पहले से मौजूद होती है उनमें हमेशा नेटिव सीएसएस का इस्तेमाल करें. ऐसा इसलिए, क्योंकि वे ज़्यादा बेहतर तरीके से काम करते हैं.

ध्यान दें कि फ़ॉलबैक तरीके का इस्तेमाल करके, हर इफ़ेक्ट को फिर से नहीं बनाया जा सकता.

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

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
  const wrapper = document.querySelector('.wrapper');
  const layers = document.querySelectorAll('.layer');

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        window.addEventListener('scroll', onScroll);
      } else {
        window.removeEventListener('scroll', onScroll);
      }
    });
  }, { threshold: 0 });

  observer.observe(wrapper);

  function onScroll() {
    const scrollY = window.scrollY;
    const wrapperRect = wrapper.getBoundingClientRect();
    const wrapperTop = wrapperRect.top + scrollY;
    const wrapperHeight = wrapperRect.height;
    const windowHeight = window.innerHeight;

    if (scrollY >= wrapperTop - windowHeight && scrollY <= wrapperTop + wrapperHeight) {
      const scrollPercent = (scrollY - (wrapperTop - windowHeight)) / (wrapperHeight + windowHeight);
      
      layers.forEach((layer, index) => {
        // This matches the effect as defined in the CSS example above.
        // Customize this further if needed.
        const initialTranslateY = 100 * index;
        const translateY = initialTranslateY * (1 - scrollPercent);
        layer.style.transform = `translateY(${translateY}px)`;
      });
    }
  }

  // Trigger onScroll once to set initial positions
  onScroll();
}