ResizeMonitorer: إنها مثل document.onresize للعناصر

يُعلمك الخيار ResizeObserver عند تغيير حجم عنصر.

قبل ResizeObserver، كان عليك إرفاق مستمع بحدث resize للحصول على إشعار بشأن أي تغيير في سمات مساحة العرض. في معالج الحدث، عليك بعد ذلك تحديد العناصر التي تأثّرت بهذا التغيير واستدعاء روتين معيّن للتفاعل بشكل مناسب. إذا كنت بحاجة إلى معرفة سمات العنصر الجديدة بعد تغيير حجمه، كان عليك استدعاء getBoundingClientRect() أو getComputedStyle()، ما قد يؤدي إلى تعذُّر عرض العناصر إذا لم تهتم بتجميع كل عمليات القراءة وكل عمليات الكتابة.

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

لهذا السبب، ResizeObserver هو عنصر أساسي مفيد. ويتفاعل مع التغييرات في حجم أيّ من العناصر المرصودة، بغض النظر عن سبب التغيير. ويتيح أيضًا الوصول إلى الحجم الجديد للعناصر المرصودة.

توافق المتصفّح

  • Chrome: 64
  • ‫Edge: 79
  • Firefox: 69
  • ‫Safari: 13.1

المصدر

واجهة برمجة التطبيقات

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

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

بعض التفاصيل

ما الذي يتم الإبلاغ عنه؟

بشكل عام، يُبلغ عنصر ResizeObserverEntry عن مربّع محتوى عنصر من خلال سمة تُسمى contentRect، والتي تعرض عنصرًا من نوع DOMRectReadOnly. مربّع المحتوى هو المربّع الذي يمكن وضع المحتوى فيه. وهو مربّع الحدود مطروحًا منه الحشو.

مخطّط بياني لنموذج مربّعات CSS

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

اعتبارًا من الإصدار 84 من Chrome، يحتوي ResizeObserverEntry على ثلاث خصائص جديدة لتقديم معلومات أكثر تفصيلاً. تُعرِض كلّ من هاتين السمتَين عنصرًا ResizeObserverSize يحتوي على سمة blockSize وسمة inlineSize. تتناول هذه المعلومات العنصر الذي تم رصده في وقت استدعاء دالة ردّ الاتصال.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

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

إنّ توافق هذه السمات مع المنصات محدود، ولكن Firefox يتوافق مع السمتَين الأوليتَين.

متى يتم الإبلاغ عن هذه المشكلة؟

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

حسنًا

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

طلب الانضمام

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

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

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

تسمح لك ResizeObserver بكتابة رمز برمجي واحد يعالج كلا السيناريوهَين. إعادة تغيير حجم النافذة هو حدث يمكن أن يرصده ResizeObserver بموجب التعريف، ولكن يؤدي استدعاء appendChild() أيضًا إلى تغيير حجم هذا العنصر (ما لم يتم ضبطoverflow: hidden)، لأنّه يحتاج إلى توفير مساحة للعناصر الجديدة. مع أخذ ذلك في الاعتبار، يستغرق الأمر بضعة أسطر فقط لتحقيق أثره المرجو:

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

رائع، أليس كذلك؟

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

وتشمل حالات الاستخدام الأخرى أي نوع من العناصر المخصّصة التي تُنشئ تنسيقها الخاص. حتى ResizeObserver، لم تكن هناك طريقة موثوقة للحصول على إشعار عند تغيير أبعادها حتى يمكن ترتيب عناصرها مرة أخرى.

تأثيرات "مدى استجابة الصفحة لتفاعلات المستخدم" (INP)

مدى استجابة الصفحة لتفاعلات المستخدم (INP) هو مقياس يقيس مدى استجابة الصفحة بشكل عام لتفاعلات المستخدم. إذا كان مقياس INP للصفحة ضمن الحدّ "الجيد"، أي 200 ملي ثانية أو أقل، يمكن القول إنّ الصفحة تستجيب بشكل موثوق لتفاعلات المستخدِم معها.

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

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

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

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

الخاتمة

يتوفّر ResizeObserver في جميع المتصفّحات الكبرى ويوفّر طريقة فعّالة لرصد عمليات تغيير حجم العناصر على مستوى العنصر. فقط احرِص على عدم تأخير العرض كثيرًا باستخدام واجهة برمجة التطبيقات هذه القوية.