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

الأحداث المتعلقة بدورة حياة صفحة HTML وكيفية التحكم بها عبر جافاسكربت


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

لدورة حياة صفحة 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

هناك استثناءان لهذه القاعدة:

  1. السكربتات ذوات السمة async، التي سنتناولها عن قريب، لا تعيق DOMContentLoaded.
  2. السكربتات االتي تُولَّد ديناميكيّا بواسطة 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. [1] initial readyState:loading
  2. [2] readyState:interactive
  3. [2] DOMContentLoaded
  4. [3] iframe onload
  5. [4] img onload
  6. [4] readyState:complete
  7. [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


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...