یک رویداد نشانهای از چیزی است که اتفاق افتاده است. همهی عناصر درخت 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 اختصاص میدهد.
پس این روش درواقع شبیه روش قبلی است.
این دو قطعه کد همانند هم عمل میکنند:
-
Only HTML:
<input type="button" onclick="alert('کلیک!')" value="دکمه">
-
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 تعریف کنیم، میتوانیم از شئ 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>
اکنون، کنترلکنندهها به وضوح جدا شدهاند. در آینده برای ارتقا کد کار آسانتری خواهیم داشت.
خلاصه
۳ راه برای اختصاص کنترلکنندهها به رویدادها وجود دارد:
- صفتهای HTML:
onclick="..."
. - خصوصیت DOM:
elem.onclick = function
. - متدها:
elem.addEventListener(event, handler[, phase])
برای اضافه کردن,removeEventListener
برای حذف کردن.
صفتهای HTML کاربرد خیلی کمی دارند، چرا که نوشتن کد جاوااسکریپت وسط یک تگ HTML مقداری عجیب به نظر میرسد. همچنین نمیشود کد زیادی در آنجا نوشت.
صفتهای DOM برای استفاده مناسب است، اما نمیتوانیم بیشتر از یک کنترلکننده برای یک رویداد خاص استفاده کنیم. در بیشتر اوقات این محدودیت برای ما مهم نیست.
اما راه آخر قابل انعطافترین راه است، اما نوشتن بیشتری نسبت به دو روش قبل دارد. تعداد کمی از رویدادها فقط با این روش کار میکنند. مثل transitionend
و DOMContentLoaded
(در آینده درباره آن صحبت میکنیم). همچنین addEventListener
اشیا را نیز به عنوان کنترلکننده قبول میکند. در این حالت متد handleEvent
در صورت اتفاق افتادن رویداد صدا زده میشود.
بدون توجه به این که از کدام روش برای اختصاص دادن کنترلکننده به رویداد استفاده کنید، همیشه یک شئ رویداد به عنوان اولین ورودی دریافت خواهد کرد. این شئ شامل اطلاعات و جزئیاتی درباره اینکه چه اتفاقی افتاده است.
درباره کلیت رویدادها و تفاوت انواع آنها در بخشهای بعدی یاد خواهیم گرفت.