لدورة حياة صفحة HTML ثلاثة أحداث مهمّة:
-
DOMContentLoaded
-- حمّل المتصفّحُ بنية HTML الكاملة للصفحة، وبُنيت شجرة DOM، لكنّ الموارد الخارجيّة كالصور<img>
وأوراق التنسيق stylesheets قد لا تكون حُمّلت بعد. -
load
-- حمّل المتصفّح الـ HTML، وكذلك جميع الموارد الخارجيّة من صور وتنسيقات وغيرها. -
beforeunload/unload
-- يغادر المستخدم الصفحة.
يمكن لأيّ من هذه الأحداث أن يكون ذا فائدة:
-
الحدث
DOMContentLoaded
-- صارت شجرة DOM جاهزة، ويمكن للمعالج إذًا البحث فيه عن العقد، وتهيئة الواجهة. -
الحدث
load
-- قد حُمّلت الموارد الخارجيّة، وبذلك تكون التنسيقات قد طُبّقت، ومقاسات الصور عُلمت، وما إلى ذلك. -
الحدث
beforeunload
-- يهمّ المُستخدم بالمغادرة. يمكننا عندها أن نتحقّق من أنّه قد حفظ التغييرات وأن نسأله إن كان حقّا يودّ المغادرة. -
الحدث
unload
-- المُستخدم على وشك المغادرة، لكنّنا لازلنا نستطيع إجراء بعض العمليّات كإرسال الإحصائيّات مثلا.
لنطّلع على تفاصيل هذه الأحداث.
الحدث DOMContentLoaded
يقع الحدث DOMContentLoaded
على الكائن document
، ولابدّ أن نستعمل addEventListener
لالتقاطه:
document.addEventListener("DOMContentLoaded", ready); // "document.onDOMContentLoaded = ..." ليس
على سبيل المثال:
<script> function ready() { alert('DOM is ready'); // 0x0 لم تُحمّل الصورة بعد (إلّا إن كانت قد خُبّئت في ذاكرة التخزين المؤقّت)، لذا فمقاسها هو alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`); } document.addEventListener("DOMContentLoaded", ready); </script> <img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
في هذا المثال، يشتغل معالج DOMContentLoaded
عندما يُحمَّل المستند، ويمكنه عندها رؤية جميع العناصر، بما في ذلك <img>
الذي في الأسفل. لكنّه لا ينتظر الصورة حتى تُحمّل، ولذلك يُظهر alert
المقاسات صفرا.
من الوهلة الأولى، يبدو الحدث DOMContentLoaded
بسيطا للغاية. تجهز شجرة DOM، فيقع الحدث. مع ذلك، ينبغي التنبيه على بعض الدقائق.
DOMContentLoaded والسكربتات
عندما يعالج المتصفّح مستند HTML ويلاقي وسم <script>
، فيجب عليه تنفيذه قبل أن يتابع بناء DOM. هذا من باب الاحتياط، إذ قد تودّ السكربتات تغيير DOM، أو حتى إجراء document.write
عليه، فيجب إذًا على DOMContentLoaded
أن ينتظر. لذلك لابدّ أن يكون حدوث DOMContentLoaded بعد هذه السكربتات:
<script> document.addEventListener("DOMContentLoaded", () => { alert("DOM ready!"); }); </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script> <script> alert("Library loaded, inline script executed"); </script>
في المثال أعلاه، نلاحظ "Library loaded…" أوّلا، ثمّ "DOM ready!" (جميع السكربتات قد نُفّذت).
تنبيه: السكربتات التي لا تعيق DOMContentLoaded
هناك استثناءان لهذه القاعدة:
-
السكربتات ذوات السمة
async
، التي سنتناولها عن قريب، لا تعيقDOMContentLoaded
. -
السكربتات االتي تُولَّد ديناميكيّا بواسطة
document.createElement('script')
ثمّ تُضاف إلى صفحة الويب لا تعيق هذا الحدث أيضا.
DOMContentLoaded والتنسيقات
لا تؤثّر أوراق التنسيق في DOM، لذا فإنّ DOMContentLoaded
لا ينتظرها. لكنّ هناك مزلقا. إذا كان لدينا سكربت موجود بعد التنسيق، فيجب على ذلك السكربت أن ينتظر حتى تُحمّل ورقة التنسيق:
<link type="text/css" rel="stylesheet" href="style.css"> <script> // لا يُنفّذ السكربت حتى تُحمّل ورقة التنسيق alert(getComputedStyle(document.body).marginTop); </script>
سبب ذلك أنّ ذلك السكربت قد يودّ الحصول على إحداثيّات أو غيرها من خاصيّات العناصر المتعلّقة بالتنسيق، كما في المثال أعلاه. فمن الطبيعيّ إذا أن ينتظر التنسيقات حتى تُحمّل.
بانتظار الحدث DOMContentLoaded
للسكربتات، عليه انتظار التنسيقات التي قبلها كذلك.
تعبئة الاستمارت تلقائيا في المتصفح
تُعبّئ المتصفّحاتُ Firefox وChrome وOpera الاستمارات تلقائيّا عند حدوث DOMContentLoaded
.
على سبيل المثال، إذا كان في الصفحة استمارة لاسم المستخدم وكلمة المرور، وتذكّر المتصفّح القيم الخاصّة بها، فعند حدوث DOMContentLoaded
، قد يحاول تعبئتها تلقائيّا (إذا أقرّ المستخدم ذلك).
فإذا أُجِّل DOMContentLoaded
بسبب سكربتات طويلة التحميل، فإنّ التعبئة التلقائيّة تنتظر أيضا. قد تكون لاحظت ذلك في بعض المواقع (إذا كنت تستخدم تعبئة المتصفّح التلقائيّة) -- لا تُعبّأ حقول اسم المستخدم وكلمة المرور فورا، بل إنّ هناك تأخيرا إلى حين تحميل الصفحة بالكامل. ذلك في الواقع هو الوقت الذي يأخذه DOMContentLoaded
للحدوث.
الحدث window.onload
يقع الحدث load
على كائن النافذة window
عندما تُحمّل الصفحة كاملة، بما في ذلك التنسيقات والصور وغيرها من الموارد. هذا الحدث متاح من خلال الخاصّيّة onload
.
يظهر المثال أدناه مقاسات الصور الصحيحة، لأنّ window.onload
ينتظر جميع الصور:
<script> window.onload = function() { // window.addEventListener('load', (event) => { :هذا مماثل لكتابة alert('Page loaded'); // تُحمّل الصورة في هذه اﻷثناء alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`); }; </script> <img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
الحدث window.onunload
عندما يغادر الزائر الصفحة، يقع الحدث unload
على window
. يمكننا عندها القيام بأمور لا تتطلّب تأخيرا، كإغلاق النوافذ المنبثقة popup مثلا. الاستثناء الجدير بالذكر هو إرسال التحليلات analytics.
لنفترض أنّنا نودّ تجميع معطيات حول كيفيّة استخدام الصفحة: نقرات الفأرة والتمريرات واﻷجزاء المُشاهدَة من الصفحة وما إلى ذلك. من الطبيعيّ أن يقع الحدث unload
عندما يغادرنا المستخدم، ونريد عندها أن نحفظ المعطيات في خادمنا.
هناك التابع navigator.sendBeacon(url, data)
المخصّص لهذا الغرض، وهو مبيّن في المواصفة. يعمل هذا التابع على إرسال المعطيات في الخلفيّة، فلا يتأخّر بذلك الانتقال إلى صفحة أخرى: يغادر المتصفّح الصفحة، لكنّه يبقى مؤدّيا لـ sendBeacon
. هذه كيفيّة استخدامه:
let analyticsData = { /* كائن فيه المعطيات المُجمّعة */ }; window.addEventListener("unload", function() { navigator.sendBeacon("/analytics", JSON.stringify(analyticsData)); });
- يُرسل الطلب على شكل POST.
- لا يمكننا إرسال السلاسل النصّيّة فقط، بل الاستمارات وصيغ أخرى كذلك، كما سنوضع ذلك في مقال لاحق، لكن يُرسل غالبا كائن محوّل إلى سلسلة نصّيّة stringified object.
- يُحدّ حجم المعطيات بـ 64kb.
عند انتهاء الطلب sendBeacon
، يكون المتصفّح قد غادر المستند، وبالتالي لا سبيل للحصول على إجابة من الخادم (التي تكون في العادة فارغة بالنسبة للتحليلات).
هناك أيضا الراية keepalive
، للقيام بطلبات مماثلة تتعلّق بما "بعد مغادرة الصفحة"، في التابع Fetch الخاصّ بالطلبات العامّة على الشبكة. يمكنك الاطلاع على المزيد من المعلومات حول ذلك في مقال الواجهة البرمجية fetch في JavaScript.
إذا أردنا إلغاء الانتقال إلى صفحة أخرى، لا يمكننا فعل ذلك هنا. لكن يمكننا استعمال حدث آخر -- onbeforeunload
.
الحدث window.onbeforeunload
إذا شرع الزائر في مغادرة الصفحة أو حاول إغلاق النافذة، يطلب معالج beforeunload
المزيد من التثبّت. وإذا ألغينا الحدث، قد يسأل المتصفّح المستخدم إن كان بالفعل متأكّدا. يمكنك تجربة ذلك بواسطة إجراء هذه الشيفرة ثم إعادة تحميل الصفحة:
window.onbeforeunload = function() { return false; };
لأسباب تاريخيّة، تُعدّ إعادة سلسلة نصيّة غير فارغة أيضا بمثابة إلغاء الحدث. اعتادت المتصفّحات في السابق على إظهارها على شكل رسالة، لكن كما تنصّ [المواصفة الحديثة](https://html.spec.whatwg.org/#unloading-documents )، يجب ألا يفعلوا ذلك.
هذا مثال:
window.onbeforeunload = function() { return "There are unsaved changes. Leave now?"; };
غُيّر هذا السلوك، لأنّ بعض مديري المواقع أساؤوا استخدام معالج الحدث هذا بإظهار رسائل مضلّلة ومزعجة. فحاليّا، قد لا تزال المتصفّحات القديمة تظهرها كرسالة، لكن ما عدا ذلك، لا سبيل لتخصيص الرسالة التي تظهر للمستخدم.
الخاصية readyState
ماذا يحصل لو وضعنا معالجا للحدث DOMContentLoaded
بعد تحميل المستند؟ من الطبيعيّ أن لا يشتغل أبدا.
هناك حالات لا نكون فيها متأكّدين ما إذا كان المستند جاهزا أو لا. نريد لدالّتنا أن تُنفّذ عندما يكون DOM قد حُمّل، كان ذلك الآن أو لاحقا.
تخبرنا الخاصّيّة document.readyState
بحالة التحميل الحاليّة. وتأخذ ثلاث قيم ممكنة:
-
"loading"
-- المستند قيد التحميل. -
"interactive"
-- تمّت قراءة المستند بأكمله. -
"complete"
-- تمّت قراءة المستند بأكمله وحُمّلت جميع الموارد (مثل الصور) أيضا.
فيمكننا إذًا تفحّص document.readyState
وتهيئة معالج أو تنفيذ الشيفرة مباشرة إن كان المستند جاهزا.
هكذا:
function work() { /*...*/ } if (document.readyState == 'loading') { // لازال التحميل، انتظر الحدث document.addEventListener('DOMContentLoaded', work); } else { // جاهز DOM work(); }
الحدث readystatechange
هو آليّة بديلة لتتبّع حالة تحميل المستند، وقد ظهر قديما. أمّا حاليّا، فيندر استخدامه. لنرى مجرى الأحداث كاملا من باب التمام:
هذا مستند فيه <iframe>
و<img>
ومعالجات تقوم بتسجيل اﻷحداث:
<script> log('initial readyState:' + document.readyState); document.addEventListener('readystatechange', () => log('readyState:' + document.readyState)); document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded')); window.onload = () => log('window onload'); </script> <iframe src="iframe.html" onload="log('iframe onload')"></iframe> <img src="http://en.js.cx/clipart/train.gif" id="img"> <script> img.onload = () => log('img onload'); </script>
يمكن مشاهدة المثال يعمل في البيئة التجريبيّة
النتيجة الاعتياديّة:
- [1] initial readyState:loading
- [2] readyState:interactive
- [2] DOMContentLoaded
- [3] iframe onload
- [4] img onload
- [4] readyState:complete
- [4] window onload
تشير اﻷرقام داخل اﻷقواس المربّعة إلى وقت الحدوث بالتقريب. تقع الأحداث المُعلّمة بنفس الأرقام في نفس الوقت تقريبا (+- بضع ms).
-
document.readyState
يصيرinteractive
مباشرة قبلDOMContentLoaded
. يعني هذان نفس الشيء في الحقيقة. -
document.readyState
يصيرcomplete
عندما تُحمّل جميع الموارد (iframe
وimg
). يمكننا أن نرى هنا أنّه يحدث في نفس الوقت تقريبا معimg.onload
(img
هو آخر الموارد) وwindow.onload
. يعني الانتقال إلى الحالةcomplete
نفسwindow.onload
. الفرق الذي هناك هو أنّwindow.onload
يشتغل دائما بعد جميع معالجاتload
اﻷخرى.
الملخص
أحداث تحميل الصفحة:
-
يقع الحدث
DOMContentLoaded
علىdocument
عندما يكون DOM جاهزا. يمكننا تطبيق جافاسكربت على العناصر في هذه المرحلة.-
تعيق السكربتات مثل
<script>...</script>
أو<script src="..."></script>
الحدث DOMContentLoaded، إذ ينتظر المتصفّح تنفيذها. - يمكن أن تواصل الصور والموارد اﻷخرى التحميل.
-
تعيق السكربتات مثل
-
يقع الحدث
load
علىwindow
عندما تكون الصفحة وجميع الموارد قد حُمّلت. يندر استخدامه، إذ لا حاجة للانتظار كلّ هذا الوقت في العادة. -
يقع الحدث
beforeunload
علىwindow
عندما يغادر المستخدم في النهاية، يمكننا فعل أشياء بسيطة فقط في المعالج، لا تستلزم تأخّرات ولا تسأل المستخدم. بسبب هذه القيود، يندر استخدامها. يمكننا إرسال طلب عبر الشبكة باستعمالnavigator.sendBeacon
. -
document.readyState
هي حالة جاهزيّة المستند الحاليّة، يمكن تتبّع تغيّراتها بواسطة الحدثreadystatechange
:-
loading
-- المستند قيد التحميل. -
interactive
-- قد حُلّل المستند. يحدث في نفس الوقت تقريبا معDOMContentLoaded
، لكن قبله. -
complete
-- قد حُمّل المستند والموارد. يحدث في نفس الوقت تقريبا معwindow.onload
، لكن قبله.
-
ترجمة -وبتصرف- للمقال Page: DOMContentLoaded, load, beforeunload, unload من سلسلة Browser: Document, Events, Interfaces لصاحبها Ilya Kantor
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.