نظرة من الداخل على متصفح الويب الحديث (الجزء 4)

Mariko Kosaka

إتاحة إدخال البيانات في أداة "المُركب"

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

أحداث الإدخال من وجهة نظر المتصفّح

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

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

حدث الإدخال
الشكل 1: حدث الإدخال الذي يتم توجيهه من خلال عملية المتصفّح إلى عملية عرض المحتوى

يتلقّى "المُركّب" أحداث الإدخال.

الشكل 2: إطار العرض الذي يحوم فوق طبقات الصفحة

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

فهم المنطقة غير القابلة للتمرير السريع

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

منطقة محدودة لا يمكن التمرير فيها سريعًا
الشكل 3: مخطّط بياني للإدخال الموضّح في المنطقة التي لا يمكن التمرير فيها سريعًا

الانتباه عند كتابة معالِجات الأحداث

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

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

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

منطقة في الصفحة بأكملها يمكن التمرير فيها ببطء
الشكل 4: مخطّط بياني للإدخال الموضّح للمنطقة التي لا يمكن التمرير فيها بسرعة وتغطي صفحة كاملة

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

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

التحقّق مما إذا كان بالإمكان إلغاء الحدث

الانتقال إلى أسفل الصفحة
الشكل 5: صفحة ويب تم تثبيت جزء منها على التمرير الأفقي

تخيل أنّ لديك مربّعًا في صفحة تريد حصر اتجاه التمرير فيه على التمرير الأفقي فقط.

يعني استخدام الخيار passive: true في حدث المؤشر أنّ الانتقال في الصفحة يمكن أن يكون سلسًا، ولكن قد يبدأ الانتقال العمودي في الوقت الذي تريد فيه preventDefault للحدّ من اتجاه الانتقال. يمكنك التحقّق من ذلك باستخدام طريقة event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

بدلاً من ذلك، يمكنك استخدام قاعدة CSS مثل touch-action لإزالة معالِج الأحداث بالكامل.

#area {
  touch-action: pan-x;
}

العثور على هدف الحدث

اختبار مؤشر الماوس
الشكل 6: سلسلة المحادثات الرئيسية التي تبحث في سجلات الطلاء عن ما تم رسمه على نقطة x.y

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

تقليل عمليات إرسال الأحداث إلى سلسلة المهام الرئيسية

في المشاركة السابقة، ناقشنا كيف تُجدِّد شاشاتنا العادية الشاشة 60 مرة في الثانية، و كيفية مواكبة الإيقاع لعرض الصور المتحركة بسلاسة. بالنسبة إلى الإدخال، يرسل جهاز الشاشة التي تعمل باللمس المعتاد حدث اللمس من 60 إلى 120 مرة في الثانية، ويرسل الماوس المعتاد الأحداث 100 مرة في الثانية. حدث الإدخال له دقة أعلى من معدّل تحديث الشاشة.

إذا تم إرسال حدث مستمر مثل touchmove إلى سلسلة المهام الرئيسية 120 مرة في الثانية، قد يؤدي ذلك إلى تشغيل عدد كبير جدًا من اختبارات النتائج وتنفيذ JavaScript مقارنةً ببطء إعادة التحديث في الشاشة.

الأحداث غير المفلتَرة
الشكل 7: الأحداث التي تغمر المخطط الزمني للإطار وتتسبّب في حدوث تقطُّع في الصفحة

لتقليل عدد عمليات الاتصال المفرط بالسلسلة الرئيسية، يجمع Chrome الأحداث المستمرة (مثل wheel وmousewheel وmousemove وpointermove وtouchmove) ويؤجل إرسالها إلى قبل requestAnimationFrame التالي مباشرةً.

الأحداث المجمّعة
الشكل 8: المخطط الزمني نفسه كما في السابق، ولكن يتم دمج الحدث وتأخيره

يتم إرسال أي أحداث منفصلة، مثل keydown وkeyup وmouseup وmousedown وtouchstart وtouchend على الفور.

استخدِم getCoalescedEvents للحصول على أحداث داخل اللقطة.

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

getCoalescedEvents
الشكل 9: مسار إيماءة اللمس السلس على اليسار، ومسار محدود مدمج على اليمين
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

الخطوات التالية

في هذه السلسلة، اطّلعنا على آلية عمل متصفّح الويب. إذا لم يسبق لك التفكير في سبب اقتراح DevTools إضافة {passive: true} إلى معالِج الأحداث أو سبب كتابة سمة async في علامة النص البرمجي، نأمل أن تُلقي هذه السلسلة من المقالات بعض الضوء على سبب احتياج المتصفّح إلى هذه المعلومات لتقديم تجربة أسرع وأكثر سلاسة على الويب.

استخدام Lighthouse

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

التعرّف على كيفية قياس الأداء

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

إضافة "سياسة الميزات" إلى موقعك الإلكتروني

إذا أردت اتّخاذ خطوة إضافية، يمكنك الاطّلاع على سياسة الميزات، وهي ميزة جديدة لمنصة الويب يمكن أن تكون بمثابة قاعدة توجيهية لك عند إنشاء مشروعك. يضمن تفعيل سياسة الميزات سلوكًا معيّنًا لتطبيقك ويمنعك من ارتكاب الأخطاء. على سبيل المثال، إذا كنت تريد التأكّد من أنّ تطبيقك لن يحظر التحليل مطلقًا، يمكنك تشغيل تطبيقك على سياسة النصوص البرمجية غير المتزامنة. عند تفعيل sync-script: 'none'، سيتم منع تنفيذ JavaScript الذي يحظر المُحلِّل. ويمنع ذلك أيًّا من الرموز البرمجية من حظر المُحلِّل، ولا يحتاج المتصفّح إلى القلق بشأن إيقاف المُحلِّل مؤقتًا.

الخاتمة

شكرًا

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

نشكر بشدة جميع من راجعوا المسودات الأولى من هذه السلسلة، بما في ذلك (على سبيل المثال لا الحصر): أليكس راسل، بول إيريش، ميغين كيرن، إريك بيدلمان، ماتياس بينينز، أدي عثماني، كينوكو ياسودا، ناسكو أوسكوف، وشارلي ريس.

هل أعجبتك هذه السلسلة؟ إذا كانت لديك أي أسئلة أو اقتراحات بشأن مشاركة مستقبلية، يسعدنا التواصل معك في قسم التعليقات أدناه أو على ‎@kosamari على Twitter.