يتطلّب تحريك العناصر داخل الصفحة معرفة إحداثياتها. وتعمل معظم الدوالّ في لغة جافاسكربت وفق أحد نظامي الإحداثيات التاليين:
-
إحداثيات بالنسبة للنافذة: تشبه الموضع الثابت (
position:fixed
)، حيث تُحسب الإحداثيات إنطلاقًا من الركن العلوي الأيسر للنافذة. نسمي هذه الإحداثياتclientX
وclientY
، وسنوضح السبب من وراء ذلك فيما بعد عند التطرق لخاصيات الأحداث (event properties
). -
إحداثيات بالنسبة للصفحة: تشبه الموضع المطلق (
position:absolute
)، حيث تُحسب الإحداثيات إنطلاقًا من الركن العلوي الأيسر للصفحة. نسمي هذه الإحداثياتpageX
وpageY
.
عندما يكون أعلى الصفحة ظاهرًا في النافذة، يكون الركن العلوي الأيسر للصفحة متطابقا مع الركن العلوي الأيسر للنافذة، وتكون الإحداثيات أيضًا متساوية. ولكن بعد تحريك الصفحة تتغيّر إحداثيات العناصر بالنسبة للنافذة كلما تحركت العناصر ضمن النافذة. أما الإحداثيات بالنسبة للصفحة فلا تتغيّر، بل تبقى دائمًا ثابتة.
نأخذ في هذه الصورة نقطة من الصفحة لإظهار إحداثياتها قبل التمرير (الصورة على اليسار) وبعد التمرير (الصورة على اليمين).
عند تمرير الصفحة:
-
بقيت الترتيبة
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()
:
لاحظ أن الخاصيات 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
)
وكما ترى، لا تتطابق قيم الخاصيتين 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; }
الخلاصة
لكلّ نقطة من الصفحة إحداثيات:
-
بالنسبة للنافذة: تُساوي
elem.getBoundingClientRect()
-
بالنسبة للصفحة: تُساوي
elem.getBoundingClientRect()
+ مقدار تمرير الصفحة.
يُنصح باستخدام الإحداثيات بالنسبة للنافذة مع الموضع position:fixed
، والإحداثيات بالنسبة للصفحة مع الموضع position:absolute
. ولِكِلَا نظامي الإحداثيات إيجابيات وسلبيات، حيث يُستحسن استعمال أحدهما في حالات معيّنة، والآخر في حالات أخرى، كما هو الحال بالنسبة للمواضع absolute
وfixed
في لغة CSS.
تمارين
إيجاد إحداثيات المستطيل بالنسبة للنافذة
درجة الأهمية:5
لدينا في الصورة الموالية صفحة ويب بها مستطيل أخضر “field”. استعمل لغة جافاسكربت للحصول على إحداثيات الأركان المؤشر عليها بالأسهم الحمراء بالنسبة للنافذة.
أضيفت للصفحة إمكانية الحصول على إحداثيات أيّ نقطة منها بمجرد النقر عليها لتسهيل الأمر عليك.
يمكنك الاطلاع على شيفرة التمرين عبر هذا الرابط
عليك استعمال نموذج تمثيل الصفحة ككائن 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
، وهذه صورة تمثيلية للنتيجة التي نريد الوصول اليها:
يمكنك الاطلاع على شيفرة التمرين عبر هذا الرابط.
الحل
في هذا التمرين نحتاج فقط حسابَ الإحداثيات بشكلٍ دقيقٍ. ولتفاصيل أكثر، اطلع على شيفرة الحل. لاحظ أن العناصر ينبغي أن تكون موجودةً في الصفحة حتى يتسنى لنا قراءة الخاصية 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);
والنتيجة تكون كالآتي:
خذ شيفرة حل التمرين السابق (إظهار ملاحظة بمحاذاة عنصرٍ ما [مطلق]) واستعن بها كشيفرة نص لهذا التمرين.
الحل
يمكنك الاطلاع على الحل عبر هذا الرابط.
ترجمة -وبتصرف- للفصل Coordinates من كتاب Browser: Document, Events, Interfaces
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.