پیچیدگی های یک اسکرول بی نهایت

TL;DR: از عناصر DOM خود مجددا استفاده کنید و آنهایی را که از دید درگاه دور هستند حذف کنید. از متغیرهایی برای حساب کردن داده های تاخیری استفاده کنید. در اینجا یک نسخه آزمایشی و کد برای اسکرول بی نهایت است.

اسکرول های بی نهایت در سراسر اینترنت ظاهر می شوند. لیست هنرمندان Google Music یکی، جدول زمانی فیس بوک یکی و فید زنده توییتر نیز یکی است. شما به پایین اسکرول می کنید و قبل از اینکه به انتهای آن برسید، محتوای جدید به طور جادویی ظاهراً از ناکجاآباد ظاهر می شود. این یک تجربه یکپارچه برای کاربران است و دیدن جذابیت آن آسان است.

با این حال، چالش فنی پشت یک اسکرول بی نهایت سخت تر از آن چیزی است که به نظر می رسد. دامنه مشکلاتی که وقتی می خواهید The Right Thing™ را انجام دهید با آن مواجه می شوید بسیار زیاد است. این کار با چیزهای ساده ای مانند پیوندهای موجود در فوتر شروع می شود که عملاً غیرقابل دسترس می شوند زیرا محتوا مدام پاورقی را دور می کند. اما مشکلات سخت تر می شوند. هنگامی که شخصی تلفن خود را از حالت عمودی به منظره تبدیل می‌کند یا چگونه می‌توانید هنگام طولانی‌تر شدن لیست از ساییدگی و توقف دردناک گوشی خود جلوگیری کنید، با یک رویداد تغییر اندازه چگونه برخورد می‌کنید؟

چیز درست ™

ما فکر کردیم که این دلیل کافی برای ارائه یک پیاده سازی مرجع است که راهی برای مقابله با همه این مشکلات به روشی قابل استفاده مجدد و در عین حال حفظ استانداردهای عملکرد نشان می دهد.

ما قصد داریم از 3 تکنیک برای رسیدن به هدف خود استفاده کنیم: بازیافت DOM، سنگ قبرها و لنگر انداختن اسکرول.

کیس نمایشی ما یک پنجره چت مانند Hangouts خواهد بود که در آن می‌توانیم پیام‌ها را مرور کنیم. اولین چیزی که به آن نیاز داریم منبع بی نهایت پیام های چت است. از نظر فنی، هیچ یک از اسکرول‌های بی‌نهایت موجود در آنجا واقعاً بی‌نهایت نیستند، اما با مقدار داده‌ای که برای پمپاژ به این اسکرول‌ها در دسترس است، ممکن است همینطور باشند. برای سادگی، مجموعه‌ای از پیام‌های چت را کدگذاری می‌کنیم و پیام، نویسنده و پیوست‌های تصویری گاه به گاه را به‌طور تصادفی با کمی تأخیر مصنوعی انتخاب می‌کنیم تا کمی بیشتر شبیه به شبکه واقعی رفتار کنیم.

اسکرین شات برنامه چت

بازیافت DOM

بازیافت DOM یک تکنیک کم استفاده برای پایین نگه داشتن تعداد گره های DOM است. ایده کلی این است که به جای ایجاد عناصر جدید، از عناصر DOM از قبل ایجاد شده استفاده کنید که خارج از صفحه هستند. مسلماً، خود گره‌های DOM ارزان هستند، اما رایگان نیستند، زیرا هر یک از آنها هزینه اضافی در حافظه، چیدمان، سبک و رنگ اضافه می‌کند. اگر وب‌سایت دارای یک DOM خیلی بزرگ برای مدیریت نباشد، دستگاه‌های پایین‌رده به طور قابل توجهی کندتر می‌شوند. همچنین به خاطر داشته باشید که هر تغییر و استفاده مجدد از سبک های شما - فرآیندی که هر زمان که یک کلاس از یک گره اضافه یا حذف می شود راه اندازی می شود - با یک DOM بزرگتر گران تر می شود. بازیافت گره‌های DOM به این معنی است که ما تعداد کل گره‌های DOM را به میزان قابل توجهی کمتر می‌کنیم و همه این فرآیندها را سریع‌تر می‌کنیم.

اولین مانع خود پیمایش است. از آنجایی که ما در هر زمان معین فقط یک زیرمجموعه کوچک از همه موارد موجود در DOM خواهیم داشت، باید راه دیگری پیدا کنیم تا نوار اسکرول مرورگر به درستی مقدار محتوای موجود در آن را منعکس کند. ما از یک عنصر نگهبان 1px در 1px با یک تبدیل استفاده خواهیم کرد تا عنصری که شامل موارد - باند فرودگاه - است را مجبور کنیم که ارتفاع مورد نظر را داشته باشد. ما هر عنصر در باند فرودگاه را به لایه خود ارتقا می دهیم تا مطمئن شویم که لایه خود باند کاملاً خالی است. بدون رنگ پس زمینه، هیچ چیز. اگر لایه باند خالی نباشد، برای بهینه سازی مرورگر واجد شرایط نیست و باید بافتی را روی کارت گرافیک خود ذخیره کنیم که ارتفاع آن چند صد هزار پیکسل است. قطعاً روی دستگاه تلفن همراه قابل اجرا نیست.

هر زمان که پیمایش می کنیم، بررسی می کنیم که آیا ویوپورت به اندازه کافی به انتهای باند نزدیک شده است یا خیر. اگر چنین است، با جابجایی عنصر نگهبان و انتقال آیتم هایی که از ویوپورت خارج شده اند به پایین باند، باند فرودگاه را گسترش می دهیم و آنها را با محتوای جدید پر می کنیم.

باند نگهبان درگاه دید

همین امر در مورد پیمایش در جهت دیگر نیز صدق می کند. با این حال، ما هرگز باند را در اجرای خود کوچک نمی کنیم تا موقعیت نوار اسکرول ثابت بماند.

سنگ قبرها

همانطور که قبلاً اشاره کردیم، ما سعی می کنیم منبع داده خود را مانند چیزی در دنیای واقعی رفتار کنیم. با تاخیر شبکه و همه چیز. این بدان معناست که اگر کاربران ما از پیمایش سریع استفاده کنند، می‌توانند به راحتی آخرین عنصری را که ما برای آن داده‌ای داریم مرور کنند. اگر این اتفاق بیفتد، یک مورد سنگ قبر - یک مکان‌دار - قرار می‌دهیم که پس از رسیدن داده‌ها، با آیتم با محتوای واقعی جایگزین می‌شود. سنگ قبرها نیز بازیافت می شوند و دارای یک استخر مجزا برای عناصر DOM قابل استفاده مجدد هستند. ما به آن نیاز داریم تا بتوانیم یک انتقال خوب از سنگ قبر به آیتم پر محتوا انجام دهیم، که در غیر این صورت برای کاربر بسیار آزاردهنده خواهد بود و ممکن است باعث شود که او مسیر تمرکز خود را از دست بدهد.

چنین قبر. خیلی سنگی وای.

یک چالش جالب در اینجا این است که اقلام واقعی می توانند ارتفاع بیشتری نسبت به آیتم سنگ قبر داشته باشند، زیرا مقادیر متفاوت متن در هر مورد یا یک تصویر پیوست شده است. برای حل این مشکل، هر بار که داده‌ها وارد می‌شوند و یک سنگ قبر در بالای درگاه دید جایگزین می‌شود، موقعیت اسکرول فعلی را تنظیم می‌کنیم و موقعیت اسکرول را به جای یک مقدار پیکسل به یک عنصر متصل می‌کنیم . به این مفهوم لنگر انداختن اسکرول می گویند.

لنگر انداختن اسکرول

لنگر اسکرول ما هم هنگام تعویض سنگ قبرها و هم زمانی که اندازه پنجره تغییر می‌کند (که در هنگام چرخاندن دستگاه نیز اتفاق می‌افتد!) فراخوانی می‌شود. ما باید بفهمیم که بالاترین عنصر قابل مشاهده در viewport چیست. از آنجایی که آن عنصر فقط تا حدی قابل مشاهده است، ما همچنین افست را از بالای عنصری که درگاه نمایش شروع می شود ذخیره می کنیم.

نمودار لنگر انداختن.

اگر اندازه ویوپورت تغییر کند و باند فرودگاه تغییراتی داشته باشد، می‌توانیم وضعیتی را بازیابی کنیم که از نظر بصری برای کاربر یکسان است. پیروزی! به جز یک پنجره تغییر اندازه به این معنی است که هر آیتم به طور بالقوه ارتفاع خود را تغییر داده است، بنابراین چگونه می‌توانیم بفهمیم محتوای لنگر چقدر باید قرار گیرد؟ ما نمی کنیم! برای اینکه بفهمیم باید هر عنصر را بالای آیتم لنگر انداخته چیدمان کنیم و تمام ارتفاعات آنها را جمع کنیم. این می تواند باعث توقف قابل توجهی پس از تغییر اندازه شود، و ما آن را نمی خواهیم. در عوض، ما به این فرض متوسل می‌شویم که هر آیتم بالا به اندازه یک سنگ قبر است و موقعیت اسکرول خود را بر این اساس تنظیم می‌کنیم. همانطور که عناصر در باند فرودگاه اسکرول می شوند، ما موقعیت اسکرول خود را تنظیم می کنیم و عملاً کار چیدمان را به زمانی که واقعاً مورد نیاز است به تعویق می اندازیم.

چیدمان

من از یک جزئیات مهم صرف نظر کرده ام: طرح بندی. هر بازیافت یک عنصر DOM به طور معمول کل باند فرودگاه را تغییر می دهد که ما را به زیر هدف ما یعنی 60 فریم در ثانیه می رساند. برای جلوگیری از این امر، بار طرح‌بندی را بر دوش خود می‌گیریم و از عناصر کاملاً موقعیت‌یافته با تبدیل استفاده می‌کنیم. به این ترتیب می‌توانیم وانمود کنیم که همه عناصر بالاتر از باند هنوز فضا را اشغال می‌کنند، در حالی که در واقع فقط فضای خالی وجود دارد. از آنجایی که ما خودمان طرح‌بندی را انجام می‌دهیم، می‌توانیم موقعیت‌هایی را که در آن هر آیتم به پایان می‌رسد را در حافظه پنهان نگه داریم و وقتی کاربر به عقب اسکرول می‌کند، می‌توانیم بلافاصله عنصر صحیح را از حافظه پنهان بارگذاری کنیم.

در حالت ایده‌آل، آیتم‌ها فقط یک بار زمانی که به DOM متصل می‌شوند رنگ‌آمیزی می‌شوند و با افزودن یا حذف سایر اقلام در باند فرود از بین می‌روند. این امکان پذیر است، اما فقط با مرورگرهای مدرن.

ترفندهای خونریزی دهنده

اخیراً کروم پشتیبانی از CSS Containment را اضافه کرده است ، قابلیتی که به ما توسعه دهندگان این امکان را می دهد تا به مرورگر بگوییم که یک عنصر مرزی برای کار با چیدمان و نقاشی است. از آنجایی که ما خودمان در اینجا طرح‌بندی را انجام می‌دهیم، این یک برنامه اصلی برای مهار است. هر زمان که عنصری را به باند فرودگاه اضافه می‌کنیم، می‌دانیم که دیگر موارد نیازی به تأثیر رله‌آو ندارند. بنابراین هر آیتم باید contain: layout باشد. ما همچنین نمی‌خواهیم روی بقیه وب‌سایت‌مان تأثیر بگذاریم، بنابراین خود باند باید این دستورالعمل سبک را نیز دریافت کند.

مورد دیگری که ما در نظر گرفتیم استفاده از IntersectionObservers به ​​عنوان مکانیزمی برای تشخیص زمانی است که کاربر به اندازه کافی پیمایش کرده است تا بتوانیم عناصر را بازیافت کنیم و داده های جدید را بارگذاری کنیم. با این حال، IntersectionObservers با تأخیر بالا مشخص شده‌اند (مثلاً از requestIdleCallback استفاده می‌کنند)، بنابراین ممکن است در واقع با IntersectionObservers نسبت به بدون آن احساس پاسخگویی کمتری داشته باشیم. حتی اجرای فعلی ما با استفاده از رویداد scroll نیز از این مشکل رنج می‌برد، زیرا رویدادهای اسکرول بر اساس "بهترین تلاش" ارسال می‌شوند. در نهایت، Houdini's Compositor Worklet راه حلی با وفاداری بالا برای این مشکل خواهد بود.

هنوز هم کامل نیست

اجرای فعلی بازیافت DOM ایده‌آل نیست، زیرا به جای توجه به مواردی که واقعاً روی صفحه هستند، همه عناصری را که از پنجره دید عبور می‌کنند اضافه می‌کند. این به این معنی است که وقتی واقعاً سریع اسکرول می‌کنید، آنقدر برای طرح‌بندی و نقاشی روی کروم کار می‌کنید که نمی‌تواند آن را ادامه دهد. در نهایت چیزی جز پس‌زمینه نمی‌بینید. این پایان دنیا نیست، اما قطعا چیزی برای بهبود است.

امیدواریم وقتی می خواهید تجربه کاربری عالی را با استانداردهای عملکرد بالا ترکیب کنید، مشکلات ساده چقدر چالش برانگیز می شوند. با تبدیل شدن برنامه‌های وب پیشرو به تجربیات اصلی در تلفن‌های همراه، این مهم‌تر می‌شود و توسعه‌دهندگان وب باید برای استفاده از الگوهایی که به محدودیت‌های عملکردی احترام می‌گذارند، سرمایه‌گذاری کنند.

تمام کدها را می توان در مخزن ما پیدا کرد. ما تمام تلاش خود را کرده ایم تا آن را قابل استفاده مجدد نگه داریم، اما آن را به عنوان یک کتابخانه واقعی در npm یا به عنوان یک مخزن جداگانه منتشر نمی کنیم. استفاده اولیه آموزشی است.