DOM 조작과 이벤트 처리 기초
우리는 지금까지 자바스크립트 기본 문법, 데이터 저장/조작, 흐름 제어를 학습했습니다. 이제는 이 지식을 바탕으로 웹 페이지와 직접 상호작용하는 단계로 넘어갑니다.
이를 가능하게 하는 핵심이 DOM(Document Object Model) 조작과 이벤트 처리입니다. 이번 장에서는 두 개념을 통해 정적 페이지에 동작과 반응을 추가하는 기본 흐름을 정리합니다.
DOM 조작: 웹 페이지의 요소 다루기
웹 브라우저가 HTML 문서를 로드하면, 그 문서는 객체 형태로 구조화되어 메모리에 저장됩니다. 이것이 바로 DOM(Document Object Model)입니다. DOM은 HTML 문서의 각 요소를 객체(노드)로 표현하고, 이 객체들을 트리(tree) 구조로 연결하여 구성됩니다. 자바스크립트는 이 DOM을 이용하여 HTML 요소의 내용, 스타일, 속성 등을 읽고 변경하며, 새로운 요소를 추가하거나 기존 요소를 삭제할 수 있습니다.
요소 선택하기: 조작의 첫걸음
어떤 HTML 요소를 조작하려면, 먼저 해당 요소를 자바스크립트 코드 내에서 선택해야 합니다. 요소를 선택하는 다양한 방법이 있습니다.
document.getElementById('id명'): id 속성 값으로 요소를 선택합니다. id는 문서 내에서 유일해야 합니다.
<h1 id="mainTitle">안녕하세요, DOM 조작!</h1>// script.js (HTML 파일에 연결된 자바스크립트 파일)
const titleElement = document.getElementById('mainTitle');
console.log(titleElement); // <h1 id="mainTitle">...</h1> 요소 객체가 출력됩니다.document.querySelector('CSS 선택자'): CSS 선택자와 동일한 방식으로 요소를 선택합니다. 조건에 맞는 첫 번째 요소를 반환합니다.
<p class="greeting">반갑습니다.</p>
<div id="container">
<span class="text">첫 번째 텍스트</span>
<span class="text">두 번째 텍스트</span>
</div>const firstParagraph = document.querySelector('p'); // 첫 번째 <p> 태그
const greetingText = document.querySelector('.greeting'); // class가 greeting인 첫 번째 요소
const containerDiv = document.querySelector('#container'); // id가 container인 요소
const firstSpan = document.querySelector('#container .text'); // container 내부의 첫 번째 class="text" span
console.log(firstParagraph);
console.log(greetingText);
console.log(containerDiv);
console.log(firstSpan);document.querySelectorAll('CSS 선택자'): CSS 선택자와 동일한 방식으로 요소를 선택합니다. 조건에 맞는 모든 요소를 NodeList 형태로 반환합니다. (배열과 유사하지만 완전히 같지는 않습니다. forEach 등을 사용할 수 있습니다.)
const allSpansInContainer = document.querySelectorAll('#container .text');
console.log(allSpansInContainer); // NodeList(2) [span.text, span.text]
allSpansInContainer.forEach(span => {
console.log(span.textContent);
});
// 결과:
// 첫 번째 텍스트
// 두 번째 텍스트요소 내용 조작하기
선택한 요소의 텍스트 내용이나 HTML 내용을 변경할 수 있습니다.
element.textContent: 요소 내부의 텍스트 콘텐츠를 가져오거나 설정합니다. HTML 태그는 순수한 텍스트로 처리됩니다.element.innerHTML: 요소 내부의 HTML 콘텐츠를 가져오거나 설정합니다. HTML 태그가 포함된 문자열을 할당하면 브라우저가 이를 HTML로 파싱하여 렌더링합니다.<p id="myParagraph">원래 텍스트</p> <div id="myDiv"></div>const paragraph = document.getElementById('myParagraph'); const myDiv = document.getElementById('myDiv'); // textContent 변경 paragraph.textContent = "새로운 텍스트입니다!"; console.log(paragraph.textContent); // 결과: 새로운 텍스트입니다! // innerHTML 변경 (HTML 태그 포함) myDiv.innerHTML = "<h2>새로운 제목</h2><p>이것은 <b>HTML</b>입니다.</p>";innerHTML은 편리하지만, 사용자 입력을 그대로innerHTML에 넣는 것은 보안상 취약점(XSS 공격)을 야기할 수 있으므로 주의해야 합니다. 순수한 텍스트만 변경할 때는textContent를 사용하는 것이 안전합니다.
요소 속성(Attribute) 조작하기
HTML 요소의 속성(예: src, href, class, id 등)을 가져오거나 설정할 수 있습니다.
element.getAttribute('속성명'): 특정 속성의 값을 가져옵니다.element.setAttribute('속성명', '값'): 특정 속성의 값을 설정합니다.element.removeAttribute('속성명'): 특정 속성을 제거합니다.<img id="myImage" src="image1.jpg" alt="첫 번째 이미지"> <a id="myLink" href="https://www.google.com">Google로 이동</a>const image = document.getElementById('myImage'); const link = document.getElementById('myLink'); // src 속성 값 가져오기 console.log(image.getAttribute('src')); // 결과: image1.jpg // src 속성 값 변경 image.setAttribute('src', 'image2.png'); image.setAttribute('alt', '두 번째 이미지'); // href 속성 값 변경 link.setAttribute('href', 'https://www.naver.com'); link.textContent = "Naver로 이동"; // 링크 텍스트도 변경 // class 속성 추가/제거 (class는 특별히 classList 메서드를 주로 사용) // image.removeAttribute('alt'); // alt 속성 제거
classList
요소의 class 속성은 스타일링에 매우 중요하며, 자바스크립트에서는 classList 속성을 통해 편리하게 조작할 수 있습니다.
element.classList.add('클래스명')element.classList.remove('클래스명')element.classList.toggle('클래스명')(있으면 제거, 없으면 추가)element.classList.contains('클래스명')(해당 클래스가 있는지 확인, 불리언 반환)<button id="myButton" class="btn">클릭하세요</button>/* style.css */ .btn { padding: 10px 20px; background-color: blue; color: white; } .active { background-color: green; font-weight: bold; }const button = document.getElementById('myButton'); button.classList.add('active'); // 'active' 클래스 추가 console.log(button.className); // 결과: "btn active" button.classList.remove('btn'); // 'btn' 클래스 제거 console.log(button.className); // 결과: "active" button.classList.toggle('active'); // 'active' 클래스 제거 console.log(button.className); // 결과: "" button.classList.toggle('active'); // 'active' 클래스 다시 추가 console.log(button.classList.contains('active')); // 결과: true
요소 스타일(CSS) 조작하기
자바스크립트로 직접 요소의 CSS 스타일을 변경할 수 있습니다. element.style.속성명 형식으로 접근합니다. CSS 속성 이름이 하이픈(-)으로 연결되어 있다면, 자바스크립트에서는 카멜 케이스(camelCase)로 변환하여 사용해야 합니다.
<p id="styledParagraph">이 문단은 스타일이 바뀔 것입니다.</p>const styledParagraph = document.getElementById('styledParagraph');
styledParagraph.style.color = 'red'; // 글자색 변경
styledParagraph.style.fontSize = '20px'; // 글자 크기 변경
styledParagraph.style.backgroundColor = 'lightgray'; // 배경색 변경 (CSS: background-color)하지만, 인라인 스타일로 직접 조작하는 것보다는 CSS 클래스를 토글하여 스타일을 변경하는 것이 더 효율적이고 유지보수가 용이합니다.
새로운 요소 생성 및 추가/제거하기
DOM API를 사용하여 완전히 새로운 HTML 요소를 생성하고, 문서에 추가하거나 제거할 수 있습니다.
document.createElement('태그명'): 새로운 HTML 요소를 생성합니다.부모요소.appendChild(자식요소): 부모 요소의 자식으로 요소를 맨 뒤에 추가합니다.부모요소.removeChild(자식요소): 부모 요소에서 특정 자식 요소를 제거합니다.<ul id="myList"> <li>기존 아이템 1</li> <li>기존 아이템 2</li> </ul>const myList = document.getElementById('myList'); // 새로운 li 요소 생성 const newItem = document.createElement('li'); newItem.textContent = "새로운 아이템 3"; newItem.style.color = "purple"; // 생성된 요소에 스타일 적용 // ul에 li 추가 myList.appendChild(newItem); // 결과: <ul>...<li>새로운 아이템 3</li></ul> // 마지막 아이템 제거 const lastChild = myList.lastElementChild; // 마지막 자식 요소 선택 if (lastChild) { myList.removeChild(lastChild); } // 결과: <ul><li>기존 아이템 1</li><li>기존 아이템 2</li></ul> (새로운 아이템 3은 사라짐)
이벤트 처리: 사용자 상호작용에 반응하기
DOM 조작과 이벤트 처리 기초에서는 값의 변화, 실행 순서, 화면 반응을 함께 봅니다.
이벤트(Event)는 웹 페이지에서 발생하는 모든 종류의 사건을 의미합니다. 예를 들어, 사용자가 버튼을 클릭하는 것, 키보드를 누르는 것, 마우스를 움직이는 것, 페이지 로딩이 완료되는 것 등 모든 것이 이벤트입니다. 이벤트 처리(Event Handling)는 이러한 이벤트가 발생했을 때 특정 자바스크립트 코드를 실행하도록 하는 메커니즘입니다.
이벤트 리스너 등록하기
요소에 이벤트를 감지하고 처리하는 함수(이벤트 핸들러)를 연결하는 가장 일반적이고 권장되는 방법은 addEventListener() 메서드를 사용하는 것입니다.
// element.addEventListener('이벤트_타입', 함수);이벤트_타입: 발생할 이벤트의 종류를 나타내는 문자열입니다. (예:'click','mouseover','keydown','submit','load')함수: 이벤트가 발생했을 때 실행될 코드 블록을 포함하는 함수(이벤트 핸들러)입니다.<button id="clickMeBtn">여기를 클릭하세요</button> <input type="text" id="myInput" placeholder="여기에 입력하세요">const clickButton = document.getElementById('clickMeBtn'); const myInput = document.getElementById('myInput'); // 1. 클릭 이벤트 (click) clickButton.addEventListener('click', function() { alert("버튼이 클릭되었습니다!"); }); // 2. 입력 이벤트 (input) myInput.addEventListener('input', function() { console.log("현재 입력 값:", myInput.value); }); // 3. 마우스 오버 이벤트 (mouseover) clickButton.addEventListener('mouseover', function() { this.style.backgroundColor = 'lightgreen'; // 일반 함수 리스너의 this는 보통 currentTarget과 같습니다. }); // 4. 마우스 아웃 이벤트 (mouseout) clickButton.addEventListener('mouseout', function() { this.style.backgroundColor = ''; // 원래대로 (또는 특정 색상) });
이벤트 핸들러 함수는 종종 event 객체(관례적으로 e나 event로 명명)를 인자로 받습니다. 이 event 객체는 발생한 이벤트에 대한 자세한 정보를 담고 있습니다.
myInput.addEventListener('keydown', function(e) {
console.log("키가 눌렸습니다. 눌린 키:", e.key); // 눌린 키 문자열
console.log("키 코드:", e.keyCode); // 눌린 키의 숫자 코드 (구식)
if (e.key === 'Enter') {
alert("Enter 키를 누르셨군요!");
}
});
// 링크 기본 동작 방지 예시
const preventLink = document.getElementById('myLink');
preventLink.addEventListener('click', function(e) {
e.preventDefault(); // 링크의 기본 동작(페이지 이동)을 막습니다.
console.log("링크 클릭! 하지만 페이지 이동은 막았습니다.");
});e.target: 이벤트가 실제로 발생한 요소를 가리킵니다.e.currentTarget: 현재 실행 중인 리스너가 등록된 요소를 가리킵니다. 이벤트 위임에서는target과currentTarget이 서로 다를 수 있습니다.e.eventPhase: 현재 리스너가 캡처, 타깃, 버블 단계 중 어디에서 실행되는지 나타냅니다.e.preventDefault(): 특정 이벤트의 기본 동작(예: 링크 클릭 시 페이지 이동, 폼 제출 시 새로고침)을 막습니다. 단, 취소 가능한 이벤트에서만 의미가 있고passive: true리스너에서는 사용할 수 없습니다.e.stopPropagation(): 이벤트가 상위 요소로 전파되는 것을 중단합니다. 같은 요소의 뒤쪽 리스너까지 막아야 한다면stopImmediatePropagation()을 사용합니다.
이벤트는 보통 캡처 단계 → 타깃 단계 → 버블 단계 → 기본 동작의 흐름으로 이해하면 됩니다. 대부분의 UI 코드는 버블 단계 리스너를 쓰지만, 바깥 클릭 감지나 문서 전체 감시처럼 먼저 확인해야 하는 작업은 { capture: true }를 선택할 수 있습니다.
이벤트 위임을 사용할 때는 부모 요소에 리스너를 하나만 등록하고 e.target.closest('[data-action]')처럼 실제 처리 대상을 찾습니다. 이때 e.currentTarget.contains(target)로 이벤트가 현재 영역 안에서 시작된 것인지 확인하면 다른 영역의 이벤트를 잘못 처리하는 일을 줄일 수 있습니다.
이벤트 리스너 제거하기
removeEventListener() 메서드를 사용하여 등록했던 이벤트 리스너를 제거할 수 있습니다. 이는 특히 메모리 누수를 방지하거나, 특정 조건에서만 이벤트를 처리하고 싶을 때 유용합니다. addEventListener()에 전달했던 것과 동일한 함수 참조를 사용해야 하며, 캡처 단계 리스너라면 capture 값도 맞춰야 합니다.
const onceButton = document.getElementById('clickMeBtn');
function clickHandler() {
alert("한 번만 실행됩니다!");
onceButton.removeEventListener('click', clickHandler); // 함수 실행 후 리스너 제거
}
onceButton.addEventListener('click', clickHandler);한 번만 실행하면 되는 리스너는 직접 제거하는 대신 { once: true } 옵션을 사용할 수 있습니다.
onceButton.addEventListener('click', () => {
alert("한 번만 실행됩니다!");
}, { once: true });여러 리스너를 한꺼번에 해제해야 한다면 AbortController의 signal을 공유하는 방식도 사용할 수 있습니다.
const controller = new AbortController();
window.addEventListener('resize', handleResize, {
signal: controller.signal,
});
controller.abort(); // signal을 공유한 리스너 해제자주 사용되는 이벤트 타입
몇 가지 일반적인 이벤트 타입을 정리해드립니다.
- 마우스 이벤트:
click,dblclick,mousedown,mouseup,mouseover,mouseout,mousemove - 키보드 이벤트: 새 코드에서는
keydown,keyup중심으로 처리합니다. - 폼 이벤트:
submit,focus,blur,change,input - 한글/IME 입력 이벤트:
compositionstart,compositionupdate,compositionend - 문서/창 이벤트:
load(페이지 로드 완료),DOMContentLoaded(DOM 트리 구성 완료),resize,scroll
DOM 조작과 이벤트 처리는 선택, 변경, 감지, 정리의 순서로 생각하면 안정적입니다. 다음 다이어그램은 사용자 입력이 화면 변경으로 이어지는 흐름을 한 번에 보여줍니다.
마지막으로 실제 화면 코드를 점검할 때는 선택자 범위, 이벤트 등록 위치, 화면 변경 방식을 분리해서 확인합니다.
반복 실전 점검
이번 장에서는 자바스크립트로 웹 페이지를 동적으로 변경하는 DOM 조작과 이벤트 처리를 다뤘습니다.
DOM 조작에서는 요소 선택, 내용 변경(textContent, innerHTML), 속성 변경(setAttribute, classList), 스타일 변경(style)을 확인했습니다. 새 요소를 생성하고 추가하거나 제거하는 흐름도 함께 다뤘습니다.
이벤트 처리를 통해 사용자의 상호작용(클릭, 입력 등)에 반응하여 특정 자바스크립트 코드를 실행하는 방법을 배웠습니다. addEventListener()를 사용하여 이벤트 리스너를 등록하고, event 객체를 통해 이벤트에 대한 상세 정보를 얻는 방법을 이해했습니다.
DOM 조작과 이벤트 처리는 정적인 HTML을 사용자 입력에 반응하는 화면으로 바꾸는 기본 도구입니다. 예제를 직접 바꿔 보며 어떤 이벤트가 어떤 요소와 상태를 바꾸는지 확인해 보세요.
아래 다이어그램은 DOM 조작: 웹 페이지 요소 다루기를 값의 흐름, 실행 컨텍스트, 브라우저 반응 기준으로 살펴봅니다.
DOM 조작과 이벤트 처리 기초에서 다시 볼 기준과 확인 순서를 정리했습니다.
다음 학습으로 넘어가기 전, DOM 조작과 이벤트 처리 기초에서 남은 개념 경계와 실습 확인 포인트를 점검합니다.