اذهب إلى المحتوى

التمرير (scrolling) وأحداثه والتعامل معه في جافاسكربت


محمد أمين بوقرة

يمكّن الحدث scroll من الاستجابة لتمرير الصفحة أو عنصرٍ ما. يمكننا فعل العديد من الأمور الجيّدة في هذا الباب. على سبيل المثال:

  • إظهار/إخفاء أزرار التحكّم أو معلوماتٍ إضافيّة حسب مكان المستخدم في المستند.
  • تحميل المزيد من البيانات عندما يمرّر المستخدم الصفحة حتى النهاية.

إليك دالّة صغيرة تعرض الموضع الحاليّ للتمرير:

window.addEventListener('scroll', function() {
  document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px';
});

عند اشتغالها:

يعمل الحدث scroll سواءً مع النافذة window أو العناصر القابلة للتمرير.

منع التمرير

كيف يمكن أن نجعل شيئا ما غير قابل للتمرير؟

لا يمكن منع التمرير بمجرّد استخدام event.preventDefault()‎ في منصت الحدث onscroll، لأنّ هذا الحدث يقع بعد حصول التمرير.

لكن يمكننا منع التمرير باستخدام event.preventDefault()‎ مع الحدث الذي يتسبّب في التمرير، مثل حدث keydown عند الضغط على المفاتيح pageUp وpageDown. فإذا وضعنا معالجات لهذه اﻷحداث مع event.preventDefault()‎ داخلها، لن يحصل التمرير.

هناك عدّة طرق لحصول التمرير، لذا يجدر استخدام الخاصّيّة overflow في CSS.

هذه بعض التمارين التي يمكنك حلّها أو النظر فيها للاطلاع على بعض تطبيقات onscroll.

التمارين

صفحة لا متناهية

أنشئ صفحة لا متناهية. عندما يقوم الزائر بالتمرير حتى النهاية، يُلحَق التاريخ والوقت الحاليّان بالنصّ تلقائيّا (ليستطيع الزائر تمرير المزيد)، كما يمكنك مشاهدة ذلك من هنا.

يُرجى التنبّه إلى ميزتين مهمّتين للتمرير:

  1. التمرير "متمدّد". يمكننا مواصلة التمرير قليلا إلى ما وراء بداية أو نهاية المستند في بعض المتصفّحات/الأجهزة (تظهر مساحة فارغة في اﻷسفل، قبل أن "يرتدّ" المستند كما كان).
  2. التمرير غير دقيق. عندما نمرّر الصفحة حتى النهاية، قد نكون في الواقع على بعد حوالي 0-50px من نهاية المستند الحقيقيّة.

وبذلك، فإنّ "التمرير حتى النهاية" يعني أنّه لا بدّ للزائر ألّا يبعد أكثر من 100px من نهاية المستند.

ملاحظة: قد يكون الغرض في الحالات الواقعيّة عرض "المزيد من الرسائل" أو "المزيد من السلع".

أنجز التمرين في البيئة التجريبيّة.

الحلّ

يكمن الحلّ في إنشاء دالّة تضيف المزيد من التواريخ إلى الصفحة (أو تحمّل المزيد من الأشياء في الحالات الواقعيّة) عندما نكون في آخر الصفحة. يمكننا استدعاؤها مباشرة وإضافتها كمعالج window.onscroll.

لكنّ أهمّ سؤال هو: "كيف نعرف أنّ الصفحة قد وصلت إلى اﻷسفل؟".

لنستخدم في ذلك الإحداثيّات بالنسبة إلى النافذة.

يمكننا الحصول على الإحداثيّات بالنسبة إلى النافذة في كامل المستند بواسطة document.documentElement.getBoundingClientRect()‎، وتكون الخاصيّة bottom هي إحداثيّات أسفل المستند بالنسبة إلى النافذة.

على سبيل المثال، إذا افترضنا أنّ ارتفاع كامل مستند HTML هو 2000px، يكون لدينا:

// عندما نكون في أعلى الصفحة
// القمّة بالنسبة للنافذة = 0
document.documentElement.getBoundingClientRect().top = 0

// الأسفل بالنسبة للنافذة = 2000
// المستند طويل، لذا فإنّه يتجاوز على اﻷرجح أسفل النافذة بكثير
document.documentElement.getBoundingClientRect().bottom = 2000

إذا مرّرنا 500px، فإنّ:

// 500px أعلى المستند فوق النافذة بـ
document.documentElement.getBoundingClientRect().top = -500
// 500px أسفل المستند أقرب بـ 
document.documentElement.getBoundingClientRect().bottom = 1500

إذا مرّرنا إلى النهاية، بافتراض أنّ ارتفاع النافذة هو 600px:

// 1400px أعلى المستند فوق النافذة بـ
document.documentElement.getBoundingClientRect().top = -1400
// 600px أسفل المستند تحت النافذة بـ 
document.documentElement.getBoundingClientRect().bottom = 600

يُرجى التنبّه أنّ اﻷسفل bottom لا يمكن أن يكون 0 لأنّه لا يرتقي إلى أعلى النافذة أبدا. أقلّ حدّ للإحداثيّة bottom هو ارتفاع النافذة (افترضنا أنّها 600)، لا نستطيع تمريرها أعلى من ذلك.

يمكننا الحصول على ارتفاع النافذة بواسطة document.documentElement.clientHeight.

في هذا التمرين، نحتاج أن نعرف متى نبعد عن أسفل المستند بأقلّ من 100px (بمعنى يكون أسفل المستند 600-700px بالنسبة للنافذة، إذا كان ارتفاعها 600).

هذه هي الدالّة إذًا:

function populate() {
  while(true) {
    // أسفل المستند
    let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;

    // على النهاية)‏‎ 100px إذا لم يمرّر المستخدم بعيدا بما يكفي (أكثر من
    if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;

    // لنُضف المزيد من البيانات
    document.body.insertAdjacentHTML("beforeend", `<p>Date: ${new Date()}</p>`);
  }
}

شاهد الحلّ في البيئة التجريبيّة.

زرّ اﻷعلى/اﻷسفل

اﻷهميّة:5

أنشئ زرّ "الانتقال نحو اﻷعلى" للمساعدة على تمرير الصفحة. يجب أن يعمل كالتالي:

  • إذا لم تُمرَّر النافذة نحو اﻷسفل بمقدار ارتفاع النافذة على اﻷقلّ، فإنّه لا يظهر.
  • إذا مُرّرت الصفحة نحو اﻷسفل بمقدار يزيد على ارتفاع النافذة، يظهر سهم يشير نحو اﻷعلى في الزاوية العليا من اليسار. إذا أعيد تمرير الصفحة نحو اﻷعلى، فإنّه يختفي.
  • عند الضغط على السهم، فإنّ الصّفحة تُمرّر حتى القمّة. كما هو مبيّن هنا (في الزاوية العليا من اليسار، مرّر الصفحة لتراه).

أنجز التمرين في البيئة التجريبيّة.

الحل

شاهد الحلّ في البيئة التجريبيّة.

حمّل الصور المرئيّة

اﻷهميّة: 4

لنفترض أنّ لدينا عميلا ذا سرعة قليلة ونريد أن نحفظ له بيانات الجوّال. فقرّرنا لهذا الغرض ألّا نظهر الصور فورا، بل نضع مكانها نائبًا (placeholder)، بهذا الشكل:

<img src="placeholder.svg" width="128" height="128" data-src="real.jpg">

في البداية، تكون كلّ الصّور placeholder.svg. وعندما تُمرّر الصفحة إلى موضع يمكن للمستخدم فيه أن يرى الصورة، نغيّر قيمة src إلى القيمة الموجودة في data-src، فتُحمَّل الصّورة.

يمكن مشاهدة المثال حيّا من هنا. قم بالتمرير لترى الصور تُحمّل "حسب الطلب".

المتطلّبات:

  • عندما تُحمّل الصفحة، يجب أن تُحمّل الصور التي على الشاشة فورا، قبل حصول التمرير.
  • قد تكون بعض الصور عاديّة، دون data-src. يجب أن تتركها الشيفرة على حالها.
  • بعد انتهاء تحميل الصورة، يجب ألّا يُعاد تحميلها مرّة أخرى عند تمريرها إلى اﻷسفل/اﻷعلى.

ملاحظة: إذا أمكن، أنجز حلّا أكثر تقدّما يتيح "التحميل المسبق" للصور التي تكون قبل/بعد الموضع الحاليّ بمقدار صفحة.

ملاحظة أخرى: ينبغي فقط معالجة التمرير العموديّ، لا التمرير الأفقيّ.

أنجز التمرين في البيئة التجريبيّة.

الحل

يجب أن يعمل المعالجُ onscroll على تحديد الصّور مرئيّة وعرضها. نريده أيضا أن يشتغل عند تحميل الصفحة، لاكتشاف الصور المرئيّة فورا وعرضها. يجب أن تشتغل الشيفرة بعد الانتهاء من تحميل المستند، لتتمكّن من الوصول إلى محتواه. أو توضع أسفل العنصر <body>.

// ...محتوى الصفحة الفوق...

function isVisible(elem) {

  let coords = elem.getBoundingClientRect();

  let windowHeight = document.documentElement.clientHeight;

  // مرئيّ؟ elem هل الحدّ العلويّ لـ
  let topVisible = coords.top > 0 && coords.top < windowHeight;

  // مرئيّ؟ elem هل الحدّ السفليّ لـ
  let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;

  return topVisible || bottomVisible;
}

تستخدم الدالّةُ showVisible()‎ اختبارَ الرؤية، المُنفَّذ بواسطة isVisible()‎، لتحميل الصور المرئيّة:

function showVisible() {
  for (let img of document.querySelectorAll('img')) {
    let realSrc = img.dataset.src;
    if (!realSrc) continue;

    if (isVisible(img)) {
      img.src = realSrc;
      img.dataset.src = '';
    }
  }
}

showVisible();
window.onscroll = showVisible;

ملاحظة: هناك أيضا حلّ بديل حيث تعمل فيه isVisible " على "التحميل المسبق" للصور الموجودة ضمن صفحة واحدة أعلى/أسفل موضع التمرير الحاليّ للمستند.

ترجمة -وبتصرف- للمقال Scrolling من سلسلة Browser: Document, Events, Interfaces لصاحبها Ilya Kantor


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...