ما قصد داریم این پروژهٔ متن‌باز را در دسترس همهٔ مردم در سرتاسر دنیا قرار دهیم.

به ترجمهٔ محتوای این آموزش به زبان خودتان کمک کنید/a>.

درامدی بر رویدادهای مرورگر

یک رویداد نشانه‌ای از چیزی است که اتفاق افتاده است. همه‌ی عناصر درخت DOM این نشانه‌ها را تولید می‌کنند (اما رویدادها محدود به درخت DOM نیستند).

در اینجا صرفا برای آشنایی اولیه، لیستی از پرکاربردترین رویدادهای DOM اورده شده:

رویدادهای موس:

  • click – زمانی که موس روی یک عنصر کلیک می‌کند (دستگاه‌های لمسی این رویدادها هنگام ضربه زدن ایجاد می‌کنند).
  • contextmenu – زمانی که موس روی یک عنصر راست‌کلیک می‌کند.
  • mouseover / mouseout – زمانی که اشاره‌گر موس روی/بیرون یک عنصر می‌رود…
  • mousedown / mouseup – زمانی که کلید موس روی عنصر فشرده/رها می‌شود.
  • mousemove – زمانی که اشاره‌گر موس حرکت می‌کند.

رویدادهای صفحه‌کلید:

  • keydown و keyup – زمانی که یک کلید از صفحه‌کلید فشرده/رها می‌شود.

رویدادهای عناصر form:

  • submit – زمانی که بازدیدکننده یک <form> را ثبت می‌کند.
  • focus – زمانی بازدیدکننده روی یک عنصر تمرکز کند, برای مثال یک <input>.

رویدادهای :

  • DOMContentLoaded – زمانی که سند HTML لود و پردازش شود، همچنین درخت DOM نیز کاملا تشکیل شده است.

رویدادهای CSS:

  • transitionend – زمانی که یک انیمیشن CSS تمام می‌شود.

رویدادهای متعدد دیگری نیز وجود دارد. درباره جزئیات مخصوص به هرکدام از رویدادها در بخش‌های بعدی صحبت می‌کنیم.

کنترل‌کننده‌های رویدادها

برای واکنش به این رویداد‌ها باید یک کنترل‌کننده به آن اختصاص دهیم – یک تابع که در زمان اتفاق افتادن یک رویداد اجرا می‌شود.

کنترل‌کننده‌ها یک روش برای اجرای کدهای JavaScript در جواب به رفتارهای کاربر هستند.

چندین راه برای اختصاص کنترل‌کننده‌ها وجود دارد. برای آشنا شدن با آنها، از ساده‌ترین روش شروع می‌کنیم.

صفت HTML

یک کنترل‌کننده می‌تواند درون HTML با یک صفتی به نام on<event> تعریف شود.

برای مثال اگر بخواهیم یک کنترل‌کننده click برای یک input اختصاص دهیم، مشابه مثال زیر از onclick استفاده می‌کنیم:

<input value="روی من کلیک کن" onclick="alert('کلیک!')" type="button">

در زمان کلیک موس، کدی که داخل onclick است اجرا خواهد شد.

توجه کنید که داخل onclick ما از سینگل کوتیشن استفاده کردیم، چون که خود صفت درون یک دابل‌کوتیشن تعریف شده. اگر فراموش کنیم که کد داخل یک صفت است و از دابل‌کوتیشن استفاده کنیم، مثل این: onclick="alert("کلیک!")"، کد ما کار نخواهد کرد.

صفت HTML جای آنچنان مناسبی برای نوشتن کد‌های طولانی نیست. پس بهتر است یک تابع جاوااسکریپت ایجاد کنیم و درون این صفت آنرا صدا بزنیم.

اینجا یک کلیک، تابع countRabbits() را فراخوانی می‌کند:

<script>
  function countRabbits() {
    for(let i=1; i<=3; i++) {
      alert("تعداد خرگوش " + i);
    }
  }
</script>

<input type="button" onclick="countRabbits()" value="خرگوش‌ها را بشمار!">

همانطور که می‌دانیم، صفت‌های HTML به بزرگی و کوچکی حروف (case-sensitive) وابسته نیستند، پس ‍ONCLICK مانند onClick و onCLICK کار می‌کند … اما معمولا صفت‌ها با حروف کوچک نوشته می‌شوند، مانند: onclick.

خاصیت DOM

ما با استفاده از یک خاصیت DOM به نام on<event> می‌توانیم یک کنترل‌کننده تعریف کنیم.

برای مثال، elem.onclick:

<input id="elem" type="button" value="روی من کلیک کن">
<script>
  elem.onclick = function() {
    alert('ممنونم');
  };
</script>

اگر که کنترل‌کننده توسط یک صفت HTML تعریف‌شده باشد، مرورگر آنرا می‌خواند و یک تابع جدید از مقدار آن صفت ایجاد می‌کند و آنرا به خاصیت متناظر DOM اختصاص می‌دهد.

پس این روش درواقع شبیه روش قبلی است.

این دو قطعه کد همانند هم عمل می‌کنند:

  1. Only HTML:

    <input type="button" onclick="alert('کلیک!')" value="دکمه">
  2. HTML + JS:

    <input type="button" id="button" value="دکمه">
    <script>
      button.onclick = function() {
        alert('کلیک!');
      };
    </script>

در مثال اولی، ما از صفت HTML برای مقدار دهی به button.onclick استفاده کردیم، درصورتی که در مثال دوم، این کار با کد انجام شده. تنها تفاوتشان همین است.

از آنجایی که فقط یک خاصیت onclick روی عنصر وجود دارد، نمی‌توانیم بیشتر از یک کنترل‌کننده‌ برای این رویداد تعریف کنیم.

در مثال زیر، وقتی که یک کنترل‌کننده توسط جاواسکریپت به عنصر اختصاص می‌دهیم، می‌بینیم که جایگزین کنترل‌کننده قبلی می‌شود.

<input type="button" id="elem" onclick="alert('قبل')" value="روی من کلیک کن">
<script>
  elem.onclick = function() { // کنترل‌کننده فعلی را رونویسی می‌کند
    alert('بعد'); // فقط این پیام نمایش داده می‌شود
  };
</script>

برای برداشتن یا حذف یک کنترل کننده، می‌توانیم از elem.onclick = null استفاده کنیم.

دسترسی به عنصر: this

مقدار this داخل یک کنترل‌کننده خود عنصر است. عنصری که کنترل‌کننده روی آن تعریف شده.

در کد زیر button محتویات خود را با this.innerHTML نمایش می‌دهد:

<button onclick="alert(this.innerHTML)">روی من کلیک کن</button>

اشتباهات احتمالی

اگر که به تازگی می‌خواهید با رویدادها کار کنید، به این نکات مهم توجه کنید.

ما می‌توانیم تابعی که از قبل تعریف شده و وجود دارد را به عنوان کنترل‌کننده استفاده کنیم.

function sayThanks() {
  alert('ممنونم!');
}

elem.onclick = sayThanks;

اما مراقب باشید: تابع باید به صورت sayThanks به خاصیت DOM اختصاص یابد، نه به صورت sayThanks().

// درست
button.onclick = sayThanks;

// اشتباه
button.onclick = sayThanks();

اگر که ما پرانتزها را اضافه کنیم تابع sayThanks() صدا زده می‌شود. پس مورد دوم درواقع خروجی حاصل از اجرای تابع را، که undefined است (چون تابع چیزی را باز نمی‌گرداند)، به عنوان کنترل‌کننده به onclick اختصاص می‌دهد، که قاعدتا کار نمی‌کند.

…از سوی دیگر، ما در کد HTML به پرانتز ها نیاز داریم:

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

توضیح و توجیه این تفاوت آسان است. زمانی که مرورگر مقدار صفت را می‌خواند، یک کنترل‌کننده با بدنه‌‌ای شامل محتویات آن صفت می‌سازد.

پس چیزی شبیه این خاصیت ایجاد می‌شود:

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 باشد, کنترل‌کننده بعد از اولین اجرا از روی عنصر حذف می‌شود.
  • capture: مرحله‌ای که کنترل‌کننده در آن عمل می‌کند, در این مورد در قسمت بالارفتن و گرفتن صبحت می‌شود. به دلایلی, options می‌تواند false/true باشد, که مشابه همان {capture: false/true} خواهد بود.
  • passive: اگر true باشد, کنترل‌کننده تابع preventDefault() را صدا نخواهد زد، درباره این موضوع بعدا در قسمت اکشن‌های پیشفرض مرورگر صحبت خواهیم کرد.

برای حذف یک کنترل کننده از removeEventListener‌ استفاده می‌کنیم:

element.removeEventListener(event, handler, [options]);
حذف کردن دقیقا به همان تابع نیاز خواهد داشت

برای حذف یک کنترل‌کننده ما باید دقیقا همان تابعی که به عنوان کنترل‌کننده اختصاص داده بودیم را به تابع بدهیم.

کد زیر کار نمی‌کند:

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

کنترل‌کننده حذف نمی‌شود، چونکه removeEventListener تابع دیگری دریافت کرده. درست است که کدهای آنها مشابه است، اما مهم نیست، چرا که یک شئ‌تابع متفاوت است.

این روش درست است:

function handler() {
  alert( 'ممنون!' );
}

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

توجه کنید، اگر که ما تابع کنترل‌کننده‌را درون یک متغیر ذخیره نکنیم، نمی‌توانیم آنرا حذف کنیم. راهی برای “بازخوانی” کنترل‌کننده‌هایی که با addEventListener اختصاص داده‌ می‌شوند وجود ندارد.

با چند بار صدا زدن addEventListener می‌توانیم چندین کنترل‌کننده اضافه کنیم مانند این:

<input id="elem" type="button" value="روی من کلیک کن"/>

<script>
  function handler1() {
    alert('ممنونم!');
  };

  function handler2() {
    alert('باز هم ممنونم!');
  }

  elem.onclick = () => alert("Hello");
  elem.addEventListener("click", handler1); // ممنونم!
  elem.addEventListener("click", handler2); // باز هم ممنونم!
</script>

همانطور که در مثال‌های بالا میبینیم، می‌توانیم کنترل‌کننده‌ها را با هر دو روش استفاده از خاصیت DOM و addEventListener اختصاص دهیم. اما معمولا از یکی از این دو روش استفاده می‌کنیم.

برای بعضی از رویدادها، کنترل‌کننده‌ها فقط با addEventListener کار می‌کنند

رویدادهایی وجود دارند که نمی‌توان با استفاده از خاصیت DOM برای آنها کنترل‌کننده اختصاص داد. فقط با addEventListener.

برای مثال، رویداد DOMContentLoaded که زمانی اتفاق می‌افتد که سند بارگزاری شده و درخت DOM ساخته شده.

// هیچ‌وقت اجرا نمی‌شود
document.onDOMContentLoaded = function() {
  alert("درخت DOM ساخته شد");
};
// با این روش کار می‌کند
document.addEventListener("DOMContentLoaded", function() {
  alert("درخت DOM ساخته شد");
});

پس رویداد addEventListener کلی‌تر است. هرچند, این چنین رویداد‌هایی بیشتر استثنا هستند تا یک قانون.

شئ رویداد

برای اینکه بتوانیم بهتر یک رویداد را کنترل کنیم باید در مورد اینکه چه اتفاقی اتفاده اطلاعات بیشتری داشته باشیم. اینکه فقط “click” یا “keydown” اتفاق افتاده کافی نیست، بلکه مختصات اشاره‌گر موس، اینکه کدام کلید فشرده شده، و … اهمیت دارد.

زمانی که یک رویداد اتفاق می‌افتد، مرورگر یک شئ رویداد (event object) ایجاد می‌کند، جزئیاتی درباره رویداد را درون آن قرار می‌دهد و به عنوان ورودی به کنترل کننده ارسال می‌کند.

این یک نمونه از گرفتن مختصات اشاره‌گر از شئ رویداد است:

<input type="button" value="روی من کلیک کن" id="elem">

<script>
  elem.onclick = function(event) {
    // نمایش نوع رویداد, عنصر و و مختصات کلیک
    alert(event.type + " در " + event.currentTarget);
    alert("مختصات: " + event.clientX + ":" + event.clientY);
  };
</script>

بعضی از خصوصیات شئ event:

event.type
نوع رویداد، در اینجا "click" است.
event.currentTarget
عنصری که رویداد را کنترل می‌کند. دقیقا همان this است، مگر اینکه کنترل کننده یک تابع پیکانی (arrow function) باشد، یا اینکه this به چیز دیگری مقید شده باشد، در این صورت می‌توانیم عنصر را از event.currentTarget بگیریم.
event.clientX / event.clientY
مختصات اشاره‌گر موس نسبت به پنجره برای رویدادهای مربوط به اشاره‌گر موس.

خصوصیات بیشتری نیز وجود دارد. خیلی از آنها به نوع رویداد بستگی دارند: رویدادهای صفحه‌کلید یک مجموعه خصوصیت دارند، رویدادهای اشاره‌گر موس یک مجموعه دیگر. همانطور که به جزئیات رویدادهای مختلف می‌پردازیم درباره آنها در آینده بیشتر یاد خواهیم گرفت.

شئ رویداد در کنترل‌کننده‌های HTML نیز وجود دارد

اگر که یک کنترل‌کننده را درون HTML تعریف کنیم، می‌توانیم از شئ event استفاده کنیم، مانند:

<input type="button" onclick="alert(event.type)" value="نوع رویداد">

این امکان‌پذیر است زیرا زمانی که مرورگر صفت را می٬خواند، یک کنترل‌کننده مانند: function(event) {alert(event.type) } می‌سازد. که یعنی: اولین آرگومان "event" نام دارد و بدنه‌ی تابع از صفت گرفته شده است.

اشیاء کنترل‌کننده: handleEvent

نه تنها می‌توانیم تابع را به عنوان کنترل‌کننده‌ها استفاده کنیم، بلکه می‌توانیم با استفاده از addEventListener اشياء را نیز به عنوان کنترل‌کننده اختصاص دهیم. زمانی که رویدادی اتفاق می‌افتد خصوصیت handleEvent آن شئ صدا زده می‌شود.

برای مثال:

<button id="elem">روی من کلیک کن</button>

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

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

همانطور که میبینید زمانی که addEventListener یک شئ را به کنترل‌کننده دریافت می‌کند، obj.handleEvent(event) را در صورت اتفاق افتادن آن رویداد صدا می‌زند.

همچنین می‌توانیم از یک کلاس شخصی‌سازی شده برای این کار استفاده کنیم، مثل این:

<button id="elem">روی من کلیک کن</button>

<script>
  class Menu {
    handleEvent(event) {
      switch(event.type) {
        case 'mousedown':
          elem.innerHTML = "کلید موس فشار داده شد";
          break;
        case 'mouseup':
          elem.innerHTML += "...و رها شد.";
          break;
      }
    }
  }

  let menu = new Menu();

  elem.addEventListener('mousedown', menu);
  elem.addEventListener('mouseup', menu);
</script>

اینجا یک شئ هر دو رویداد را کنترل می‌کند. توجه کنید که برای اینکار باید دقیقا از addEventListener برای اختصاص کنترل‌کننده استفاده کنیم. menu فقط mousedown و mouseup را کنترل می‌کند و در صورت وقوع رویدادهای دیگر کاری انجام نمی‌دهد.

متد handleEvent قرار نیست که همه‌ی کارها را خودش انجام دهد. می‌تواند بقیه متد‌های مربوط به رویدادها را صدا بزند. برای مثال:

<button id="elem">روی من کلیک کن</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 = "کلید موس فشرده شد";
    }

    onMouseup() {
      elem.innerHTML += "...و رها شد.";
    }
  }

  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"> در زمان کلیک شدن مخفی شود.

دمو: