모던 자바스크립트 입문 - Chapter 14: 문서제어 💻
웹 개발에서 자바스크립트의 가장 강력한 기능 중 하나는 바로 문서 객체 모델(DOM)을 조작하는 것입니다.
HTML 문서의 구조를 동적으로 변경하고, 사용자와 상호작용하며, 웹 페이지를 살아 숨쉬는 애플리케이션으로 만들어주는 핵심 기술이죠.
이번 포스트에서는 DOM을 제어하는 다양한 방법들을 체계적으로 살펴보겠습니다.
📌 목차
- DOM 트리의 이해
- 노드 객체 가져오기
- 속성 값의 읽기와 쓰기
- HTML 요소의 내용을 읽고 쓰기
- 노드 생성/삽입/삭제하기
- HTML 요소의 위치
- HTML 폼 다루기
- CSS 제어하기
📝 DOM 트리의 이해
DOM(Document Object Model)은 HTML 문서를 트리 구조로 표현한 객체 모델입니다.
웹 브라우저가 HTML을 파싱할 때 생성되는 이 트리 구조를 통해 자바스크립트는 문서의 모든 요소에 접근하고 조작할 수 있습니다.
DOM 트리는 여러 종류의 노드로 구성됩니다.
가장 중요한 것은 요소 노드(Element Node)로, HTML 태그를 나타냅니다.
텍스트 노드(Text Node)는 태그 사이의 텍스트 내용을, 속성 노드(Attribute Node)는 HTML 요소의 속성을 나타냅니다.
모든 노드는 계층적 관계를 가지며, 부모-자식 관계와 형제 관계로 연결되어 있습니다.
이러한 구조 덕분에 특정 요소에서 시작해서 다른 요소로 탐색하는 것이 가능합니다.
📝 노드 객체 가져오기
DOM 조작의 첫 번째 단계는 원하는 요소를 선택하는 것입니다.
자바스크립트는 다양한 방법으로 노드 객체를 가져올 수 있는 메서드들을 제공합니다.
getElementById() 메서드는 특정 id를 가진 요소를 반환합니다.
id는 문서 내에서 유일해야 하므로, 이 메서드는 항상 하나의 요소만 반환합니다.
const header = document.getElementById('main-header');
getElementsByClassName()과 getElementsByTagName()은 클래스명이나 태그명으로 요소들을 선택합니다.
이들은 HTMLCollection을 반환하므로, 여러 요소가 선택될 수 있습니다.
const buttons = document.getElementsByClassName('btn');
const paragraphs = document.getElementsByTagName('p');
또 다른 방법으로는 querySelector()와 querySelectorAll()이 있습니다.
이들은 CSS 선택자를 사용해서 요소를 선택할 수 있어 매우 유연하고 강력합니다.
const firstButton = document.querySelector('.btn');
const allButtons = document.querySelectorAll('.btn');
const navLink = document.querySelector('nav a[href="#home"]');
📝 속성 값의 읽기와 쓰기
HTML 요소의 속성을 읽고 수정하는 것은 DOM 조작의 기본입니다.
getAttribute() 메서드로 속성값을 읽을 수 있고, setAttribute() 메서드로 속성값을 설정할 수 있습니다.
const link = document.querySelector('a');
const href = link.getAttribute('href'); // 속성값 읽기
link.setAttribute('href', 'https://example.com'); // 속성값 설정
hasAttribute() 메서드는 특정 속성이 존재하는지 확인하고, removeAttribute() 메서드는 속성을 제거합니다.
if (link.hasAttribute('target')) {
link.removeAttribute('target');
}
많이 사용되는 속성들은 프로퍼티로도 접근할 수 있습니다.
예를 들어, id, className, src, href 등은 직접 프로퍼티로 접근하고 수정할 수 있어 더 간편합니다.
const img = document.querySelector('img');
img.src = 'new-image.jpg';
img.className = 'thumbnail active';
data-* 속성의 경우 dataset 프로퍼티를 통해 접근할 수 있습니다.
이는 HTML5에서 도입된 기능으로, 사용자 정의 데이터를 저장하는 표준 방법입니다.
// <div data-user-id="123" data-role="admin">
const div = document.querySelector('div');
console.log(div.dataset.userId); // "123"
div.dataset.role = 'user';
📝 HTML 요소의 내용을 읽고 쓰기
요소의 내용을 조작하는 방법에는 여러 가지가 있습니다.
innerHTML 프로퍼티는 요소 내부의 HTML을 문자열로 반환하거나 설정합니다.
HTML 태그를 포함한 전체 내용을 다룰 때 유용합니다.
const container = document.getElementById('content');
container.innerHTML = '<h2>새로운 제목</h2><p>새로운 내용</p>';
console.log(container.innerHTML); // HTML 태그 포함된 내용
textContent 프로퍼티는 요소의 텍스트 내용만을 다룹니다.
HTML 태그는 무시되고 순수한 텍스트만 반환되거나 설정됩니다.
보안상 더 안전한 방법이기도 합니다.
const paragraph = document.querySelector('p');
paragraph.textContent = '안전한 텍스트 내용'; // XSS 공격 방지
console.log(paragraph.textContent); // 태그 없는 순수 텍스트
innerText 프로퍼티도 텍스트 내용을 다루지만, textContent와 달리 실제로 화면에 표시되는 텍스트만을 고려합니다.
숨겨진 요소의 텍스트는 포함되지 않습니다.
입력 요소의 경우 value 프로퍼티를 통해 값을 읽고 설정할 수 있습니다.
const input = document.querySelector('input[type="text"]');
input.value = '기본값 설정';
console.log(input.value); // 현재 입력된 값
📝 노드 생성/삽입/삭제하기
동적으로 요소를 생성하고 DOM에 추가하거나 제거하는 것은 현대 웹 애플리케이션의 핵심 기능입니다.
createElement() 메서드로 새로운 요소를 생성할 수 있습니다. 생성된 요소는 메모리에만 존재하므로, DOM에 추가하기 위해서는 별도의 과정이 필요합니다.
const newDiv = document.createElement('div');
newDiv.textContent = '새로 생성된 요소';
newDiv.className = 'new-element';
appendChild() 메서드는 요소를 다른 요소의 마지막 자식으로 추가합니다.
insertBefore() 메서드는 특정 위치에 요소를 삽입할 수 있습니다.
const container = document.getElementById('container');
container.appendChild(newDiv); // 마지막에 추가
const firstChild = container.firstElementChild;
container.insertBefore(newDiv, firstChild); // 첫 번째 위치에 삽입
removeChild() 메서드로 자식 요소를 제거할 수 있고, remove() 메서드를 사용하면 요소 자체에서 직접 제거할 수 있습니다.
// 방법 1: 부모 요소에서 제거
container.removeChild(newDiv);
// 방법 2: 요소 자체에서 제거 (더 간단)
newDiv.remove();
cloneNode() 메서드는 기존 노드를 복사할 때 사용합니다. 매개변수로 true를 전달하면 자식 요소까지 모두 복사하는 깊은 복사가 수행됩니다.
const original = document.querySelector('.template');
const copy = original.cloneNode(true); // 자식 요소까지 복사
container.appendChild(copy);
📝 HTML 요소의 위치
요소의 위치와 크기 정보를 얻는 것은 동적인 레이아웃 조작이나 애니메이션 구현에 필수적입니다.
offsetLeft, offsetTop 프로퍼티는 요소의 위치를 나타내고, offsetWidth, offsetHeight 프로퍼티는 요소의 크기를 나타냅니다.
이들은 border를 포함한 전체 크기를 반환합니다.
clientWidth, clientHeight는 padding을 포함하지만 border는 제외한 크기를 반환합니다.
scrollWidth, scrollHeight는 스크롤 영역을 포함한 전체 크기를 나타냅니다.
getBoundingClientRect() 메서드는 요소의 크기와 뷰포트에 상대적인 위치 정보를 담은 객체를 반환합니다.
이는 요소의 정확한 화면 상 위치를 알아야 할 때 매우 유용합니다.
📝 HTML 폼 다루기
웹 애플리케이션에서 사용자 입력을 처리하는 것은 핵심 기능입니다.
HTML 폼과 관련된 요소들을 자바스크립트로 제어하는 방법을 이해해야 합니다.
폼 요소는 document.forms 컬렉션을 통해 접근할 수 있고, 개별 폼 컨트롤은 elements 프로퍼티를 통해 접근할 수 있습니다.
입력 요소의 값은 value 프로퍼티로 읽고 설정할 수 있으며, 체크박스나 라디오 버튼의 경우 checked 프로퍼티를 사용합니다.
폼 검증은 checkValidity() 메서드를 사용하여 수행할 수 있고, setCustomValidity() 메서드로 사용자 정의 검증 메시지를 설정할 수 있습니다.
폼 이벤트인 submit, change, input 등을 활용하면 사용자 입력에 실시간으로 반응하는 인터페이스를 만들 수 있습니다.
📝 CSS 제어하기
자바스크립트를 통해 CSS 스타일을 동적으로 변경하는 것은 인터랙티브한 웹 페이지를 만드는 핵심 기술입니다.
style 프로퍼티를 통해 인라인 스타일을 직접 설정할 수 있습니다.
이때 CSS 속성명은 카멜케이스로 변환해서 사용해야 합니다.
const element = document.querySelector('.box');
element.style.backgroundColor = 'red';
element.style.fontSize = '16px';
element.style.marginTop = '20px';
getComputedStyle() 함수는 요소에 실제로 적용된 모든 CSS 스타일 값을 반환합니다.
이는 CSS 파일에서 정의된 스타일이나 상속된 스타일까지 포함합니다.
const computedStyle = getComputedStyle(element);
console.log(computedStyle.color); // 실제 적용된 색상값
console.log(computedStyle.width); // 계산된 너비값
클래스를 추가하거나 제거하는 방식도 많이 사용됩니다.
classList 프로퍼티의 add(), remove(), toggle() 메서드를 활용하면 효과적으로 스타일을 제어할 수 있습니다.
const button = document.querySelector('.btn');
button.classList.add('active'); // 클래스 추가
button.classList.remove('disabled'); // 클래스 제거
button.classList.toggle('highlighted'); // 클래스 토글
// 클래스 존재 여부 확인
if (button.classList.contains('active')) {
console.log('버튼이 활성 상태입니다');
}
CSS 변수(Custom Properties)도 자바스크립트에서 setProperty() 메서드를 통해 동적으로 변경할 수 있어, 테마 변경이나 동적 스타일링에 매우 유용합니다.
// CSS: :root { --primary-color: blue; }
document.documentElement.style.setProperty('--primary-color', 'green');
📚 마무리 정리
오늘 배운 DOM 제어 기술들을 정리하면:
DOM 트리는 HTML 문서의 구조를 객체 모델로 표현한 것으로, 모든 요소가 노드로 연결된 계층 구조입니다.
노드 선택은 getElementById, querySelector 등의 메서드를 통해 수행하며, CSS 선택자를 활용한 querySelector가 가장 유연합니다.
속성 제어는 getAttribute/setAttribute 메서드나 직접 프로퍼티 접근을 통해 가능하며, data-* 속성은 dataset으로 접근합니다.
내용 조작은 innerHTML, textContent, value 등의 프로퍼티를 통해 수행하며, 보안을 고려할 때는 textContent가 더 안전합니다.
동적 조작은 createElement로 요소를 생성하고 appendChild로 추가하며, remove()로 제거할 수 있습니다.
위치 정보는 offset, client, scroll 관련 프로퍼티와 getBoundingClientRect() 메서드로 얻을 수 있습니다.
폼 제어는 forms 컬렉션과 elements 프로퍼티를 활용하며, 검증 API를 통해 사용자 입력을 검증할 수 있습니다.
CSS 제어는 style 프로퍼티, classList, getComputedStyle() 등을 통해 가능하며, CSS 변수도 동적으로 변경할 수 있습니다.
💡 오늘 새롭게 알게 된 것
- DOM 트리의 계층 구조와 노드 간의 관계를 이해하게 되었습니다
- querySelector와 getElementById의 차이점과 각각의 장단점을 알게 되었습니다
- innerHTML과 textContent의 보안상 차이점을 이해했습니다
- getBoundingClientRect() 메서드의 활용법을 배웠습니다
🤔 어려웠던 점
- 다양한 노드 선택 방법들의 차이점과 언제 어떤 것을 사용해야 하는지 구분하는 것입니다
- offset, client, scroll 관련 프로퍼티들의 정확한 차이점을 이해하는 것입니다
🎯 다음 학습 계획
다음 포스팅에서는 이벤트 처리에 대해 알아보겠습니다.