تعقيدات تمرير لا نهائي

النصّ المختصر (TL;DR): أعِد استخدام عناصر DOM وأزل العناصر البعيدة عن إطار العرض. استخدم العناصر النائبة لحساب البيانات المتأخرة. إليك عرضًا توضيحيًا والرمز البرمجي لشريط التمرير اللا نهائي.

تنبثق برامج تمرير لانهائية على الإنترنت. قائمة الفنانين في Google Music هي واحدة، والجدول الزمني على Facebook هو واحد وخلاصة مباشرة على Twitter أيضًا. يمرر المحتوى للأسفل وقبل أن تصل إلى الأسفل، يظهر المحتوى الجديد على ما يبدو من العدم. إنها تجربة سلسة للمستخدمين ومن السهل رؤية الجاذبية الخاصة بها.

ومع ذلك، فإن التحدي الفني وراء تمرير لا نهائي هو أصعب مما يبدو. إن مجموعة المشكلات التي تواجهها عندما تريد القيام بـ TheRight ThingTM واسعة النطاق. فهو يبدأ بأشياء بسيطة مثل الروابط الموجودة في التذييل لا يمكن الوصول إليها عمليًا لأن المحتوى يستمر في دفع التذييل بعيدًا. لكن المشكلات تصبح أكثر صعوبة. كيف يمكنك التعامل مع حدث تغيير الحجم عندما يحوّل أحد الأشخاص هاتفه من الوضع العمودي إلى الوضع الأفقي أو كيف يمكن منع هاتفك من التوقف عن العمل بشكل مؤلم عندما تكون القائمة طويلة جدًا؟

الشيء الصحيحTM

نعتقد أنّ هذا كان سببًا كافيًا للتوصل إلى تطبيق مرجعي يوضّح طريقة لمعالجة كل هذه المشاكل بطريقة قابلة لإعادة الاستخدام مع الحفاظ على معايير الأداء.

سنستخدم 3 تقنيات لتحقيق هدفنا، وهي: إعادة تدوير نموذج العناصر في المستند (DOM) وشوائب القبر وإرساء التمرير.

ستكون حالة الإصدار التجريبي عبارة عن نافذة دردشة شبيهة بميزة Hangouts يمكننا من خلالها التمرير عبر الرسائل. أول شيء نحتاجه هو مصدر غير محدود لرسائل الدردشة. من الناحية الفنية، لا يوجد أي من التمريرات اللانهائية لا حصر لها، ولكن مع كمية البيانات المتاحة للانخراط في هذه الأجزاء المعروضة أيضًا. للتيسير، سننشئ ترميزًا ثابتًا لمجموعة من رسائل المحادثة ونختار الرسائل والمؤلف ومرفقات الصور من حين لآخر بشكل عشوائي مع بعض التأخيرات الاصطناعية، وذلك لكي نتصرف بشكل أشبه بالشبكة الحقيقية.

لقطة شاشة لتطبيق Chat

إعادة تدوير نموذج العناصر في المستند (DOM)

تُعد عملية إعادة تدوير نموذج العناصر في المستند (DOM) أسلوبًا غير مستخدَم بشكل كافٍ للحفاظ على انخفاض عدد عُقد DOM. والفكرة العامة هي استخدام عناصر DOM التي تم إنشاؤها بالفعل والتي تكون خارج الشاشة بدلاً من إنشاء عناصر جديدة. واعتراف بأن عُقد DOM نفسها رخيصة، لكنها ليست مجانية، حيث يضيف كل منها تكلفة إضافية في الذاكرة والتخطيط والأسلوب والطلاء. ستصبح الأجهزة ذات المواصفات المنخفضة أبطأ بشكل ملحوظ إذا لم تكن قابلة للاستخدام تمامًا إذا كان الموقع الإلكتروني يحتوي على حجم كبير جدًا من عناصر DOM لإدارته. ضَع في اعتبارك أيضًا أنّ كل عملية إعادة تخطيط وإعادة تطبيق لأنماطك، أي عملية يتم تشغيلها عند إضافة فئة أو إزالتها من عقدة، تصبح أكثر تكلفة مع حجم عناصر DOM الأكبر. تعني إعادة تدوير عُقد DOM أننا سنحافظ على إجمالي عدد عُقد DOM أقل بكثير، ما يجعل جميع هذه العمليات أسرع.

العقبة الأولى هي التمرير نفسه. ونظرًا لأنه لن يكون لدينا سوى مجموعة فرعية صغيرة فقط من جميع العناصر المتاحة في نموذج العناصر في المستند (DOM) في أي وقت، فنحن بحاجة إلى إيجاد طريقة أخرى لجعل شريط التمرير في المتصفح يعكس بشكل صحيح مقدار المحتوى الموجود نظريًا. سنستخدم عنصر sendinel بمقاس 1 بكسل × 1 بكسل مع تحويل لإجبار العنصر الذي يحتوي على العناصر - المدرج - بالارتفاع المطلوب. وسنعمل على ترقية كل عنصر في المدرج إلى طبقته الخاصة لنتأكد من أن طبقة المدرج نفسه فارغة تمامًا. بدون لون للخلفية، ولا شيء. إذا كانت طبقة المدرج غير فارغة، فلن تكون مؤهلة لتحسينات المتصفح وسيتعين علينا تخزين زخرفة على بطاقة الرسومات الخاصة بنا التي يبلغ ارتفاعها مئات الآلاف من وحدات البكسل. بالتأكيد غير قابلة للتطبيق على الجهاز المحمول.

وعند التمرير، سنتحقق مما إذا كان إطار العرض يقترب بدرجة كافية من نهاية المدرج. إذا كان الأمر كذلك، سنعمل على تمديد المدرج من خلال تحريك العنصر المرسل وتحريك العناصر التي تركت إطار العرض إلى الجزء السفلي من المدرج وملؤها بمحتوى جديد.

الطريق الطريق

وينطبق الشيء نفسه على التمرير في الاتجاه الآخر. ومع ذلك، لن نقلص مطلقًا مسرحية التنفيذ لدينا، بحيث يظل موضع شريط التمرير ثابتًا.

ملفات Tombstone

كما ذكرنا سابقًا، نحاول أن نجعل مصدر البيانات يتصرف كشيء في العالم الحقيقي. مع وقت استجابة الشبكة وجميع الميزات المتوفّرة وهذا يعني أنه إذا استخدم المستخدمون التمرير الوامض، فيمكنهم التمرير بسهولة عبر العنصر الأخير الذي لدينا بيانات عنه. إذا حدث ذلك، فسنقوم بوضع عنصر ضريح - عنصر نائب - سيتم استبداله بعنصر بالمحتوى الفعلي بمجرد وصول البيانات. تتم أيضًا إعادة تدوير ملفات Tombstone وتحتوي على مجموعة منفصلة لعناصر DOM القابلة لإعادة الاستخدام. نحتاج إلى ذلك حتى نتمكن من إجراء انتقال لطيف من شاهد قبر إلى عنصر مملوء بالمحتوى، والذي سيكون مزعجًا جدًا للمستخدم بخلاف ذلك وقد يجعله في الواقع يتجاهل ما كان يركز عليه.

هكذا. حجري للغاية. يا إلهي!

يتمثل أحد التحديات المثيرة للاهتمام هنا في أن العناصر الحقيقية يمكن أن يكون ارتفاعها أكبر من عنصر النصب التذكاري بسبب اختلاف كميات النص لكل عنصر أو صورة مرفقة. لحل هذه المشكلة، سيتم تعديل موضع التمرير الحالي في كل مرة يتم فيها إدخال البيانات ويتم استبدال رمز دليل الملاحظات فوق إطار العرض، مع إرساء موضع التمرير إلى عنصر بدلاً من قيمة البكسل. هذا المفهوم يسمى ارتساء التمرير.

تمرير الارتساء

وسيتم استدعاء ميزة الإرساء القابلة للتمرير عند استبدال شواهد القبور وكذلك عند تغيير حجم النافذة (وهو ما يحدث أيضًا عند قلب الأجهزة!). سيتعين علينا معرفة العنصر الأعلى ظهورًا في إطار العرض. ونظرًا لأن هذا العنصر يمكن أن يكون مرئيًا جزئيًا فقط، فسنخزن أيضًا الإزاحة من أعلى العنصر حيث يبدأ إطار العرض.

تمرير مخطط الارتساء.

إذا تم تغيير حجم إطار العرض وتغيرات على المدرج، فيمكننا استعادة موقف يبدو مطابقًا مرئيًا للمستخدم. لقد فزت! باستثناء نافذة تم تغيير حجمها يعني احتمال تغيير ارتفاع كل عنصر، فكيف نعرف إلى أي مدى يجب وضع المحتوى الثابت؟ نحن لا نفعل ذلك! لمعرفة أنه يتعين علينا تخطيط كل عنصر أعلى العنصر الارتساء وإضافة كل ارتفاعاته؛ قد يتسبب هذا في توقف مؤقت بشكل كبير بعد تغيير الحجم، ولا نريد ذلك. بدلاً من ذلك، نلجأ إلى افتراض أن كل عنصر أعلاه له نفس حجم شاهد ضريح ونضبط موضع التمرير وفقًا لذلك. وبينما يتم تمرير العناصر إلى المدرج، نقوم بتعديل موضع التمرير لدينا، مع تأجيل عمل التخطيط بشكل فعال إلى وقت الحاجة إليه بالفعل.

التنسيق

لقد تخطيت أحد التفاصيل المهمة: التخطيط. ستعمل كل عملية إعادة تدوير لعنصر DOM عادةً على إعادة تخطيط المدرج بالكامل مما سيجعلنا أقل بكثير من هدفنا وهو 60 إطارًا في الثانية. لتجنب ذلك، نتحمل عبء التخطيط على أنفسنا ونستخدم عناصر موضوعة تمامًا مع التحويلات. بهذه الطريقة، يمكننا التظاهر بأن جميع العناصر التي تقع على طول المدرج لا تزال تشغل مساحة بينما في الواقع لا توجد سوى مساحة فارغة. ونظرًا لأننا نقوم بالتخطيط بأنفسنا، يمكننا تخزين المواضع التي ينتهي بها كل عنصر مؤقتًا ويمكننا تحميل العنصر الصحيح على الفور من ذاكرة التخزين المؤقت عندما يقوم المستخدم بالتمرير للخلف.

من الناحية المثالية، ستتم إعادة طلاء العناصر مرة واحدة فقط عندما يتم إرفاقها بنموذج العناصر في المستند (DOM) وتصبح غير باهتة بسبب إضافات أو إزالة عناصر أخرى على منصة العرض. هذا ممكن، ولكن فقط مع المتصفحات الحديثة.

تعديلات سريعة

مؤخرًا، أضاف Chrome دعمًا لاحتواء CSS، وهي ميزة تتيح لنا للمطوّرين إعلام المتصفح بأنّ عنصر ما يشكّل حدودًا لأعمال التنسيق والطلاء. نظرًا لأننا نقوم بالتخطيط بأنفسنا هنا، فهو تطبيق أساسي للاحتواء. عندما نضيف عنصرًا إلى العرض، نعرف أن العناصر الأخرى لا تحتاج إلى أن تتأثر بإعادة التنسيق. لذلك ينبغي أن يحصل كل عنصر على contain: layout. نحن أيضًا لا نريد التأثير على باقي موقعنا، لذا يجب أن يحصل مسار العرض نفسه على توجيه النمط هذا أيضًا.

هناك شيء آخر وضعناه في الاعتبار وهو استخدام IntersectionObservers كآلية لرصد الوقت الذي ينتقل فيه المستخدم إلى مسافة كافية كي نبدأ بإعادة تدوير العناصر وتحميل بيانات جديدة. مع ذلك، تم تصميم IntersectionMonitorers بالاستجابة السريعة (كما لو كانت تستخدم requestIdleCallback)، لذا قد نشعر بأنّنا أقل استجابة مع IntersectionMonitorers مقارنةً بغيرها. تحدث هذه المشكلة أيضًا في عملية التنفيذ الحالية باستخدام حدث scroll، لأنّ أحداث التنقّل يتم إرسالها وفقًا لـ "أفضل جهد". في النهاية، ستكون أداة Compositor Worklet الخاصة بـ Houdini هي الحل عالي الدقة لهذه المشكلة.

لا تزال غير مثالية

لا تُعد عملية إعادة تدوير DOM في الوقت الحالي خيارًا مثاليًا لأنّه يضيف جميع العناصر التي تمر عبر إطار العرض، بدلاً من مجرد الاهتمام بالعناصر التي تظهر على الشاشة. وهذا يعني أنه عند الانتقال بسرعة الحقيقية، يبذل الكثير من الجهد للتنسيق والطلاء على Chrome بشكل يتعذر معه مواكبته. سينتهي بك الأمر برؤية لا شيء سوى الخلفية. إنها ليست نهاية العالم ولكنها بالتأكيد شيء مطلوب تحسينه.

نأمل أن تلاحظ مدى صعوبة المشكلات البسيطة التي يمكن أن تصبح عندما تريد الجمع بين تجربة المستخدم الرائعة ومعايير الأداء العالية. مع تحول تطبيقات الويب التقدّمية إلى تجارب أساسية على الهواتف الجوّالة، سيصبح هذا الأمر أكثر أهمية وسيتعين على مطوّري الويب مواصلة الاستثمار في استخدام الأنماط التي تحترم قيود الأداء.

يمكن العثور على جميع الرموز في مستودعنا. لقد بذلنا قصارى جهدنا لجعله قابلاً لإعادة الاستخدام، ولكننا لن ننشره كمكتبة فعلية في npm أو كمستودع منفصل. الاستخدام الأساسي هو المحتوى التعليمي.