جافاسكربت في المتصفح فهم جملة إحداثيات عناصر صفحة الويب وضبطها في جافاسكربت


عائشة ميرا

يتطلّب تحريك العناصر داخل الصفحة معرفة إحداثياتها. وتعمل معظم الدوالّ في لغة جافاسكربت وفق أحد نظامي الإحداثيات التاليين:

  1. إحداثيات بالنسبة للنافذة: تشبه الموضع الثابت (position:fixed)، حيث تُحسب الإحداثيات إنطلاقًا من الركن العلوي الأيسر للنافذة. نسمي هذه الإحداثيات clientX وclientY، وسنوضح السبب من وراء ذلك فيما بعد عند التطرق لخاصيات الأحداث (event properties).
  2. إحداثيات بالنسبة للصفحة: تشبه الموضع المطلق (position:absolute)، حيث تُحسب الإحداثيات إنطلاقًا من الركن العلوي الأيسر للصفحة. نسمي هذه الإحداثيات pageX وpageY.

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

نأخذ في هذه الصورة نقطة من الصفحة لإظهار إحداثياتها قبل التمرير (الصورة على اليسار) وبعد التمرير (الصورة على اليمين).

001coordinates.png

عند تمرير الصفحة:

  • بقيت الترتيبة pageY بالنسبة للصفحة على حالها ولم تتغير، حيث تُحسب إنطلاقًا من أعلى الصفحة (الذي لم يعد ظاهرًا في النافذة بعد التحريك).
  • تغيّرت الترتيبة clientY بالنسبة للنافذة (أصبح السهم أقصر) لأن النقطة نفسها أصبحت أقرب لأعلى النافذة.

إحداثيات عنصر ما: getBoundingClientRect

تعيد الدالّة elem.getBoundingClientRect() الإحداثيات (بالنسبة للنافذة) الخاصة بأصغر مستطيل يكون العنصر elem محصورًا فيه بصفته كائنا من الصنف المدمج DOMRect

أهم خاصيات الصنف DOMRect:

  • x وy: إحداثيات نقطة بداية المستطيل بالنسبة للنافذة (X وY).
  • width وheight: عرض وطول المستطيل (يمكن أن تكون القيم سالبة).

كما يملك الصنف DOMRect خاصيات مشتقة:

  • top وbottom: ترتيبتي ركني المستطيل العلوي والسفلي.
  • left وright: فاصلتي ركني المستطيل الأيسر والأيمن. فعلى سبيل المثال، عند استعمال الدالّةbutton.getBoundingClientRect()‎للحصول على إحداثيات زرٍ ما مع تمرير الصفحة نزولًا وصعودًا، ستلاحظ أنه كلما تغيّر موضع الزر بالنسبة للنافذة تغيّرت إحداثياته بالنسبة للنافذة (yوtopوbottom` إذا كان التمرير عموديًا).

إليك صورة تمثيلية تعرض مخرجات (output) الدالّة elem.getBoundingClientRect()‎:

002getBoundingClientRect.png

لاحظ أن الخاصيات x وy وwidth وheight تصف المستطيل وصفًا وافيًا، حيث يمكن حساب بقية الخاصيات المشتقة إنطلاقًا منها:

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

لاحظ هنا أنه:

  • يمكن للإحداثيات أن تكون أعدادًا عشريةً مثل العدد 10.5. يُعد هذا الأمر عاديًا، حيث يستعمل المتصفح في عمليات الحساب عمليات القسمة، كما أنه ليس علينا تقريب هذه القيم (to round) عند إسنادها للخاصيتين style.left وstyle.top.
  • يمكن أن تكون قيم الإحداثيات سالبة. في حالة تمرير الصفحة مثلا بحيث يكون العنصر elem تمامًا فوق النافذة، تكون قيمة الخاصية elem.getBoundingClientRect().top سالبة.

ملاحظة: لماذا نحتاج إلى الخاصيات المشتقة؟ وما الهدف من وجود الخاصيتين left وtop في وجود الإحداثيات x وy؟

يُعرّف المستطيل في الرياضيات بإحداثيات نقطة البداية (x,y) وشعاع الاتجاه (width,height)، وبذلك تُستخدَم الخاصيات المشتقة لتسهيل الأمر فقط.

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

وتعني القيم السالبة للخاصيتين width وheight أن المستطيل يبدأ من الركن الأيمن السفلي ليتشكّل عن طريق سحب الفأرة في الاتجاه الأيسر نحو الأعلى. وتمثل الصورة الموالية مستطيلًا بقيم سالبة لكل من الخاصيتين width وheight (مثال: width=-200, height=-100)

003negativeValues.png

وكما ترى، لا تتطابق قيم الخاصيتين left وtop مع قيم الخاصيتين x وy في هذه الحالة. ولكن عمليًا، تُعيد دائمًا الدالّة elem.getBoundingClientRect()‎ قيمًا موجبةً للعرض والطول. غير أن السبب الذي حملنا على ذكر القيم السالبة هنا، هو أن هذه القيم التي تبدو لك مكرّرةً، ليست مكرّرةً في الحقيقة.


ملاحظة: لا يدعم المتصفح Internet Explorer الإحداثيات x وy

لا يدعم المتصفح Internet Explorer خاصيتي الإحداثيات x وy لاسباب تاريخية. يمكنك إذًا إمّا استعمال مكتبة بديلة (polyfill) (إضافة جالبات الخاصيات (getters) للخاصية DomRect.prototype) أو استعمال الخاصيتين left وtop فقط بما أنهما دائمًا متطابقتين مع الإحداثيات x وy في الحالات التي تكون فيها قيم الخاصيتين width وheight موجبة، وخاصة في القيم التي تعيدها الدالّة elem.getBoundingClientRect()‎.


ملاحظة: تختلف الإحداثيات right وbottom عن خاصيات التموضع المعتمدة في شيفرة CSS

هناك أوجه تشابه جليّة بين الإحداثيات بالنسبة للنافذة، والموضع (position:fixed) المعتمد في شيفرة CSS. ولكن إذا تحدثنا عن التموضع في شيفرة CSS، تمثّل الخاصية right المسافة إنطلاقًا من الركن الأيمن، وتمثّل الخاصية bottom المسافة إنطلاقًا من الركن السفلي.

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


elementFromPoint(x, y)‎

تعيد الدالّة document.elementFromPoint(x, y)‎ العنصر الأكثر تداخلًا مع إحداثيات النافذة (x, y)، وصيغتها كالآتي:

let elem = document.elementFromPoint(x, y);

فعلى سبيل المثال تُلقي هذه الشيفرة الضوء أو تُبرِز وسم العنصر المتموضع وسط النافذة أثناء معاينة الشيفرة:

let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;
let elem = document.elementFromPoint(centerX, centerY);
elem.style.background = "red";
alert(elem.tagName);

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


ملاحظة: تعيد الدالّةelementFromPoint القيمة null إذا مُرِّرَت لها إحداثيات خارجة عن نطاق النافذة

تعمل الدالّة document.elementFromPoint(x,y)‎ فقط إذا كانت الإحداثيات (x,y) المُمَرّرة لها داخل نطاق الجزء من الصفحة الظاهر في النافذة. وإذا كانت إحدى الإحداثيات سالبةً أو خارج نطاق طول أوعرض النافذة، تُعيد الدالّة القيمة null. وإليك نوع الخطأ الذي ينجر عن ذلك:

let elem = document.elementFromPoint(x, y);
//null القيمة elementFromPoint إذا كانت الإحداثيات خارج نطاق النافذة تعيد الدالّة (elem = null)
elem.style.background = ''; // Error!


استعمال الإحداثيات للتموضع الثابت (fixed)

غالبا ما نحتاج للإحداثيات من أجل إضافة عنصرٍ ما في موضعٍ ما. ولإظهار عنصرٍ بمحاذاة عنصرٍ آخر، يمكننا استعمال الدالّة getBoundingClientRect للحصول على إحداثياته، ثم الاستعانة بالخاصية position مع الخاصيتين left وtop (أو right وbottom).

فعلى سبيل المثال، تُظهر الدالّة createMessageUnder(elem, html)‎ في الشيفرة الموالية رسالةً تحت العنصر elem:

let elem = document.getElementById("coords-show-mark");
function createMessageUnder(elem, html) {
  //  إنشاء العنصر الذي يضم الرسالة
  let message = document.createElement('div');
  //يستحسن استعمال صنف CSS لتحديد النمط التنسيقي
  message.style.cssText = "position:fixed; color: red";
  //إسناد الإحداثيات دون أن ننسى الحرفين 'px'
  let coords = elem.getBoundingClientRect();
  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";
  message.innerHTML = html;
  return message;
}
// :الاستعمال
// إضافته للصفحة لمدّة خمس ثوان 
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

عند الضغط على الزر صاحب الخاصية id="coords-show-mark"‎ تظهر الرسالة تحته.

يمكن التعديل على الشيفرة لإظهار الرسالة على اليسار، أو على اليمين، أو في الأسفل، أو تطبيق شيفرة حركات CSS (أي CSS animation) عليه لجعله يختفي تدريجيا، وهكذا دواليك. سيكون ذلك سهلًا طالما نملك كافة إحداثيات ومقاسات العنصر.

لاحظ هذا التفصيل المهم: في حالة ما إذا قمنا بتمرير الصفحة تبتعد الرسالة عن الزر. والسبب واضح: موضع الرسالة ثابت (position:fixed)، لذا تبقى في نفس الموضع من النافذة عند تمرير الصفحة. ولتغيير ذلك، علينا استعمال الإحداثيات بالنسبة للصفحة، والموضع المطلق (position:absolute).

إحداثيات الصفحة

يبدأ احتساب الإحداثيات بالنسبة للصفحة من الركن العلوي الأيسر للصفحة، لا النافذة. ويقابِل الإحداثيات بالنسبة للنافذة في لغة CSS الموضع position:fixed، أما الإحداثيات بالنسبة للصفحة فيقابلها في لغة CSS الموضع position:absolute من الأعلى.

يمكننا استعمال الموضع position:absolute والخاصيتين left وtop لوضع عنصرٍ ما في موضعٍ ما من الصفحة ، بحيث يبقى في نفس الموضع عند تمرير الصفحة. ولكن علينا أولا إيجاد الإحداثيات الصحيحة.

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

  • pageY=clientY+ طول الجزء العمودي من الصفحة غير الظاهر في النافذة.
  • pageX=clientX+ عرض الجزء الأفقي من الصفحة غير الظاهر في النافذة.

تأخذ الدالّة getCoords(elem)‎ الإحداثيات بالنسبة للنافذة باستخدام الدالّة elem.getBoundingClientRect()‎ وتضيف مسافة التمرير لها.

//  الحصول على إحداثيات العنصر بالنسبة للصفحة
function getCoords(elem) {
  let box = elem.getBoundingClientRect();
  return {
    top: box.top + window.pageYOffset,
    right: box.right + window.pageXOffset,
    bottom: box.bottom + window.pageYOffset,
    left: box.left + window.pageXOffset
  };
}

في المثال أعلاه، استعملنا الموضع position:absolute، وبذلك تبقى الرسالة بمحاذاة العنصر عند تمرير الصفحة وتصبح الدالّة createMessageUnder بعد التعديل كالآتي:

function createMessageUnder(elem, html) {
  let message = document.createElement('div');
  message.style.cssText = "position:absolute; color: red";
  let coords = getCoords(elem);
  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";
  message.innerHTML = html;
  return message;
}

الخلاصة

لكلّ نقطة من الصفحة إحداثيات:

  1. بالنسبة للنافذة: تُساوي elem.getBoundingClientRect()‎
  2. بالنسبة للصفحة: تُساوي elem.getBoundingClientRect()‎ + مقدار تمرير الصفحة.

يُنصح باستخدام الإحداثيات بالنسبة للنافذة مع الموضع position:fixed، والإحداثيات بالنسبة للصفحة مع الموضع position:absolute. ولِكِلَا نظامي الإحداثيات إيجابيات وسلبيات، حيث يُستحسن استعمال أحدهما في حالات معيّنة، والآخر في حالات أخرى، كما هو الحال بالنسبة للمواضع absolute وfixed في لغة CSS.

تمارين

إيجاد إحداثيات المستطيل بالنسبة للنافذة

درجة الأهمية:5

لدينا في الصورة الموالية صفحة ويب بها مستطيل أخضر “field”. استعمل لغة جافاسكربت للحصول على إحداثيات الأركان المؤشر عليها بالأسهم الحمراء بالنسبة للنافذة.

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

يمكنك الاطلاع على شيفرة التمرين عبر هذا الرابط

004greenField.png

عليك استعمال نموذج تمثيل الصفحة ككائن DOM للحصول على الإحداثيات بالنسبة للنافذة لكل من:

  • الركن الخارجي العلوي الأيسر (وهو سهل).
  • الركن الخارجي السفلي الأيمن (وهو سهل أيضا).
  • الركن الداخلي العلوي الأيسر (صعب بعض الشيء).
  • الركن الداخلي السفلي الأيمن ( هناك عدة طرائق للحصول عليه. اختر واحدة منها)

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

الحل

الركنان الخارجيان

تكون إحداثيات الركنين الخارجيين عادةً هي الإحداثيات التي تعيدها الدالّة elem.getBoundingClientRect()‎.

في الشيفرة التالية، تمثّل answer1 إحداثيات الركن العلوي الأيسر، وتمثّل answer2 إحداثيات الركن السفلي الأيمن.

let coords = elem.getBoundingClientRect();
let answer1 = [coords.left, coords.top];
let answer2 = [coords.right, coords.bottom];
الركن الداخلي العلوي الأيسر

الفرق بينه وبين الركن الخارجي هو عرض الإطار. والطريقة المثلى للحصول على هذه المسافة هي باستعمال الخاصيتين clientLeft وclientTop.

let answer3 = [coords.left + field.clientLeft, coords.top + field.clientTop];
الركن الداخلي السفلي الأيمن

علينا في هذه الحالة طرح عرض الإطار من الإحداثيات الخارجية. ويمكننا استعمال لغة CSS كالآتي:

let answer4 = [
  coords.right - parseInt(getComputedStyle(field).borderRightWidth),
  coords.bottom - parseInt(getComputedStyle(field).borderBottomWidth)
];

هناك أيضًا طريقةٌ بديلةٌ، وربما هي أحسن، تكون بإضافة قيم الخاصيتين clientWidth وclientHeight لإحداثيات الركن العلوي الأيسر.

let answer4 = [
  coords.left + elem.clientLeft + elem.clientWidth,
  coords.top + elem.clientTop + elem.clientHeight
];

يمكنك الاطلاع على شيفرة الحل عبر هذا الرابط

إظهار ملاحظة بمحاذاة عنصرٍ ما

درجة الأهمية: 5

أنشئ الدالّة positionAt(anchor, position, elem)‎ التي تضع العنصرelem في موضعٍ position قريبٍ من العنصرanchor.

يكون الموضع position عبارة عن سلسلة نصية ويأخذ إحدى القيم التالية:

  • "top": تضع العنصر elem فوق العنصرanchor مباشرة.
  • "right": تضع العنصرelem على يمين العنصرanchor مباشرة.
  • "bottom": تضع العنصرelem أسفل العنصر anchor مباشرة.

تُستعمل هذه الدالّة بداخل الدالّة showNote(anchor, position, html)‎ (الشيفرة الخاصة بالدالّة معطاة في نص التمرين) التي تُنشئ عنصرًا يحتوي على ملاحظة من خلال الشيفرة html المُمَرّرة لها، وتظهر هذه الملاحظة في الموضع position المُمَرر لها بمحاذاة العنصر anchor، وهذه صورة تمثيلية للنتيجة التي نريد الوصول اليها:

005nearAnchorPosition.png

يمكنك الاطلاع على شيفرة التمرين عبر هذا الرابط.

الحل

في هذا التمرين نحتاج فقط حسابَ الإحداثيات بشكلٍ دقيقٍ. ولتفاصيل أكثر، اطلع على شيفرة الحل. لاحظ أن العناصر ينبغي أن تكون موجودةً في الصفحة حتى يتسنى لنا قراءة الخاصية offsetHeight وخاصيات أخرى. حيث أن العنصر المخفي (display:none) أو غير الموجود بالصفحة لا يملك مقاسات.

يمكنك الاطلاع على الحل عبر هذا الرابط.

إظهار ملاحظة بمحاذاة عنصرٍ ما (مطلق)

درجة الأهمية: 5

عدِّل على حل التمرين السابق بحيث تستعمل الملاحظة موضعًا مطلقًا (position:absolute) بدلًا من الموضع الثابت (position:fixed). هذا ما يُبقِي الملاحظة بمحاذاة العنصر عند تمرير الصفحة.

استعمل حل التمرين السابق كنقطة انطلاق، ولمعاينة التمرين أضف النمط التنسيقي التالي: <body style="height: 2000px"‎>

الحل

الحل بسيطٌ جدًا.

  • غيّر الخاصية position الخاصة بالمحدِّد .note من position:fixed إلىposition:absolute في شيفرة CSS.
  • استعمل الدالّة getCoords()‎ المعرّفة أعلاه للحصول على الإحداثيات بالنسبة للصفحة.

يمكنك الإطلاع على الحل عبر هذا الرابط.

وضع الملاحظة داخل العنصر (مطلق)

درجة الأهمية: 5

استعمل التمرين السابق (إظهار ملاحظة بمحاذاة عنصرٍ ما [مطلق]) وعدّل على الدالّة positionAt(anchor, position, elem)‎ لتسمح بإضافة عنصر elem داخل العنصرanchor.

والقيم الجديدة للموضع position هي:

  • bottom-out ،right-out ،top-out: هي نفس المواضع المعرّفة في الحل الأول والتي تسمح بإضافة العنصرelem فوق أو على يمين أو تحت العنصر anchor.
  • bottom-in، right-in، top-in: تسمح بإضافة العنصر elem داخل العنصر anchor بحيث يلتصق بالركن العلوي أو الأيمن أو السفلي.

على سبيل المثال:

// تظهر الملاحظة فوق إطار المقولة
positionAt(blockquote, "top-out", note);

// تظهر الملاحظة داخل إطار المقولة في الأعلى
positionAt(blockquote, "top-in", note);

والنتيجة تكون كالآتي:

006inAnchorPosition.png

خذ شيفرة حل التمرين السابق (إظهار ملاحظة بمحاذاة عنصرٍ ما [مطلق]) واستعن بها كشيفرة نص لهذا التمرين.

الحل

يمكنك الاطلاع على الحل عبر هذا الرابط.

ترجمة -وبتصرف- للفصل Coordinates من كتاب Browser: Document, Events, Interfaces





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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن