۲۹ سپتامبر ۲۰۲۲

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

یک رویداد نشانه‌ای از چیزی است که اتفاق افتاده است. همه‌ی عناصر درخت 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() را صدا نخواهد زد، درباره این موضوع بعدا در قسمت Browser default actions صحبت خواهیم کرد.

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

دمو:

باز کردن یک sandbox برای تمرین.

اهمیت: 5

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

مثل این:

برای دسترسی به “خود عنصر” می‌توانیم درون کنترل‌کننده از this استفاده کنیم:

<input type="button" onclick="this.hidden=true" value="کلیک کنید تا مخفی شود">
اهمیت: 5

یک دکمه درون متغیر ذخیره شده. ولی هیچ کنترل‌کننده به آن اختصاص نیافته.

با توجه به کد زیر، کدام کنترل‌کننده بعد از کلیک اجرا می‌شود؟ کدوم پیغام نمایش داده می‌شود؟

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 کار می‌کند.

اهمیت: 5

باید یک کلیک توپ را در طول زمین حرکت کنید. مانند:

نیازها:

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

نکات:

  • کد شما باید با توپ‌ها و اندازه‌های مختلف زمین کارکند، و مقید به مقادیر ثابتی نباشد.
  • از خصوصیات event.clientX/event.clientY برای گرفتن مختصات اشاره‌گر موس استفاده کنید.

باز کردن یک sandbox برای تمرین.

اول نیاز داریم که روش قرارگیری توپ را انتخاب کنیم.

نمی‌توانیم از position:fixed به این منظور استفاده کنیم. چون اسکرول کردن صفحه باید توپ را در زمین جابجا کند.

پس باید از position:absolute استفاده کنیم و برای اینکه از موقعیت توپ مطمئن شویم، روش قرارگیری field را نیز تنظیم می‌کنیم.

در این صورت، توپ نسبت به زمین قرار گرفته می‌شود:

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

#ball {
  position: absolute;
  left: 0; /* نسبت به نزدیکترین والدی که موقعیت آن تنظیم شده (field) */
  top: 0;
  transition: 1s all; /* انیمیشن‌های CSS برای چپ/بالا، انگار توپ پرتاب شده */
}

حال باید مقدارهای درستی به ball.style.left/top بدهیم. این مقدار نسبت به زمین خواهد بود.

مانند این تصویر:

ما 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 آنها را مقدار دهی کنیم.

باز کردن راه‌حل درون sandbox.

اهمیت: 5

یک منویی بسازید که در زمان کلیک باز/بسته شود:

پی‌نوشت: سورس کد HTML/CSS سند باید تغییر کند.

باز کردن یک sandbox برای تمرین.

HTML/CSS

اول باید که HTML/CSS را بسازیم.

منو یک جزء گرافیکی مستقل روی صفحه است. پس بهتر است که همه آنرا را درون یک عنصر DOM قرار دهیم.

یک لیست از آیتم‌های منو می‌تواند مانند لیستی از ul/li باشد.

در اینجا یک ساختار نمونه آمده:

<div class="menu">
  <span class="title">شیرینی‌ها (کلیک کنید)!</span>
  <ul>
    <li>کیک</li>
    <li>دونات</li>
    <li>عسل</li>
  </ul>
</div>

برای عنوان از <spcan> استفاده می‌کنیم، زیرا <div> از قبل یک ویژگی خاص display: block دارد و 100% عرض افقی را پر می‌کند.

مانند این:

<div style="border: solid red 1px" onclick="alert(1)">شیرینی‌ها (کلیک کنید)!</div>

پس اگر onclick را روی آن تعریف کنیم، کلیک‌ها را در سمت راست متن نیز دریافت می‌کند.

در صورتی که <span> از قبل یک ویژگی خاص display: inline‍ دارد، تنها فضای مورد نیاز متن را اشغال می‌کند:

<span style="border: solid red 1px" onclick="alert(1)">شیرینی‌ها (کلیک کنید)!</span>

باز و بسته کردن منو

باز و بسته کردن منو باید کمان‌ها را تغییر دهد و آیتم‌های درون لیست منو را نشان دهد یا مخفی کند.

همه‌ی این تغییرات کاملا توسط CSS کنترل می‌شود. در جاوااسکریپت باید وضعیت باز و بسته رودن را با اضافه کردن یا حذف کردن کلاس .open مشخص کنیم.

بدون این کلاس، منو بسته است:

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

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

… و با .open کمان‌ها تغییر می‌کنند و لیست نمایش داده می‌شود:

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

.menu.open ul {
  display: block;
}

باز کردن راه‌حل درون sandbox.

اهمیت: 5

لیستی از پیام‌ها موجود است.

با استفاده از جاوااسکریپت یک دکمه بستن به گوشه بالا راست هر پیام اضافه کنید.

نتیجه باید چنین چیزی باشد:

باز کردن یک sandbox برای تمرین.

برای اضافه کردن دکمه می‌توانیم از position:absoulute (و روی pane از 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();

باز کردن راه‌حل درون sandbox.

اهمیت: 4

یک “اسلایدر چرخشی” بسازید. نوار از عکس‌ها که با کلیک بر روی فلش‌ها پیمایش می‌شوند.

بعدا می‌توانیم امکانات دیگری به آن اضافه کنیم: پیمایش بی‌نهایت، بارگزاری پویا و …

پی‌نوشت: برای این تکلیف ساختار HTML/CSS تقریبا 90% راه حل است.

باز کردن یک sandbox برای تمرین.

نوار عکس‌ها را می‌توان به عنوان یک لیست ul/li‍ شامل عکس‌ها <img> نشان داد.

معمولا، چنین نوار عریض است، اما ما یک <div> با سیز ثایت دور قرار می‌دهیم تا آنرا “ببریم”. با اینکار فقط قستمی از آن نمایش داده می‌شود:

برا اینکه لیست را افقی نمایش دهیم باید خصوصیات CSS‌ درستی را برای <li> نسبت دهیم. مثلا display: inline-block.

برای <img> باید display را تغییر دهیم. چرا که مقدار پیشفرض آن inline است. فضای رزورشده اضافی زیر عناصر inline قرار دارد (برای “letter tail” ها)، برای حذف آن می‌توانیم از display:block کمک بگیریم.

برای پیمایش کردن، می‌توانیم <ul> را تغییر مکان دهیم. راه‌های زیادی برای این کار وجود دارد. برای مثال عوض کردن مقدار margin-left یا (برای کارایی بالاتر) استفاده از transform: translateX()‍:

<div> بیرونی طول ثابتی دارد. پس عکس‌های “اضافه” برش داده می‌شوند.

همه‌ی این اسلایدر یک حزء گرافیکی مستقل روی صحفه است. پس بهتر است آنرا درون یک <div class="carousel"> قرار دهیم و اجزای درون آنرا استایل دهیم

باز کردن راه‌حل درون sandbox.

نقشه آموزش

نظرات

قبل از نظر دادن این را بخوانید…
  • اگر پیشنهادی برای بهبود ترجمه دارید - لطفا یک ایشوی گیت‌هاب یا یک پول‌ریکوئست به جای کامنت‌گذاشتن باز کنید.
  • اگر چیزی را در مقاله متوجه نمی‌شوید – به دقت توضیح دهید.
  • برای قراردادن یک خط از کد، از تگ <code> استفاده کنید، برای چندین خط – کد را درون تگ <pre> قرار دهید، برای بیش از ده خط کد – از یک جعبهٔ شنی استفاده کنید. (plnkr، jsbin، codepen…)