التعريف بـVisualViewport

ماذا لو قلت لك أنّ هناك أكثر من مساحة عرض واحدة؟

BRRRRAAAAAAAMMMMMMMMMM

وإطار العرض الذي تستخدمه الآن هو في الواقع إطار عرض ضمن إطار عرض.

BRRRRAAAAAAAMMMMMMMMMM

وفي بعض الأحيان، تشير البيانات التي يوفّرها لك نموذج DOM إلى أحد مساحات العرض هذه وليس إلى الأخرى.

BRRRRAAAAM… لحظة، ماذا؟

هذا صحيح، إليك نظرة:

إطار عرض التنسيق مقابل إطار العرض المرئي

يعرض الفيديو أعلاه صفحة ويب يتم التمرير فيها وتكبيرها/تصغيرها باستخدام إصبعَين، بالإضافة إلى خريطة مصغّرة على يسار الصفحة تعرض موضع مساحات العرض ضمن الصفحة.

تكون الأمور بسيطة جدًا أثناء التمرير العادي. تمثل المنطقة الخضراء إطار عرض التنسيق الذي تلتصق به position: fixed عنصرًا.

تصبح الأمور غريبة عند استخدام ميزة التصغير/التكبير بإصبعَين. يمثّل المربّع الأحمر إطار العرض المرئي، وهو الجزء من الصفحة الذي يمكننا رؤيته فعليًا. يمكن أن يتحرك إطار العرض هذا بينما تبقى عناصر position: fixed في مكانها، متّصلة بإطار عرض التصميم. إذا تم التمرير في حدود إطار عرض التنسيق، يتم سحب إطار عرض التنسيق معه.

تحسين التوافق

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

على سبيل المثال، تعرض element.getBoundingClientRect().y الإزاحة ضمن إطار عرض التنسيق. هذا رائع، ولكننا نريد غالبًا معرفة الموضع داخل الصفحة، لذلك نكتب:

element.getBoundingClientRect().y + window.scrollY

ومع ذلك، تستخدم العديد من المتصفّحات إطار العرض المرئي لنظام التشغيل window.scrollY، ما يعني أنّه يتعطّل الرمز البرمجي أعلاه عندما يكبّر المستخدم المحتوى باستخدام إصبعَين.

يغيّر الإصدار 61 من Chrome العنصر window.scrollY للإشارة إلى مساحة عرض التنسيق بدلاً من ذلك، ما يعني أنّ الرمز أعلاه يعمل حتى عند التكبير/التصغير باستخدام إصبعَين. في الواقع، تغيّر المتصفّحات ببطء جميع السمات الموضعية للإشارة إلى إطار عرض التصميم.

باستثناء موقع جديد واحد…

إتاحة إطار العرض المرئي للنص البرمجي

تعرض واجهة برمجة تطبيقات جديدة إطار العرض المرئي على النحو التالي: window.visualViewport. هذه مسودة مواصفات، وقد حصلت على موافقة على مستوى جميع المتصفّحات، وسيتم طرحها في الإصدار 61 من Chrome.

console.log(window.visualViewport.width);

في ما يلي المعلومات التي يوفّرها window.visualViewport:

visualViewport مكانًا للإقامة
offsetLeft المسافة بين الحافة اليسرى لمساحة العرض المرئية ومساحة عرض التنسيق، بالبكسل في CSS
offsetTop المسافة بين الحافة العلوية لإطار العرض المرئي وإطار عرض التنسيق، بالبكسل في CSS
pageLeft المسافة بين الحافة اليسرى لإطار العرض المرئي والحافة اليسرى للمستند، بالبكسل في CSS
pageTop المسافة بين الحافة العلوية لمساحة العرض المرئية والحافة العلوية للمستند، بالبكسل في CSS
width عرض إطار العرض المرئي بالبكسل في CSS
height ارتفاع إطار العرض المرئي بالبكسل في CSS
scale النطاق الذي يتم تطبيقه من خلال التصغير أو التكبير بإصبعَين إذا كان المحتوى ضعف حجمه بسبب التكبير، سيتم عرض 2. ولا يتأثر ذلك devicePixelRatio.

هناك أيضًا حدثان:

window.visualViewport.addEventListener('resize', listener);
فعاليات visualViewport
resize يتم تشغيله عند تغيير width أو height أو scale.
scroll يتم تشغيله عند تغيير offsetLeft أو offsetTop.

عرض توضيحي

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

الأخطاء

لا يتم تشغيل الأحداث إلا عند تغيير إطار العرض المرئي.

قد يبدو هذا الأمر واضحًا، ولكنني لم أدرك ذلك عندما بدأت في اللعب مع visualViewport.

إذا تم تغيير حجم إطار عرض التنسيق بدون تغيير حجم إطار العرض المرئي، لن تتلقّى حدث resize. ومع ذلك، من غير المعتاد أن يتم تغيير حجم إطار عرض التنسيق بدون تغيير العرض/الارتفاع في إطار العرض البصري أيضًا.

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

إذا كنت تريد معرفة كل التغييرات التي تطرأ على مساحة العرض المرئية، بما في ذلك pageTop وpageLeft، عليك الاستماع إلى حدث الانتقال في النافذة أيضًا:

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

تجنُّب تكرار العمل مع مستمعين متعدّدين

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

// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);

let pendingUpdate = false;

function update() {
    // If we're already going to handle an update, return
    if (pendingUpdate) return;

    pendingUpdate = true;

    // Use requestAnimationFrame so the update happens before next render
    requestAnimationFrame(() => {
    pendingUpdate = false;

    // Handle update here
    });
}

لقد أرسلتُ طلبًا بشأن المواصفات، لأنّني أعتقد أنّه قد تكون هناك طريقة أفضل، مثل حدث update واحد.

عدم عمل معالِجات الأحداث

لا يعمل ما يلي بسبب خطأ في Chrome:

الإجراءات غير المُوصى بها

يتضمّن أخطاء: يستخدم معالج حدث

visualViewport.onscroll = () => console.log('scroll!');

بدلاً من ذلك:

الإجراءات الموصى بها

يعمل - يستخدم أداة معالجة حدث

visualViewport.addEventListener('scroll', () => console.log('scroll'));

يتم تقريب قيم الإزاحة

أعتقد (وأتمنى) أنّ هذا خطأ آخر في Chrome.

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

معدّل الأحداث بطيء

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

تسهيل الاستخدام

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

يمكن استخدام visualViewport لتحسين سهولة الاستخدام. على سبيل المثال، إذا كان المستخدم يكبِّر الصورة، يمكنك اختيار إخفاء عناصر position: fixed الزخرفية، لتجنُّب عرقلة المستخدم. ومع ذلك، يجب الحرص على عدم إخفاء محتوى يحاول المستخدم الاطّلاع عليه عن كثب.

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

visualViewport.addEventListener('resize', () => {
    if (visualViewport.scale > 1) {
    // Post data to analytics service
    }
});

هذا كل ما في الأمر! visualViewport هي واجهة برمجة تطبيقات رائعة تحلّ مشكلات التوافق أثناء التنفيذ.