इनफ़ाइनाइट स्क्रोलर की समस्याएं

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

इनफ़ाइनाइट स्क्रोलर, इंटरनेट पर हर जगह दिखते हैं. Google Music की कलाकार सूची एक है, Facebook की टाइमलाइन एक है, और Twitter का लाइव फ़ीड भी एक है. नीचे की ओर स्क्रोल करने पर, आपको सबसे नीचे पहुंचने से पहले ही नया कॉन्टेंट दिखने लगता है. इससे उपयोगकर्ताओं को बेहतरीन अनुभव मिलता है. साथ ही, यह आसानी से समझ में आ जाता है कि इस सुविधा का इस्तेमाल क्यों करना चाहिए.

हालांकि, इनफ़ाइनाइट स्क्रोलर के पीछे की तकनीकी चुनौती, दिखने में जितनी आसान लगती है उससे कहीं ज़्यादा मुश्किल है. The Right Thing™ करने के दौरान, आपको कई तरह की समस्याओं का सामना करना पड़ सकता है. इसकी शुरुआत फ़ुटर में मौजूद लिंक जैसी सामान्य चीज़ों से होती है. कॉन्टेंट की वजह से फ़ुटर नीचे की ओर खिसकता रहता है, इसलिए लिंक पर क्लिक करना मुश्किल हो जाता है. लेकिन, समस्याएं और मुश्किल होती जाती हैं. जब कोई व्यक्ति अपने फ़ोन को पोर्ट्रेट से लैंडस्केप मोड में बदलता है, तो साइज़ बदलने वाले इवेंट को कैसे मैनेज किया जाता है? इसके अलावा, जब सूची बहुत लंबी हो जाती है, तो अपने फ़ोन को अचानक बंद होने से कैसे रोका जाता है?

The right thing™

हमें लगा कि यह एक वजह है कि हम एक रेफ़रंस लागू करने का तरीका लेकर आएं. इससे इन सभी समस्याओं को बार-बार इस्तेमाल किए जा सकने वाले तरीके से हल करने का तरीका दिखाया जा सकता है. साथ ही, परफ़ॉर्मेंस के मानकों को बनाए रखा जा सकता है.

हमारा लक्ष्य है कि स्क्रोल करने पर लेआउट में बदलाव न हो. इसके लिए, हम तीन तकनीकों का इस्तेमाल करेंगे: DOM रीसाइक्लिंग, टॉम्बस्टोन, और स्क्रोल ऐंकरिंग.

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

Chat ऐप्लिकेशन का स्क्रीनशॉट

डीओएम रीसाइकलिंग

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

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

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

Runway Sentinel Viewport

दूसरी दिशा में स्क्रोल करने पर भी यही होता है. हालांकि, हम स्क्रोलबार की लंबाई को कभी कम नहीं करेंगे, ताकि स्क्रोलबार की पोज़िशन एक जैसी बनी रहे.

टूंबस्टोन

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

इस तरह की
कब्र. बहुत पत्थर. वाह।

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

स्क्रोल ऐंकरिंग

स्क्रोल ऐंकरिंग की सुविधा, इन दोनों स्थितियों में काम करेगी: जब टॉम्बस्टोन को बदला जा रहा हो और जब विंडो का साइज़ बदला जा रहा हो. विंडो का साइज़ तब भी बदलता है, जब डिवाइस को फ़्लिप किया जा रहा हो! हमें यह पता लगाना होगा कि व्यूपोर्ट में सबसे ऊपर दिखने वाला एलिमेंट कौन-सा है. यह एलिमेंट सिर्फ़ कुछ हद तक दिख सकता है. इसलिए, हम एलिमेंट के सबसे ऊपर वाले हिस्से से उस ऑफ़सेट को भी सेव करेंगे जहां से व्यूपोर्ट शुरू होता है.

स्क्रोल ऐंकरिंग का डायग्राम.

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

लेआउट

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

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

एडवांस सेटिंग में बदलाव

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

हमने एक और चीज़ पर विचार किया है. वह यह है कि IntersectionObservers का इस्तेमाल करके यह पता लगाया जाए कि उपयोगकर्ता ने इतना स्क्रोल कर लिया है कि हम एलिमेंट को रीसाइकल करना शुरू कर सकें और नया डेटा लोड कर सकें. हालांकि, IntersectionObserver को ज़्यादा इंतज़ार के समय के लिए तय किया गया है (जैसे कि requestIdleCallback का इस्तेमाल करना). इसलिए, ऐसा हो सकता है कि IntersectionObserver के साथ, हमें बिना IntersectionObserver के मुकाबले कम रिस्पॉन्स मिले. scroll इवेंट का इस्तेमाल करके लागू किए गए हमारे मौजूदा सिस्टम में भी यह समस्या आती है. ऐसा इसलिए, क्योंकि स्क्रोल इवेंट को “बेस्ट एफर्ट” के आधार पर भेजा जाता है. आखिरकार, Houdini के कंपोज़िटर वर्कलेट का इस्तेमाल करके इस समस्या को हल किया जा सकता है.

यह अब भी पूरी तरह से सटीक नहीं है

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

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

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