कम शब्दों में: अपने डीओएम एलिमेंट का फिर से इस्तेमाल करें और उन एलिमेंट को हटाएं जो व्यूपोर्ट से बहुत दूर हैं. देर से मिलने वाले डेटा को शामिल करने के लिए, प्लेसहोल्डर का इस्तेमाल करें. यहां अनलिमिटेड स्क्रोलर का डेमो और कोड दिया गया है.
इनफ़ाइनाइट स्क्रोलर, इंटरनेट पर हर जगह पॉप अप होते हैं. Google Music पर कलाकार की सूची एक है, Facebook पर टाइमलाइन एक है, और Twitter पर लाइव फ़ीड भी एक है. आपने स्क्रीन पर नीचे की ओर स्क्रोल किया और सबसे नीचे पहुंचने से पहले ही, नया कॉन्टेंट अचानक से दिखने लगा. इससे उपयोगकर्ताओं को आसानी से अनुभव मिलता है और इसे आसानी से समझा जा सकता है.
हालांकि, अनलिमिटेड स्क्रोलर बनाने की तकनीकी चुनौती, जितनी आसान दिखती है उतनी नहीं है. सही काम करने के लिए, आपको कई तरह की समस्याओं का सामना करना पड़ता है. यह समस्या, फ़ुटर में मौजूद लिंक को ऐक्सेस करने में आने वाली समस्याओं से शुरू होती है. ऐसा इसलिए होता है, क्योंकि कॉन्टेंट की वजह से फ़ुटर नीचे की ओर चला जाता है. हालांकि, समस्याएं और मुश्किल हो जाती हैं. जब कोई व्यक्ति अपने फ़ोन को पोर्ट्रेट से लैंडस्केप मोड में बदलता है, तो स्क्रीन के साइज़ में बदलाव करने वाले इवेंट को कैसे मैनेज किया जाता है? इसके अलावा, जब सूची बहुत लंबी हो जाती है, तो अपने फ़ोन को रुकने से कैसे रोका जाता है?
The right thing™
हमने सोचा कि यह एक ऐसा रेफ़रंस है जिसे लागू करने से, परफ़ॉर्मेंस के मानकों को बनाए रखते हुए, इन सभी समस्याओं को फिर से इस्तेमाल किए जा सकने वाले तरीके से हल किया जा सकता है.
हम अपने लक्ष्य को हासिल करने के लिए, तीन तकनीकों का इस्तेमाल करेंगे: डीओएम रीसाइकलिंग, टॉम्बस्टोन और स्क्रोल ऐंकरिंग.
हमारा डेमो केस, Hangouts जैसी चैट विंडो होगी, जहां हम मैसेज को स्क्रोल कर सकते हैं. सबसे पहले, हमें चैट मैसेज का अनलिमिटेड सोर्स चाहिए. तकनीकी तौर पर, इनफ़ाइनाइट स्क्रोलर में मौजूद डेटा वाकई में इनफ़ाइनाइट नहीं होता. हालांकि, इन स्क्रोलर में जितने डेटा को पंप किया जा सकता है वह इनफ़ाइनाइट हो सकता है. आसानी के लिए, हम चैट मैसेज के एक सेट को हार्ड-कोड करेंगे. साथ ही, मैसेज, लेखक, और कभी-कभी इमेज अटैचमेंट को रैंडम तौर पर चुनेंगे. इसमें थोड़ी देरी भी होगी, ताकि यह असल नेटवर्क की तरह काम कर सके.

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

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

अगर व्यूपोर्ट का साइज़ बदला जाता है और रनवे में बदलाव होते हैं, तो हम उस स्थिति को वापस ला सकते हैं जो उपयोगकर्ता को विज़ुअल तौर पर एक जैसी लगती है. जीतें! रीसाइज़ की गई विंडो को छोड़कर, हर आइटम की ऊंचाई में बदलाव हो सकता है. इसलिए, हमें यह कैसे पता चलेगा कि ऐंकर किए गए कॉन्टेंट को कितनी नीचे रखा जाना चाहिए? हम ऐसा नहीं करते! यह पता लगाने के लिए, हमें ऐंकर किए गए आइटम के ऊपर हर एलिमेंट को लेआउट करना होगा और उनकी सभी ऊंचाइयों को जोड़ना होगा. इससे, साइज़ बदलने के बाद थोड़ा समय लग सकता है और हम ऐसा नहीं चाहते. इसके बजाय, हम यह मानते हैं कि ऊपर मौजूद हर आइटम का साइज़, टॉम्बस्टोन के साइज़ के बराबर है. इसके हिसाब से, हम स्क्रोल की पोज़िशन में बदलाव करते हैं. जब एलिमेंट को रनवे में स्क्रोल किया जाता है, तो हम अपनी स्क्रोल पोज़िशन में बदलाव करते हैं. इससे, लेआउट का काम तब तक नहीं किया जाता, जब तक कि उसकी ज़रूरत न पड़े.
लेआउट
मैंने एक अहम जानकारी नहीं दी है: लेआउट. आम तौर पर, किसी DOM एलिमेंट को रीसाइकल करने पर, पूरे रनवे का लेआउट फिर से बन जाता है. इससे, हर सेकंड 60 फ़्रेम के हमारे टारगेट से काफ़ी कम फ़्रेम मिलते हैं. इससे बचने के लिए, हम लेआउट का भार खुद उठा रहे हैं और ट्रांसफ़ॉर्म के साथ पूरी तरह से पोज़िशन किए गए एलिमेंट का इस्तेमाल कर रहे हैं. इस तरह, हम यह दिखा सकते हैं कि रनवे के आगे मौजूद सभी एलिमेंट अब भी जगह ले रहे हैं, जबकि असल में वहां सिर्फ़ खाली जगह है. हम खुद लेआउट बना रहे हैं, इसलिए हम उन जगहों को कैश मेमोरी में सेव कर सकते हैं जहां हर आइटम खत्म होता है. साथ ही, जब उपयोगकर्ता पीछे की ओर स्क्रोल करता है, तो हम कैश मेमोरी से सही एलिमेंट तुरंत लोड कर सकते हैं.
आम तौर पर, आइटम को सिर्फ़ एक बार फिर से रंगा जाता है, जब वे DOM से जुड़ जाते हैं और रनवे में अन्य आइटम जोड़ने या हटाने से उन पर कोई असर नहीं पड़ता. ऐसा करना मुमकिन है, लेकिन सिर्फ़ मॉडर्न ब्राउज़र पर.
नए-नए बदलाव
हाल ही में, Chrome में सीएसएस कंटेनमेंट की सुविधा जोड़ी गई है. इस सुविधा की मदद से, डेवलपर ब्राउज़र को बता सकते हैं कि कोई एलिमेंट लेआउट और पेंट वर्क के लिए बाउंड्री है. हम यहां खुद लेआउट कर रहे हैं, इसलिए यह कॉन्टेंट को कंट्रोल करने के लिए सबसे सही तरीका है. जब भी हम रनवे में कोई एलिमेंट जोड़ते हैं, तो हमें पता होता है कि रीलेआउट से अन्य आइटम पर असर नहीं पड़ना चाहिए. इसलिए, हर आइटम को contain: layout
मिलना चाहिए. हम अपनी वेबसाइट के बाकी हिस्सों पर भी असर नहीं डालना चाहते. इसलिए, रनवे को भी यह स्टाइल डायरेक्टिव मिलना चाहिए.
हमने एक और बात पर भी विचार किया है. हमने IntersectionObservers
का इस्तेमाल, यह पता लगाने के लिए किया है कि उपयोगकर्ता ने स्क्रीन पर इतना स्क्रोल किया है कि हम एलिमेंट को रीसाइकल करना शुरू कर सकें और नया डेटा लोड कर सकें. हालांकि, IntersectionObservers के लिए ज़्यादा इंतज़ार का समय तय किया गया है (जैसे कि requestIdleCallback
का इस्तेमाल करना), इसलिए हो सकता है कि IntersectionObservers के बिना, हमें ऐसा लगे कि वे कम रिस्पॉन्सिव हैं. scroll
इवेंट का इस्तेमाल करके, फ़िलहाल जिस तरीके से इवेंट ट्रिगर किए जा रहे हैं उसमें भी यह समस्या आ रही है. ऐसा इसलिए है, क्योंकि स्क्रोल इवेंट “बेहतरीन कोशिश” के आधार पर डिस्पैच किए जाते हैं. आखिरकार, Houdini का कंपोजिटर वर्कलेट, इस समस्या का बेहतर समाधान होगा.
यह अब भी पूरी तरह से सही नहीं है
फ़िलहाल, DOM रीसाइकलिंग को लागू करने का हमारा तरीका सही नहीं है. इसकी वजह यह है कि यह स्क्रीन पर मौजूद एलिमेंट के बजाय, व्यूपोर्ट से गुज़रने वाले सभी एलिमेंट को जोड़ता है. इसका मतलब है कि बहुत तेज़ी से स्क्रोल करने पर, Chrome पर लेआउट और पेन्ट करने के लिए इतना ज़्यादा काम करना पड़ता है कि वह इसे पूरा नहीं कर पाता. आपको सिर्फ़ बैकग्राउंड दिखेगा. यह कोई बड़ी समस्या नहीं है, लेकिन इसे ठीक करना ज़रूरी है.
हमें उम्मीद है कि आपको पता चल गया होगा कि जब आपको बेहतरीन परफ़ॉर्मेंस के स्टैंडर्ड के साथ-साथ, उपयोगकर्ताओं को बेहतरीन अनुभव देना हो, तो आसान समस्याएं भी कितनी मुश्किल हो सकती हैं. मोबाइल फ़ोन पर प्रगतिशील वेब ऐप्लिकेशन का इस्तेमाल बढ़ने के साथ, यह और ज़्यादा अहम हो जाएगा. साथ ही, वेब डेवलपर को परफ़ॉर्मेंस से जुड़ी पाबंदियों को ध्यान में रखते हुए पैटर्न इस्तेमाल करने के लिए लगातार काम करना होगा.
सारा कोड हमारी रिपॉज़िटरी में मौजूद है. हमने इसे फिर से इस्तेमाल करने लायक बनाने की पूरी कोशिश की है. हालांकि, हम इसे npm पर किसी लाइब्रेरी के तौर पर या अलग रेपो के तौर पर पब्लिश नहीं करेंगे. इसका मुख्य मकसद शिक्षा देना हो.