इनफ़ाइनाइट स्क्रोलिंग

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

कई इनफ़ाइनाइट स्क्रोलिंग लागू करने में आने वाली सबसे आम समस्याओं में से एक यह है कि जब भी कोई नया आइटम जोड़ा जाता है, तो पेज फ़ुटर (या मिलता-जुलता UX एलिमेंट) पेज में नीचे की ओर धंसा जाता है. इनफ़ाइनाइट स्क्रोलिंग को लागू करने से, यह कभी नहीं होता.

बेहतर तरीके से बातचीत करना

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

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

लागू करना

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

  • listObserver, उस #infinite-scroll-button की पोज़िशन को ट्रैक करता है जो इनफ़ाइनाइट स्क्रोलिंग लिस्ट के आखिर में मौजूद होता है. जब बटन व्यूपोर्ट के करीब होता है, तो शामिल नहीं किया गया कॉन्टेंट DOM में जोड़ दिया जाता है.
  • sentinelObserver, #sentinel एलिमेंट की पोज़िशन को ट्रैक करता है. जब सेंटिनल दिखने लगता है, तब सर्वर से ज़्यादा कॉन्टेंट का अनुरोध किया जाता है. सेंटिनल की जगह को अडजस्ट करना, यह कंट्रोल करने का एक तरीका है कि सर्वर से नए कॉन्टेंट का अनुरोध कितनी पहले से किया जाना चाहिए.

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

एचटीएमएल

<div id="infinite-scroll-container">
    <div id="sentinel"></div>
    <div class="item">A</div>
    <div class="item">B</div>
    <div class="item">C</div>
    <div class="item">D</div>
    <div class="item">E</div>
    <button id="infinite-scroll-button" disabled>
        <span class="disabled-text">Loading more items...</span>
        <span class="active-text">Show more</span>
    </button>
</div>

सीएसएस


        :root {
    --active-button-primary: #0080ff;
    --active-button-font:#ffffff;
    --disabled-button-primary: #f5f5f5;
    --disabled-button-secondary: #c4c4c4;
    --disabled-button-font: #000000;
}
#infinite-scroll-container {
    position: relative;
}
#sentinel {
    position: absolute;
    bottom: 150vh;
}
#infinite-scroll-button {
    cursor: pointer;
    border: none;
    padding: 1em;
    width: 100%;
    font-size: 1em;
}
#infinite-scroll-button:enabled {
    color: var(--active-button-font);
    background-color: var(--active-button-primary)
}
#infinite-scroll-button:disabled {
    color: var(--disabled-button-font);
    background-color: var(--disabled-button-primary);
    cursor: not-allowed;
    animation: 3s ease-in-out infinite loadingAnimation;
}
#infinite-scroll-button:enabled .disabled-text {
    display: none;
}
#infinite-scroll-button:disabled .active-text {
    display: none;
}
@keyframes loadingAnimation {
    0% {
        background-color: var(--disabled-button-primary);
    }
    50% {
        background-color: var(--disabled-button-secondary);
    }
    100% {
        background-color: var(--disabled-button-primary);
    }
}
        

JS


        function infiniteScroll() {
    let responseBuffer = [];
    let hasMore;
    let requestPending = false;
    const loadingButtonEl = document.querySelector('#infinite-scroll-button');
    const containerEl = document.querySelector('#infinite-scroll-container');
    const sentinelEl = document.querySelector("#sentinel");
    const insertNewItems = () => {
        while (responseBuffer.length > 0) {
            const data = responseBuffer.shift();
            const el = document.createElement("div");
            el.textContent = data;
            el.classList.add("item");
            el.classList.add("new");
            containerEl.insertBefore(el, loadingButtonEl);
            console.log(`inserted: ${data}`);
        }
        sentinelObserver.observe(sentinelEl);
        if (hasMore === false) {
            loadingButtonEl.style = "display: none";
            sentinelObserver.unobserve(sentinelEl);
            listObserver.unobserve(loadingButtonEl);
        }
        loadingButtonEl.disabled = true
    }
    loadingButtonEl.addEventListener("click", insertNewItems);
    const requestHandler = () => {
        if (requestPending) return;
        console.log("making request");
        requestPending = true;
        fakeServer.fakeRequest().then((response) => {
            console.log("server response", response);
            requestPending = false;
            responseBuffer = responseBuffer.concat(response.items);
            hasMore = response.hasMore;
            loadingButtonEl.disabled = false;;
        });
    }
    const sentinelObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.intersectionRatio > 0) {
                observer.unobserve(sentinelEl);
                requestHandler();
            }
        });
    });
    const listObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.intersectionRatio > 0 && entry.intersectionRatio < 1) {
                insertNewItems();
            }
        });
    }, {
        rootMargin: "0px 0px 200px 0px"
    });
    sentinelObserver.observe(sentinelEl);
    listObserver.observe(loadingButtonEl);
}
infiniteScroll();