프론트엔드 개발일지 #23 - 이벤트와 this, 폼이벤트,preventDefault,이벤트 버블링, 이벤트 위임

조아라·2024년 10월 21일
0
post-thumbnail

이벤트와 this

버튼을 눌렀을때 글자와 배경색이 바뀌거나, h1를 눌렀을때 글자와 배경색이 바뀌는 이벤트를 준다고 했을때, this는 바꿀 대상을 참조해서 이벤트를 적용시켜준다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Events and This</title>

    <style>
        button {
            width: 100px;
            height: 100px;
            margin: 20px;
        }
    </style>
</head>

<body>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    <button>CLICK</button>
    
    <h1>Click Me!</h1>
    <h1>Click Me!</h1>
    <h1>Click Me!</h1>
    <h1>Click Me!</h1>
    <h1>Click Me!</h1>
    <h1>Click Me!</h1>
    

    <script src="app.js"></script>
</body>

</html>
const makeRandColor = () => {
    const r = Math.floor(Math.random() * 255);
    const g = Math.floor(Math.random() * 255);
    const b = Math.floor(Math.random() * 255);
    return `rgb(${r}, ${g}, ${b})`;
}

const buttons = document.querySelectorAll('button');

for (let button of buttons) {
    button.addEventListener('click', colorize)
}

const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
    h1.addEventListener('click', colorize)
}

function colorize() {
    this.style.backgroundColor = makeRandColor();
    this.style.color = makeRandColor();
}

키보드 이벤트와 이벤트 객체


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Events Object</title>

</head>

<body>
    <button>CLICK</button>
    <input type="text">
    <script src="app.js"></script>
</body>

</html>
document.querySelector('button').addEventListener('click', function (evt) {
    console.log(evt)
})

// const input = document.querySelector('input');
// input.addEventListener('keydown', function (e) {
//     console.log(e.key)
//     console.log(e.code)
// })
// input.addEventListener('keyup', function () {
//     console.log("KEYUP")
// })

window.addEventListener('keydown', function (e) {
    switch (e.code) {
        case 'ArrowUp':
            console.log("UP!");
            break;
        case 'ArrowDown':
            console.log("DOWN!");
            break;
        case 'ArrowLeft':
            console.log("LEFT!");
            break;
        case 'ArrowRight':
            console.log("RIGHT!");
            break
        default:
            console.log("IGNORED!")
    }
})

폼이벤트 & preventDefault


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Event Basics</title>
</head>

<body>
    <h1>Form Events</h1>

    <form action="/shelter" id="shelterForm">
        <input type="text" name="catName">
        <button>Submit</button>
    </form>
  <h2> Available Cats</h2>
  <ul id="cats"></ul>

    </ul>

    <script src="app.js"></script>
</body>

</html>
const tweetForm = document.querySelector('#tweetForm');
const tweetsContainer = document.querySelector('#tweets');
const list = document.querySelector('#cats');

tweetForm.addEventListener('submit', function (e) {
  //기존 페이지에 머무르게 만들어주는 메서드 사용 !
    e.preventDefault();
  // .value를 통해서 입력값을 받아와서 고양이 이름으로 선언
	const catName = input.value;
    const newLi = document.createElement("Li");
    newLi.innerText = catName;
    list.append(newLi);
});

위에 코드에서 폼 요소에 class 선택자로 submit을 준다면
=> 폼 요소의 기본 동작은 HTML에서는 action 속성을 어떤 값으로 설정하든 폼이 제출되었을때 해당 위치로 데이터가 전송된다. 또한 브라우저에 뜬 페이지 역시 해당 위치로 이동한다. 이런 동작이 필요할 때도 있다. 노드가 있고 자체적으로 구성한 백엔드가 있어서 데이터를 전송할 엔드포인트가 있는 경우에는 해당 데이터를 받아 처리한 다음 응답해야하기 때문이다. 하지만 지금은 응답해야 할 곳이 없다.

그렇다면 폼 이벤트로 submit하더라도 같은 창에 머무르려면 어떻게 해야 할까?

🤔 바로 Event.preventDefault()메서드를 사용하면 된다.
기본 브라우저에서 동작 수행을 중단하고 멈추게 한다. 원래 있던 페이지에 머물게 한다.
싱글 페이지 방식으로 앱을 구성해야 할 경우, 사용가작 폼에 데이터를 입력하고 제출 했을때 해당 데이터를 가져와 동일 페이지 내에서 처리해야 하기 때문이다.

양식 이벤트 실습

이제 양식 및 양식 이벤트 작업을 연습해 볼 시간입니다! index.html에는 이미 두 개의 input요소가 포함된 양식 요소가 있습니다. 두 요소 중 하나는 수량을 위한 것이고 다른 하나는 제품 이름을 위한 것입니다. index.html에는 새 li를 추가할 빈 ul도 포함되어 있습니다. 코드가 어떻게 작동해야 하는지 대략적으로 살펴보려면 하단의 GIF를 확인하세요. 여러분이 할 일은 다음 단계를 따르는 것입니다.

양식 제출 이벤트에 대한 리스너를 설정합니다.

양식이 제출될 때, 기본 동작을 방지합니다.

수량 입력 값과 제품 입력 값을 가져옵니다.

새 li 요소를 생성합니다. 새 li의 텍스트에 양식에서 입력받은 수량과 제품 이름이 포함되도록 설정합니다.

페이지의 ul에 새 li를 추가합니다.

입력 값을 재설정합니다.

참고:
1. 유데미의 인터페이스는 아직 일부 최신 JS 구문(예:.append()을 인식하지 못합니다. 테스트를 통과하려면 이 메서드에 대체 (이전) 구문을 사용해야 합니다.

  1. 테스트를 통과하려면 양식을 form이라는 변수에 할당해야 하며, 이 코드 라인은 이미 app.js 코드에 포함되어 있습니다.

입력과 변경 이벤트

const input = document.querySelector('input');
const h1 = document.querySelector('h1');

// input.addEventListener('change', function (e) {
//     console.log("CASKDJASKJHD")
// })

input.addEventListener('input', function (e) {
    h1.innerText = input.value;
})

변경 입력은 오직 입력을 블러(blur)할때만 발동한다. 즉 다른 부분에 집중 할 때!
그러니 다른 곳을 클릭하면 값이 출력된다.
'뭔가 일어날 때마다'나 '뭔가 변경될 때마다'리고 생각하면 안되고,
'그 입력을 떠날때마다'라고 생각해야 한다.
'떠난다'는 입력하고 Enter키를 친다는 것(변경이 아니라 포커싱이당)이 아니라,
거기 없던 뭔가를 타이핑하고 떠난다면 그것이 '변경'이다.

하지만 포커스해서 블러할때만이 아니라 이 입력내의 값이 달라질때마다 발동하게 하고 싶다면
위의 코드처럼 h1.innerText = input.value;로 바로 value를 넣을 수 있도록 하면 된다.
[ 개인과제 검색기능에도 이렇게 했다 !! const searchTerm = searchInput.value.trim(); 그럼 실시간으로 입력값을 받아서 영화의 목록을 띄운다.]

입력 이벤트 실습

index.html 파일을 보면 하나의 h1 요소와 하나의 input type="text" 요소가 있습니다. index.html에서 어떤 것도 변경하지 마세요! app.js에서 다음 요구 사항을 충족하는 코드를 작성하세요.

h1은 "Enter Your Username"이라는 텍스트로 시작해야 합니다(이 작업은 이미 마크업에서 처리되어 있습니다).

input 요소에서 input이벤트가 발생할 때마다 "Welcome, "이라는 문자열 뒤에 현재 텍스트 입력 필드의 값이 오도록 h1을 업데이트하세요. 아래 GIF를 보면서 이 코드가 어떻게 작동해야 하는지 확인하세요.

input이 다시 빈 상태가 되면 h1을 업데이트하여 텍스트가 다시 "Enter Your Username"이라고 표시되도록 하세요.

이벤트 버블링

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bubbling</title>

    <style>
        .hide {
            display: none;
        }
    </style>
</head>

<body>
    <section onclick="alert('section clicked')">
        <p onclick="alert('paragraph clicked')">
            I am a paragraph:
            <button onclick="alert('button clicked')">Click</button>
        </p>
    </section>




    <div id="container">
        Click To Hide
        <button id="changeColor">Change Color</button>
    </div>

    <script src="app.js"></script>

</body>

</html>
const button = document.querySelector('#changeColor');
const container = document.querySelector('#container');

button.addEventListener('click', function (e) {
    container.style.backgroundColor = makeRandColor();
    e.stopPropagation();
})
container.addEventListener('click', function () {
    container.classList.toggle('hide');
})

const makeRandColor = () => {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    return `rgb(${r}, ${g}, ${b})`;
}

분명 하나의 영역만 클릭했는데, 모든 onClick 이벤트 핸들러가 동작을 한다.
위와 같이 중첩된 요소에서 이벤트가 발생할 때는, 이벤트 전파(Event Propagation)가 이루어집니다.이벤트가 발생한 요소부터 점점 부모 요소를 거슬러 올라가서 window 까지 이벤트를 전파하는 것이다. 마치 거품(Bubble)이 물 아래에서부터 위로 올라가는 것 처럼!

버블링을 강제로 중단시키는 방법도 존재한다. 이벤트 객체의 메서드인event.stopPropagation()를 사용하면 된다.
((버블링은 유용하다. 버블링을 꼭 멈춰야 하는 명백한 상황이 아니라면 버블링을 막지않기))

이벤트 위임

아 이거 이해하기 진짜 힘들었다(ㅠㅜ)
내가 이벤트를 적용하고싶은 요소의 부모한테 이벤트를 위임하는것이다.

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Event Delegation</title>
	<link rel="stylesheet" href="../reset.css">
	<style>
		body {
			display: flex;
			align-items: center;
			justify-content: center;																						
		}
		@keyframes pointer-ani {
			0% { transform: scale(0); }
			50% { transform: scale(1); }
			100% { transform: scale(1.5); opacity: 0; }
		}
		.pointer {
			position: absolute;
			left: 0;
			top: 0;
			width: 60px;
			height: 60px;
			margin: -30px 0 0 -30px;
			border-radius: 50%;
			background: url('../images/ilbuni2.png') no-repeat 0 0 / cover;
			transform: scale(0);
			animation: pointer-ani 0.5s linear;
		}
		.menu {
			display: flex;
			margin-top: -10vh;
			padding: 2em;
			border-radius: 20px;
			background: #eee;
		}
		.menu-btn {
			display: inline-flex;
			align-items: center;
			margin: 0 1em;
			padding: 0.5em 1em 0.5em 0.5em;
			border: 5px solid white;
			border-radius: 20px;
			outline: 0;
			font: 900 1.2rem NotoSans;
			background: linear-gradient(0deg, rgba(223,210,0,1) 0%, rgba(255,240,0,1) 24%, rgba(255,240,0,1) 70%, rgba(255,255,255,1) 100%);
			box-shadow: rgba(0,0,0,0.1) 0 0 0 1px inset;
		}
		.btn-label {
			text-shadow: rgba(255,255,255,1) 0 1px 0;
			/*pointer-events: none;*/
		}
		.icon {
			width: 60px;
			/*pointer-events: none;*/
		}
	</style>
	<script src="IlbuniPointer.js"></script>
	<script>
		window.addEventListener('DOMContentLoaded', () => new IlbuniPointer());
	</script>
</head>
<body>
	<div class="menu">
		<button class="menu-btn" data-value="1">
			<img class="icon" src="../images/ilbuni1.png" alt="">
			<span class="btn-label">일분이 1</span>
		</button>
		<button class="menu-btn" data-value="2">
			<img class="icon" src="../images/ilbuni2.png" alt="">
			<span class="btn-label">일분이 2</span>
		</button>
		<button class="menu-btn" data-value="3">
			<img class="icon" src="../images/ilbuni3.png" alt="">
			<span class="btn-label">일분이 3</span>
		</button>
	</div>
```javascript
	<script>
		const menu = document.querySelector('.menu');

		function clickHandler(event) {
			let elem = event.target;
			while (!elem.classList.contains('menu-btn')) {
				elem = elem.parentNode;

				if (elem.nodeName == 'BODY') {
					elem = null;
					return;
				}
			}

			console.log(elem.dataset.value);
		}

		menu.addEventListener('click', clickHandler);
	</script>
```

일단 부모요소인 menu를 가져와주고, elem이라는 변수에 이벤트 타겟을 선언해준다.
console.log(elem.dataset.value);<button class="menu-btn" data-value="1">의 data-value 값을 확인해준다.

몇번 타야할지 모르니까 if가 아니라 while문으로 조건식을 써준다.
(menu-btn이 나타날때까지 해야하니까?)
그리고 다른 부분을 클릭하면
if (elem.nodeName == 'BODY') {
elem = null;
return;
} 이 부분인데 버튼의 바깥 영역을 눌렀때 제한을 걸어준다.

대충은 이해했는데 아직 확 와닿진 않아서, 조금 더 열심히 필요하다..

profile
끄적 끄적 배운 걸 적습니다 / FRONT-END STUDY VELOG

0개의 댓글