التعريف بـVisualViewport

جيك أرشيبالد
جيك أرشيبالد

ماذا لو أخبرتك أنّ هناك أكثر من إطار عرض واحد.

BRRRRAAAAAAAMMMMMMMM

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

BRRRRAAAAAAAMMMMMMMM

وأحيانًا، تشير البيانات التي يقدمها لك DOM إلى أحد إطارَي العرض هذا وليس إلى الآخر.

BRRRRAAAAM... ماذا تنتظر؟

هذا صحيح، ألقِ نظرة:

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

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

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

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

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

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

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

element.getBoundingClientRect().y + window.scrollY

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

يغيّر Chrome 61 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، وهذا أمر شائع جدًا. أثناء الانتقال المنتظم للمستندات، يظل إطار العرض المرئي مغلقًا في أعلى يمين إطار عرض التنسيق، لذا لا يتم تنشيط scroll على visualViewport.

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

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

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

مثلما يتم الاستماع إلى scroll وresize على النافذة، قد تستدعي نتيجة لذلك نوعًا من دالة "تحديث". ومع ذلك، من الشائع أن يقع العديد من هذه الأحداث في نفس الوقت. إذا غيّر المستخدم حجم النافذة، سيتم عرض 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، لا يعمل ما يلي:

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

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

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 هي واجهة برمجة تطبيقات صغيرة ورائعة تعمل على حل مشكلات التوافق طوال الوقت.