JavaScript - The Complete Guide 2022

Section 14: Events - Beyond "click" Listeners

olivia_yj 2022. 11. 1. 04:59

The goals

๐Ÿ’ช๐ŸปA Closer Look at Events

โœŒ๐ŸปEvent Propagation

๐Ÿ‘๐ŸปExample: Drag & Drop

 

Events in JavaScript

์˜ˆ์‹œ: ๋ฌดํ•œ ์Šคํฌ๋กค๋ง ๊ธฐ์ดˆ

scroll ์ด๋ฒคํŠธ๋กœ ๋ฌดํ•œํžˆ ์Šคํฌ๋กคํ•  ์ˆ˜ ์žˆ๋Š” ๋ชฉ๋ก์„ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค(์•„๋ž˜ ์„ค๋ช… ์ฐธ์กฐ)!

 

์ด ์ฝ”๋“œ ์Šค๋‹ˆํŽซ์€ ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค- ์„ธ๋กœ๋กœ ์Šคํฌ๋กคํ•  ์ˆ˜ ์žˆ๋Š”์ง€(์ถฉ๋ถ„ํ•œ ๋”๋ฏธ ์ฝ˜ํ…์ธ ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ height ๋ฅผ ๋งŽ์ด ์ถ”๊ฐ€ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ์Šคํƒ€์ผ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ธŒ๋ผ์šฐ์ € ์ฐฝ์„ ์„ธ๋กœ๋กœ ์ถ•์†Œํ•ด ๋ณด๋ฉด์„œ) ํ™•์ธํ•˜์„ธ์š”.

let curElementNumber = 0;
 
function scrollHandler() {
	const distanceToBottom = document.body.getBoundingClientRect().bottom;
 
	if (distanceToBottom < document.documentElement.clientHeight + 150) {
		const newDataElement = document.createElement('div');
		curElementNumber++;
		newDataElement.innerHTML = `<p>Element ${curElementNumber}</p>`;
		document.body.append(newDataElement);
	}
}
 
window.addEventListener('scroll', scrollHandler);

์—ฌ๊ธฐ์—์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋Š” ๊ฑธ๊นŒ์š”?

๋งจ ์•„๋ž˜์—์„œ๋Š” scrollHandler ํ•จ์ˆ˜๋ฅผ ์ฐฝ ๊ฐ์ฒด์˜ 'scroll' ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ํ•ธ๋“ค๋Ÿฌ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

์ด ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋จผ์ € ๋ทฐํฌํŠธ(ํ˜„์žฌ ๋ณด๊ณ  ์žˆ๋Š” ๊ฒƒ์˜ ์™ผ์ชฝ ์ƒ๋‹จ ๋ชจ์„œ๋ฆฌ)์™€ (๋‹จ์ˆœํžˆ ํ˜„์žฌ ๋ณด์ด๋Š” ์˜์—ญ ์‚ฌ์ด์˜ ์ด ๊ธธ์ด๊ฐ€ ์•„๋‹Œ) ํŽ˜์ด์ง€ ๋ ์‚ฌ์ด์˜ ์ด ๊ธธ์ด๋ฅผ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค. ) => distanceToBottom์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋ธŒ๋ผ์šฐ์ € ์ฐฝ์˜ ๋†’์ด๊ฐ€ 500px์ธ ๊ฒฝ์šฐ ์Šคํฌ๋กคํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ˜ํ…์ธ ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด distanceToBottom์€ 684px์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ์ „์ฒด ์ฝ˜ํ…์ธ  ํ•˜๋‹จ๊นŒ์ง€์˜ ๊ธธ์ด(distanceToBottom)์™€ ์ฐฝ ๋†’์ด + ํŠน์ • ์ž„๊ณ„๊ฐ’ (์ด ์˜ˆ์—์„œ๋Š” 150px )์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. document.documentElement.clientHeight ๋Š” ์ž ์žฌ์ ์ธ ์Šคํฌ๋กค๋ฐ”๋ฅผ ๊ณ ๋ คํ•˜๋ฏ€๋กœ window.innerHeight๋ณด๋‹ค ์„ ํ˜ธ๋ฉ๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€ ์ฝ˜ํ…์ธ  ๋๊นŒ์ง€์˜ ๊ธธ์ด๊ฐ€ 150px ๋ฏธ๋งŒ์ธ ๊ฒฝ์šฐ if ๋ธ”๋ก(์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ณณ)์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ if ๋ฌธ ๋‚ด๋ถ€์—์„œ ์ƒˆ <div> ์š”์†Œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด ์š”์†Œ๋ฅผ <p> ์š”์†Œ๋กœ ์ฑ„์›Œ ์ฐจ๋ก€๋กœ ์ฆ๊ฐ€๋˜๋Š” ์นด์šดํ„ฐ ๊ฐ’์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

 
 

event

event.preventDefault()

The preventDefault() method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.

The event continues to propagate as usual, unless one of its event listeners calls stopPropagation() or stopImmediatePropagation(), either of which terminates propagation at once.

As noted below, calling preventDefault() for a non-cancelable event, such as one dispatched via EventTarget.dispatchEvent(), without specifying cancelable: true has no effect.

 

Capturing and Bubbling

Bubbling

The bubbling principle is simple.

When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.

Let’s say we have 3 nested elements FORM > DIV > P with a handler on each of them:

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

A click on the inner <p> first runs onclick:

  1. On that <p>.
  2. Then on the outer <div>.
  3. Then on the outer <form>.
  4. And so on upwards till the document object.

So if we click on <p>, then we’ll see 3 alerts: p  div  form.

The process is called “bubbling”, because events “bubble” from the inner element up through parents like a bubble in the water.

 

Capturing

There’s another phase of event processing called “capturing”. It is rarely used in real code, but sometimes can be useful.

The standard DOM Events describes 3 phases of event propagation:

  1. Capturing phase – the event goes down to the element.
  2. Target phase – the event reached the target element.
  3. Bubbling phase – the event bubbles up from the element.

Here’s the picture, taken from the specification, of the capturing (1), target (2) and bubbling (3) phases for a click event on a <td> inside a table:

 

That is: for a click on <td> the event first goes through the ancestors chain down to the element (capturing phase), then it reaches the target and triggers there (target phase), and then it goes up (bubbling phase), calling handlers on its way.

Until now, we only talked about bubbling, because the capturing phase is rarely used.

In fact, the capturing phase was invisible for us, because handlers added using on<event>-property or using HTML attributes or using two-argument addEventListener(event, handler) don’t know anything about capturing, they only run on the 2nd and 3rd phases.

To catch an event on the capturing phase, we need to set the handler capture option to true:

elem.addEventListener(..., {capture: true})

// or, just "true" is an alias to {capture: true}
elem.addEventListener(..., true)

There are two possible values of the capture option:

  • If it’s false (default), then the handler is set on the bubbling phase.
  • If it’s true, then the handler is set on the capturing phase.

Note that while formally there are 3 phases, the 2nd phase (“target phase”: the event reached the element) is not handled separately: handlers on both capturing and bubbling phases trigger at that phase.

 

Event propagation & StopPropagation

Event propagation is a mechanism that defines how events propagate or travel through the DOM tree to arrive at its target and what happens to it afterward.

Let's understand this with the help of an example, suppose you have assigned a click event handler on a hyperlink (i.e. <a> element) which is nested inside a paragraph (i.e. <p> element). Now if you click on that link, the handler will be executed. But, instead of link, if you assign the click event handler to the paragraph containing the link, then even in this case, clicking the link will still trigger the handler. That's because events don't just affect the target element that generated the event—they travel up and down through the DOM tree to reach their target. This is known as event propagation

In modern browser event propagation proceeds in two phases: capturing, and bubbling phase. Before we proceed further, take a look at the following illustration:

Above image demonstrates how event travels in the DOM tree during different phases of the event propagation when an event is fired on an element that has parent elements.

The concept of event propagation was introduced to deal with the situations in which multiple elements in the DOM hierarchy with a parent-child relationship have event handlers for the same event, such as a mouse click. Now, the question is which element's click event will be handled first when the user clicks on the inner element—the click event of the outer element, or the inner element.

In the following sections of this chapter we will discuss each phases of the event propagation in greater detail and find out the answer of this question.

toggle()

 

Event delegation

 

Quiz

์งˆ๋ฌธ 1:

"์ด๋ฒคํŠธ"์˜ ๊ฐœ๋…์€ JavaScript์—๋งŒ ์žˆ์„๊นŒ์š”?

  • ์˜ˆ

  • ์•„๋‹ˆ์˜ค - NodeJS์—๋Š” ์กด์žฌํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—๋Š” ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • ์•„๋‹ˆ์˜ค

์งˆ๋ฌธ 2:

addEventListener('click', function() { ... })๊ฐ€ someElement.onclick = function() { ... }๋ณด๋‹ค ์„ ํ˜ธ๋˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ์š” ?

  • ํด๋ฆญ์€ ๋‘ ๋ฒˆ ์ด์ƒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ (addEventListener์—์„œ ์ œ๊ณตํ•˜๋Š”) ๋ณด๋‹ค ์œ ์—ฐํ•œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค.

  • addEventListener๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€(์ œ๊ฑฐ๋„ ๊ฐ€๋Šฅ)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • addEventListener๋Š” ๋” ๋ฐ”๋žŒ์งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค ๊ทธ ๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค!

์งˆ๋ฌธ 3:

DOM ์ด๋ฒคํŠธ๋ฅผ ๋ฆฌ์Šค๋‹ํ•  ๋•Œ ์–ป๋Š” “์ด๋ฒคํŠธ” ๊ฐ์ฒด ๋‚ด๋ถ€์—๋Š” ๋ฌด์—‡์ด ์žˆ์„๊นŒ์š”?

  • ์ด๋ฒคํŠธ์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ผ๋ถ€ ํ”„๋กœํผํ‹ฐ์™€ ๋ฉ”์„œ๋“œ(์˜ˆ: event.target)๋Š” ํ•ญ์ƒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

  • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ์˜ ์ปค์„œ์˜ ์ขŒํ‘œ์ž…๋‹ˆ๋‹ค.

  • ์—ฌ๋Ÿฌ๋ถ„(๊ฐœ๋ฐœ์ž)์ด ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ๊ตฌ์„ฑํ•œ ๋ฐฉ์‹์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์œ ํ•˜๋Š” ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ์ด๋ฒคํŠธ(์˜ˆ: ํด๋ฆญ์— ๋Œ€ํ•œ MouseEvent)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ์ผ๋ถ€ ํ•ต์‹ฌ ํ”„๋กœํผํ‹ฐ์™€ ๋ฉ”์„œ๋“œ๋Š” ํ•ญ์ƒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

 

์งˆ๋ฌธ 4:

someEvent.preventDefault()์™€ someEvent.stopPropagation()์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ผ๊นŒ์š”?

  • ๋‘ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  • preventDefault()๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ์ง„ํ–‰์„ ์ค‘์ง€ํ•˜๊ณ  ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์„ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค. stopPropagation()์€ (๋‹ค๋ฅธ ์š”์†Œ์— ๋Œ€ํ•ด) ๋™์ผํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‹ค๋ฅธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์—†๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

  • preventDefault()๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ธฐ๋ณธ ๋™์ž‘์„ ๊ณ„์†ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•˜๊ณ , stopPropagation()์€ (๋‹ค๋ฅธ ์š”์†Œ์— ๋Œ€ํ•ด) ๋‹ค๋ฅธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋™์ผํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

draggable & droppable

HTML Drag and Drop interfaces enable applications to use drag-and-drop features in browsers.

The user may select draggable elements with a mouse, drag those elements to a droppable element, and drop them by releasing the mouse button. A translucent representation of the draggable elements follows the pointer during the drag operation.

You can customize which elements can become draggable, the type of feedback the draggable elements produce, and the droppable elements.

This overview of HTML Drag and Drop includes a description of the interfaces, basic steps to add drag-and-drop support to an application, and an interoperability summary of the interfaces.

<script>
  function dragstart_handler(ev) {
    // Add the target element's id to the data transfer object
    ev.dataTransfer.setData("text/plain", ev.target.id);
  }

  window.addEventListener("DOMContentLoaded", () => {
    // Get the element by id
    const element = document.getElementById("p1");
    // Add the ondragstart event listener
    element.addEventListener("dragstart", dragstart_handler);
  });
</script>

<p id="p1" draggable="true">This element is draggable.</p>

Define a drop zone

By default, the browser prevents anything from happening when dropping something onto most HTML elements. To change that behavior so that an element becomes a drop zone or is droppable, the element must have both ondragover and ondrop event handler attributes.

The following example shows how to use those attributes, and includes basic event handlers for each attribute.

<script>
  function dragover_handler(ev) {
    ev.preventDefault();
    ev.dataTransfer.dropEffect = "move";
  }
  function drop_handler(ev) {
    ev.preventDefault();
    // Get the id of the target and add the moved element to the target's DOM
    const data = ev.dataTransfer.getData("text/plain");
    ev.target.appendChild(document.getElementById(data));
  }
</script>

<p
  id="target"
  ondrop="drop_handler(event)"
  ondragover="dragover_handler(event)">
  Drop Zone
</p>

Note that each handler calls preventDefault() to prevent additional event processing for this event (such as touch events or pointer events).

 

 

 

 

 

Sources

https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault

 

Event.preventDefault() - Web APIs | MDN

The preventDefault() method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.

developer.mozilla.org

https://www.tutorialrepublic.com/javascript-tutorial/javascript-event-propagation.php

 

JavaScript Event Propagation - Tutorial Republic

JavaScript Event Propagation In this tutorial you will learn how events propagate in the DOM tree in JavaScript. Understanding the Event Propagation Event propagation is a mechanism that defines how events propagate or travel through the DOM tree to arrive

www.tutorialrepublic.com