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

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


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

تمثّل الأحداث (events) إشاراتٍ إلى أنّ شيئًا ما قد حصل. يمكن أن تنشأ هذه الإشارات من أيّ عقدة في DOM (لكنّها لا تقتصر فقط على DOM). على سبيل المثال، هذه قائمة لأكثر الأحداث فائدةً:

أحداث الفأرة:

  • click -- عند النقر بالفأرة على عنصرٍ ما (أو عند الضغط عليه باستخدام الأجهزة اللمسية)

  • contextmenu -- عند النقر بالزرّ الأيمن للفأرة على عنصرٍ ما.

  • mouseout \ mouseover -- عندما يبلغ / يغادر مؤشّر الفأرة عنصرًا ما.

  • mouseup \ mousedown -- عند ضغط / تحرير زرّ الفأرة.

  • mousemove -- عند تحريك مؤشّر الفأرة.

أحداث لوحة المفاتيح:

  • keyup \ keydown -- عند ضغط / إرسال أحد أزرار لوحة المفاتيح.

أحداث النماذج:

  • submit -- عندما يرسل المستخدم النموذج <form>.

  • focus -- عندما يحدّد المستخدم عنصرًا ما في النموذج، كتحديده عنصر <input> مثلا.

أحداث المستند:

  • DOMContentLoaded -- عند الفراغ من تحميل ملف HTML ومعالجتِه، وبناء كامل شجرة DOM.

الأحداث المتعلقة بـ CSS:

  • transitionend - عند انتهاء تحريكCSS (animation) ‎.

وهناك العديد من الأحداث الأخرى، سنتناول بعضها بمزيدٍ من التفصيل في مقالاتٍ لاحقة.

معالجات الأحداث

من أجل الاستجابة للأحداث، يمكننا تعيين معالجٍ (handler) على شكل دالّة تُنفَّذ عند وقوع الحدث. وتُعدّ بذلك المعالجات وسيلةً لتنفيذ شيفرات جافاسكربت وفقًا لما يقوم به المستخدم.

توجد عدة طرق لتعيين معالجٍ للحدث. سنتناولها بدءًا بأبسطها.

على شكل سمة HTML

يمكن تعيين المعالج في HTML على شكل سمةٍ (attribute) يكون اسمها على النحو on<event>‎ أي on متبوعة باسم الحدث.

على سبيل المثال، لتعيين معالجٍ لحدث click على عنصر input، يمكننا استخدام السمة onclick كالتالي:

<input value="Click me!" onclick="alert('Click!')" type="button">

عند النقر بالفأرة، تُنفّذ الشيفرة التي بداخل onclick.

يرجى التنبّه هنا إلى أنّنا استخدمنا علامات الاقتباس غير المزدوجة داخل onclick، لأنّ السّمة نفسها محاطة بعلامات اقتباس مزدوجة. فلو غفلنا عن أنّ الشيفرة موجودة داخل السّمة واستخدمنا داخلها علامات الاقتباس المزدوجة هكذا onclick="alert("Click!")"‎ فلن تعمل الشيفرة بشكل صحيح.

لا تعدّ سمة HTML مكانا مناسبا لكتابة الكثير من الشيفرة، فيحسُن إذًا أن ننشئ دالّة جافاسكربت ونستدعيها هناك.

في المثال أدناه، يؤدي النقر إلى تنفيذ الدالّة countRabbits()‎

<script>
  function countRabbits() {
    for(let i=1; i<=3; i++) {
      alert("Rabbit number " + i);
    }
  }
</script>

<input type="button" onclick="countRabbits()" value="Count rabbits!">

كما هو معلوم، لا فرق بين الأحرف الكبيرة والصغيرة في تسمية سمات HTML، فتمثل كلّ من ONCLICK و onClick و onCLICK نفس السّمة، لكن في الغالب تُكتب السّمات بأحرف صغيرة هكذا onclick.

على شكل خاصيّة DOM

يمكننا تعيين معالجٍ على شكل خاصيّة DOM يكون اسمها على النحو on<event>‎. على سبيل المثال، elem.onclick:

<input id="elem" type="button" value="Click me">
<script>
  elem.onclick = function() {
    alert('Thank you');
  };
</script>

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

تؤدي هاتان الشيفرتان الوظيفة نفسها:

  1. مجرّد HTML:
   <input type="button" onclick="alert('Click!')" value="Button">
  1. HTML + JS:

    <input type="button" id="button" value="Button">
    <script>
        button.onclick = function() {
            alert('Click!');
        };
    </script>

     

استخدمنا في المثال الأوّل سمة HTML لتهيئة button.onclick، بينما في المثال الثاني استخدمنا سكربتًا لذلك. هذا الفرق الذي هناك.

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

في المثال أدناه، تؤدي إضافة معالجٍ بواسطة جافاسكربت إلى استبدال المعالج الموجود مسبقًا.

<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
  elem.onclick = function() { // يستبدل المعالج الموجود
    alert('After'); // هذا ما سيظهر فقط
  };
</script>

لحذف المعالج، يكفي إعطاء الخاصيّة القيمة null هكذا: elem.onclick = null.

الوصول إلى العنصر بواسطة this

تشير الكلمة this داخل المعالج إلى نفس العنصر الذي أُسند إليه المعالج. ففي الشيفرة أدناه، يُظهر العنصر button محتواه بواسطة this.innerHTML

<button onclick="alert(this.innerHTML)">Click me</button>

أخطاء محتملة

في بداية التعامل مع معالجات الأحداث، يُرجى التنبه لعدد من الأمور الدقيقة.

يمكن تعيين معالج من دالة موجودة مسبقا:

function sayThanks() {
  alert('Thanks!');
}

elem.onclick = sayThanks;

لكن انتبه، يجب إسناد الدالة كـ sayThanks وليس sayThanks()‎.

// صحيح
button.onclick = sayThanks;

// خطأ
button.onclick = sayThanks();

بإضافة الأقواس، تصير sayThanks()‎ استدعاءًا للدالة. وبالتالي، يأخذ السطر الأخير ناتج تنفيذ الدالّة (الذي هو undefined بحكم أنّ الدالة لا تعيد أيّ شيء) ويضعه في onclick. هذا لا يصلح.

… في المقابل، نحتاج في HTML إلى تضمين الأقواس:

<input type="button" id="button" onclick="sayThanks()">

يمكن توضيح سبب ذلك كالتالي: عندما يقرأ المتصفّح السّمة، فإنّه ينشئ معالجًا على شكل دالّة لها نفس محتوى هذه السّمة. فيقوم HTML الذي لدينا بإنشاء الخاصيّة التالية:

button.onclick = function() {
  sayThanks(); // <--  يصير محتوى السّمة هنا
};

لا تستخدم setAttribute لتعيين المعالجات.

هذه الشيفرة لا تصلح:

// إلى توليد أخطاء <body> يؤدي النفر على
// بحكم أنّ السّمات هي سلاسل نصيّة، فتصير الدالة سلسلة نصيّة ايضا
document.body.setAttribute('onclick', function() { alert(1) });

تفرّق خاصيّات DOM بين الأحرف الكبيرة والصغيرة.

يجب تعيين المعالج في elem.onclick بدل elem.ONCLICK ، لأنّ خاصيّات DOM تفرُق معها الأحرف الكبيرة والصغيرة.

addEventListener

تكمن المشكلة الأساسية في طرق تعيين المعالج السالفة الذكر، في عدم إمكانيّة تعيين عدّة معالجات لحدث واحد.

لنفترض أن جزءًا من الشيفرة التي لدينا يهدف إلى إبراز أحد الأزرار عند النقر عليه، بينما يهدف جزء آخر من الشيفرة إلى إظهار رسالة ما عند نفس النقرة.

قد نودّ تعيين عدّة معالجات للحدث لتحقيق ذلك، لكن بإضافة خاصيّة جديدة إلى DOM، تُستبدل الخاصيّة الموجودة مسبقًا.

input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // يستبدل المعالج السابق

أدرك العاملون على معايير الويب هذه المشكلة منذ القدم، واقترحوا طريقة بديلة لإدارة معالجات الأحداث، وذلك بواسطة التوابع الخاصة addEventListener و removeEventListener. تكون صيغة إضافة معالجٍ فيها كالتالي:

element.addEventListener(event, handler, [options]);

حيث أن:

  • event: هو اسم الحدث، كـ "click" مثلًا.
  • handler: هي دالّة المعالج.
  • options: هو كائن إضافي اختياري، وله الخاصيّات التالية: once: إذا كانت قيمتها true، فإن منصت الحدث (event listener) يزول تلقائيا بعد حصول الحدث. capture المرحلة التي يُعالَج فيها الحدث، وسنتطرّق إليها لاحقا في مقال انتشار اﻷحداث. لأسباب تاريخية، يمكن أن تحمل options القيمة true \ false ويكون لذلك نفس معنى {capture: false/true}. passive: إذا كانت قيمتها true، فلن يستدعي المعالجُ التابعَ preventDefault()‎، وسنشرح ذلك في مقال أفعال المتصفّح الافتراضية.

يمكن حذف المعالج بواسطة:

element.removeEventListener(event, handler, [options]);

تنبيه: يتطلّب الحذفُ الدالةَ نفسَها

لحذف المعالج يجب تمرير نفس الدالة التي عُيّنت من قبل. فلو جرّبنا مثلًا:

elem.addEventListener( "click" , () => alert('Thanks!'));
// ....
elem.removeEventListener( "click", () => alert('Thanks!'));

لن يحُذف المعالج، لأن removeEventListener قد تلقّى دالة أخرى -- لها نفس الشيفرة، لكن لا يهم ذلك لأنها كائن دالة آخر.

هذه هي الطريقة الصحيحة لحذف المعالج:

function handler() {
  alert( 'Thanks!' );
}

input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);

يرُجى التنبّه هنا إلى أنّه إذا لم نحفظ الدالة في متغيّر، فلا يمكننا حذفها. إذ لا سبيل إلى "إعادة قراءة" المعالجات المُعيّنة بواسطة addEventListener.


يمكّن الاستدعاء المتكرّر لـ addEventListener من تعيين عدّة معالجات كالتالي:

<input id="elem" type="button" value="Click me"/>

<script>
  function handler1() {
    alert('Thanks!');
  };

  function handler2() {
    alert('Thanks again!');
  }

  elem.onclick = () => alert("Hello");
  elem.addEventListener("click", handler1); // Thanks!
  elem.addEventListener("click", handler2); // Thanks again!
</script>

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


تنبيه: لا يمكن تعيين معالجات لبعض الأحداث إلا بواسطة addEventListener

توجد أحداثٌ لا يمكن تعيين معالجات لها عن طريق خاصيّة DOM ، بل تشترط استخدام addEventListener.

على سبيل المثال، الحدث DOMContentLoaded، الذي يحصل حين الانتهاء من تحميل المستند وبناء DOM.

// لن يتم تنفيذ هذا أبدا
document.onDOMContentLoaded = function() {
  alert("DOM built");
};
// هذه الطريقة أصحّ
document.addEventListener("DOMContentLoaded", function() {
  alert("DOM built");
});

بذلك يكونaddEventListener أشمل، رغم أنّ هذه الأحداث تُعدّ استثناءًا وليست القاعدة.

كائن الحدث

لمعالجة الحدث كما ينبغي، قد يلزمنا معرفة المزيد عمّا حصل بالضبط. فليس مجرّد "النقر" أو "الضغط"، بل أيضا ما هي إحداثيات المؤشر؟ أو ما هو الزر التي ضُغط؟ إلى غير ذلك.

عند وقوع حدثٍ ما، يُنشئ المتصفّحُ كائن حدث ويضع فيه التفاصيل، ثم يمرّره على شكل وسيط للمعالج.

هذا مثال لكيفية الحصول على إحداثيات المؤشر من كائن الحدث:

<input type="button" value="Click me" id="elem">

<script>
  elem.onclick = function(event) {
    // يظهر نوع الحدث والعنصر وإحداثيات النقر
    alert(event.type + " at " + event.currentTarget);
    alert("Coordinates: " + event.clientX + ":" + event.clientY);
  };
</script>

هذه بعض خاصيّات كائن الحدث event:

  • event.type: نوع الحدث، و هو في هذا المثال "click"

  • event.currentTarget: العنصر الذي عالج الحدث. وهو نفس this، إلا إذا كان المعالج دالة سهمية أو أن this الخاصّ به مرتبط (bound) بشيء آخر، فعندها يمكن الحصول على العنصر بواسطة event.currentTarget.

  • event.clientX / event.clientY: هي إحداثيات المؤشّر بالنسبة للنافذة، عند الأحداث المتعلقة بالمؤشّر.

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


ملاحظة: كائن الحدث متوفر أيضا للمعالجات في HTML

إذا عينّا معالجًا في HTML، فإنّه من الممكن أيضا استخدام الكائن event، كالتالي:

<input type="button" onclick="alert(event.type)" value="Event type">

يمكننا ذلك لأن المتصفّح حينما يقرأ السمة، فإنّه ينشئ معالجًا بهذا الشكل: function(event) { alert(event.type) }‎، فيكون اسم وسيطه الأول "event"، ومحتواه مأخوذا من السمة.


الكائنات المعالجة: handleEvent

يمكن بواسطة addEventListener تعيين معالجٍ على شكل كائن أيضا، وعند وقوع الحدث، يُستدعى التابع handleEvent. على سبيل المثال:

<button id="elem">Click me</button>

<script>
  let obj = {
    handleEvent(event) {
      alert(event.type + " at " + event.currentTarget);
    }
  };

  elem.addEventListener('click', obj);
</script>

كما نرى، عندما يستقبل addEventListener كائنًا، فإنه يستدعي obj.handleEvent(event)‎ في حال وقوع الحدث.

يمكننا أيضا استخدام صنفٍ لذلك:

<button id="elem">Click me</button>

<script>
  class Menu {
    handleEvent(event) {
      switch(event.type) {
        case 'mousedown':
          elem.innerHTML = "Mouse button pressed";
          break;
        case 'mouseup':
          elem.innerHTML += "...and released.";
          break;
      }
    }
  }

  let menu = new Menu();
  elem.addEventListener('mousedown', menu);
  elem.addEventListener('mouseup', menu);
</script>

يقوم نفس الكائن هنا بمعالجة كلا الحدثين. لكن ينبغي التنبه إلى أنه يجب تحديد الأحداث المراد الإنصات إليها باستخدام addEventListener صراحةً. يتلقى الكائن menu هنا الحدثين mousedown و mouseup دون غيرهما من أنواع الأحداث الأخرى.

لا يلزم التابع handleEvent أن يقوم بكامل العمل، بل يمكنه استدعاء توابع أخرى مختصة بأحداث معيّنة، كما يلي:

<button id="elem">Click me</button>

<script>
  class Menu {
    handleEvent(event) {
      // mousedown -> onMousedown
      let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
      this[method](event);
    }

    onMousedown() {
      elem.innerHTML = "Mouse button pressed";
    }

    onMouseup() {
      elem.innerHTML += "...and released.";
    }
  }

  let menu = new Menu();
  elem.addEventListener('mousedown', menu);
  elem.addEventListener('mouseup', menu);
</script>

هكذا يتم فصل المعالجات على حدة، مما قد يجعل دعمها أسهل.

الملخص

توجد ثلاث طرق لتعيين معالجات للأحداث:

  1. سمة HTML: ‏ onclick="..."‎.
  2. خاصيّة DOM:‏ elem.onclick = function.
  3. توابع: elem.addEventListener(event, handler[, phase])‎ للإضافة، و removeEventListener للحذف.

يندر استخدام سمات HTML، لأن جافاسكربت تبدو غريبة وسط وسم HTML. بالإضافة إلى أنه لا يمكن كتابة الكثير من الشيفرة هناك.

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

تُعدّ الطريقة الأخيرة أكثرها مرونة، لكنها أيضا أطولها كتابة. هناك بعض الأحداث التي لا تعمل إلا بواسطتها، على سبيل المثال transitionend و DOMContentLoaded (ستُدرس لاحقًا). بالإضافة إلى ذلك، تدعم addEventListener تعيين معالجات للأحداث على شكل كائنات، ويُستدعى حينها التابع handleEvent عند وقوع الحدث.

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

سنتعلم المزيد عن الأحداث عموما، وعن مختلف أنواع الأحداث في المقالات القادمة.

التمارين

الإخفاء عند النقر

الأهمية: 5

أضف جافاسكربت إلى الزر button لجعل <div id="text"‎> يختفي عند النقر عليه، كما هنا

افتح البيئة التجريبيّة لإنجاز التمرين

الحل

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

الاختفاء عند النقر

الأهمية: 5

أنشئ زرّا يخفي نفسه عند النقر عليه.

الحل

يمكن استخدام this للإشارة إلى "العنصر نفسه" هنا:

<input type="button" onclick="this.hidden=true" value="Click to hide">

أيّ المعالجات ستُنفّذ؟

اﻷهمية: 5

يحتوي المتغير button على زرّ، وليس عليه معالجات.

أيّ من المعالجات ستُنفّذ بعد تشغيل الشيفرة أدناه؟ ما هي التنبيهات التي تظهر؟

button.addEventListener("click", () => alert("1"));

button.removeEventListener("click", () => alert("1"));

button.onclick = () => alert(2);

الحل

الجواب هو 1 و 2.

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

لحذف كائن دالة، نحتاج أن نحفظ مرجعا لها، كما يلي:

function handler() {
 alert(1);
}

button.addEventListener("click", handler);
button.removeEventListener("click", handler);

أما المعالج button.onclick فهو يعمل بشكل مستقل وبالإضافة إلى addEventListener.

حرّك الكرة في الملعب

حرّك الكرة في الملعب بواسطة النقر، كما هنا

المطلوب:

  • يجب أن ينتقل مركز الكرة إلى موضع المؤشر عند النقر (حبذا دون الخروج عن حافة الملعب).
  • تحريكات CSS مُرحّب بها.
  • يجب ألا تخرج الكرة عن حدود الملعب.
  • يجب ألا يؤدي تمرير الصفحة إلى اختلاط الأمور .

ملاحظات:

  • يجب أن تشتغل الشيفرة مع مختلف أحجام الكرة والملعب، وألا تكون مرتبطة بقيم معينة.
  • استخدم الخاصيّات event.clientX/event.clientY للحصول على إحداثيات النقر.

افتح البيئة التجريبيّة لإنجاز التمرين

الحل

أولا، علينا أن نختار طريقة لتغيير موضع الكرة.

لا يمكننا استخدام position:fixed لذلك، لأن تمرير الصفحة قد يخرج الكرة عن الملعب.

لذا يلزمنا استخدام position:absolute، ولكي يكون التموضع جيد الإحكام، علينا أن نعطي للملعب نفسه وضعية، لتكون بذلك الكرة مُموضَعة بالنسبة إلى الملعب:

#field {
  width: 200px;
  height: 150px;
  position: relative;
}

#ball {
  position: absolute;
  left: 0; /* (بالنسبة إلى أقرب سلف مموضَع (الملعب */
  top: 0;
  transition: 1s all; /* الكرة تطير leftو top المتعلقة بـ CSS تجعل تحريكات */
}

بعدها، علينا أن نعطي القيم المناسبة لـ ball.style.left/top ، إذ هي الآن تمثل إحداثيات الكرة بالنسبة إلى الملعب.

هذه هي الصورة:

move-ball-coords.png

تمثل event.clientX/clientY إحداثيات موضع النقر بالنسبة إلى النافذة.

للحصول على الإحداثية left لموضع النقر بالنسبة إلى الملعب، يمكننا أن نطرح كلّا من الحافة اليسرى الملعب وسمك الحد:

let left = event.clientX - fieldCoords.left - field.clientLeft;

عادةً، يشير ball.style.left إلى "الحافة اليسرى للعنصر" (الكرة)، فإذا أعطيناه قيمة المتغير left، فإن حافة الكرة هي التي ستكون تحت المؤشر وليس مركزها.

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

فتكون القيمة النهائية لـ left هي:

let left = event.clientX - fieldCoords.left - field.clientLeft - ball.offsetWidth/2;

تُحسب الإحداثية العمودية بنفس الطريقة.

يُرجى التنبه إلى أنه يجب أن يكون عرض الكرة وطولها معلومين عند قراءة ball.offsetWidth. يجب أن يُحدد ذلك في HTML أو CSS.

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

أنشئ قائمة منحدرة

اﻷهمية: 5

أنشئ قائمة تُفتح وتُغلق عند النقر كما هنا

ملاحظة: ينبغي التعديل على الملف المصدري لـ HTML/CSS.

افتح البيئة التجريبيّة لإنجاز التمرين

الحل

HTML/CSS

لننشئ أولا HTML/CSS.

تُعدّ القائمة مُكوّنا رسوميا مستقلا بذاته في الصفحة، لذا فيفضّل وضعها في عنصر DOM واحد.

يمكن تنسيق عناصر القائمة على شكل ul/li.

هذا مثال عن البنية:

<div class="menu">
  <span class="title">Sweeties (click me)!</span>
  <ul>
    <li>Cake</li>
    <li>Donut</li>
    <li>Honey</li>
  </ul>
</div>

استخدمنا <span> للعنوان، لأن <div> له ضمنيّا الخاصيّة display:block ، مما سيجعله يحتل 100% من المساحة الأفقية، هكذا:

<div style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</div>

div.png

فإذا وضعنا عليه onclick ، فإنه سيستجيب للنقرات التي على يمين النص أيضا.

وبما أن <span> له ضمنيّا الخاصيّة display: inline، فإنه سيحتل من المساحة فقط ما يكفي لاحتواء النص:

<span style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</span>

span.png

إقلاب القائمة

يؤدي إقلاب (toggling) القائمة إلى تغيير السهم وإظهار أو إخفاء عناصرها.

يمكن القيام بكل هذه التغيرات من خلال CSS. في جافاسكربت علينا فقط تمييز الوضع الحالي للقائمة من خلال إضافة أو إزالة الصنف open..

بدون open. تكون القائمة منقبضة:

.menu ul {
  margin: 0;
  list-style: none;
  padding-left: 20px;
  display: none;
}

.menu .title::before {
  content: '▶ ';
  font-size: 80%;
  color: green;
}

… ومع وجوده يتغير السهم وتظهر عناصر القائمة:

.menu.open .title::before {
  content: '▼ ';
}

.menu.open ul {
  display: block;
}

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

أضف زرّا للإغلاق

اﻷهمية: 5

هناك مجموعة من الرسائل.

باستخدام جافاسكربت، أضف زرا في أقصى اليمين العلوي للرسائل لإغلاقها.

يجب أن تبدو النتيجة كما هنا.

افتح البيئة التجريبيّة لإنجاز التمرين

الحل

لإضافة الزر، يمكن استخدام كلّ من position:absolute (وجعل وضعية اللوحة نسبية position:relative) أو float:right. تتميز طريقة float:right بأنها تضمن عدم تقاطع الزر مع النص، لكن position:absolute تمنح المزيد من الحرية. فالخيار لك.

بذلك تكون الشيفرة لكل من الألواح كالتالي:

pane.insertAdjacentHTML("afterbegin", '<button class="remove-button">[x]</button>');

و يصير <button> بذلك pane.firstChild، مما يمكّننا من تعيين معالج له هكذا:

pane.firstChild.onclick = () => pane.remove();

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

الدوّار

اﻷهمية: 4

أنجز دوّارا -- شريط من الصور يمكن تمريره بواسطة أسهم، كما هنا

يمكننا لاحقا إضافة المزيد من المزايا كالتمرير اللامتناهي، والتحميل الديناميكي وغير ذلك.

ملاحظة:

في هذا التمرين، تُمثل بنية HTML/CSS في الحقيقة 90% من الحل.

افتح البيئة التجريبيّة لإنجاز التمرين

الحل

يمكن تمثيل شريط الصور بقائمة ul/li من الصور <img>.

من المفترض أن هذا الشريط واسع، لكننا نحدّه بـ <div> ثابت الحجم لقطعه، فيبدو بذلك جزء من الشريط فقط:

carousel1.png

لعرض القائمة أفقيا، يجب تطبيق الخاصيّات المناسبة لـ <li> في CSS، مثل display: inline-block.

بالنسبة لـ <img> يجب أيضا تعديل display، لأنها تكون افتراضيا inline. توجد هناك مساحة تحت العناصر inline مخصصة لأذيال الحروف، فيمكن استخدام display:block لإزالتها.

للقيام بالتمرير يمكن إزاحة <ul>. هناك عدة طرق لفعل ذلك، مثل تغيير margin-left أو (لأداء أفضل) استخدام translateX()‎:

carousel2.png

بما أن العرض الخارجي لـ <div> محدود، فإن الصور "الزائدة" ستُخفى.

يُعد الدوّار بأكمله مكونا "رسوميا" مستقلا بذاته، فيُفضل وضعه في <div class="carousel"> واحد وتنسيق جميع الأمور بداخله.

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

ترجمة -وبتصرف- للمقال Introduction to browser events من سلسلة 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.


×
×
  • أضف...