강의 내용
1. DOM 트리 탐색
DOM 요소 간의 관계를 이용하여 효율적으로 요소를 찾는 방법을 학습합니다.
// 부모 요소 찾기
const child = document.getElementById('myChild');
const parent = child.parentElement;
const parentNode = child.parentNode;
// 자식 요소 찾기
const container = document.getElementById('container');
const firstChild = container.firstElementChild;
const lastChild = container.lastElementChild;
const allChildren = container.children; // HTMLCollection
const childNodes = container.childNodes; // NodeList (텍스트 노드 포함)
// 형제 요소 찾기
const current = document.getElementById('current');
const nextSibling = current.nextElementSibling;
const prevSibling = current.previousElementSibling;
// 요소 확인
if (element.hasChildNodes()) {
console.log('자식 노드가 있습니다');
}
console.log('자식 요소 개수:', element.children.length);
실용적인 DOM 탐색 예제
// 모든 버튼에 클릭 이벤트 추가
const buttons = document.querySelectorAll('.action-btn');
buttons.forEach((button, index) => {
button.addEventListener('click', (e) => {
// 클릭된 버튼의 다음 형제 요소 찾기
const nextElement = e.target.nextElementSibling;
if (nextElement) {
nextElement.classList.toggle('hidden');
}
});
});
// 부모 요소에 이벤트 위임 사용
const parentList = document.getElementById('task-list');
parentList.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
// 버튼의 부모 리스트 아이템 삭제
const listItem = e.target.closest('li');
listItem.remove();
}
});
2. 동적 요소 생성과 추가
JavaScript로 새로운 DOM 요소를 만들고 페이지에 추가하는 방법을 알아봅시다.
요소 생성 기본 방법
// 새 요소 생성
const newDiv = document.createElement('div');
const newParagraph = document.createElement('p');
const newButton = document.createElement('button');
// 속성 설정
newDiv.className = 'card';
newDiv.id = 'dynamic-card';
newButton.type = 'button';
newButton.setAttribute('data-action', 'delete');
// 내용 추가
newParagraph.textContent = '동적으로 생성된 문단입니다.';
newButton.innerHTML = ' 삭제';
// 요소 추가
const container = document.getElementById('container');
container.appendChild(newDiv); // 마지막에 추가
container.insertBefore(newDiv, firstChild); // 첫 번째로 추가
newDiv.appendChild(newParagraph);
newDiv.appendChild(newButton);
더 편리한 insertAdjacentHTML 사용
const container = document.getElementById('container');
// HTML 문자열로 요소 추가
const cardHTML = `
새로운 카드
동적으로 생성된 카드입니다.
`;
// 다양한 위치에 추가 가능
container.insertAdjacentHTML('beforebegin', cardHTML); // 요소 앞
container.insertAdjacentHTML('afterbegin', cardHTML); // 요소 내부 첫 번째
container.insertAdjacentHTML('beforeend', cardHTML); // 요소 내부 마지막
container.insertAdjacentHTML('afterend', cardHTML); // 요소 뒤
할 일 목록 동적 추가 예제
function addTodoItem(text) {
// 새 리스트 아이템 생성
const li = document.createElement('li');
li.className = 'todo-item';
// 체크박스 생성
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.addEventListener('change', toggleComplete);
// 텍스트 스팬 생성
const textSpan = document.createElement('span');
textSpan.textContent = text;
textSpan.className = 'todo-text';
// 삭제 버튼 생성
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '삭제';
deleteBtn.className = 'delete-btn';
deleteBtn.addEventListener('click', deleteTodoItem);
// 요소들을 리스트 아이템에 추가
li.appendChild(checkbox);
li.appendChild(textSpan);
li.appendChild(deleteBtn);
// 할 일 목록에 추가
const todoList = document.getElementById('todo-list');
todoList.appendChild(li);
}
3. 요소 삭제와 수정
DOM에서 요소를 제거하고 수정하는 다양한 방법을 알아봅시다.
// 요소 삭제
const elementToDelete = document.getElementById('delete-me');
elementToDelete.remove(); // 직접 삭제 (최신 방법)
// 부모에서 자식 삭제 (구 방법)
const parent = elementToDelete.parentNode;
parent.removeChild(elementToDelete);
// 모든 자식 요소 삭제
const container = document.getElementById('container');
container.innerHTML = ''; // 간단하지만 이벤트 리스너도 삭제됨
// 안전한 자식 요소 삭제
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// 특정 조건의 요소들 삭제
const itemsToDelete = document.querySelectorAll('.completed');
itemsToDelete.forEach(item => item.remove());
// 요소 교체
const oldElement = document.getElementById('old');
const newElement = document.createElement('div');
newElement.textContent = '새로운 요소';
oldElement.parentNode.replaceChild(newElement, oldElement);
동적 스타일 변경
// 스타일 직접 변경
element.style.backgroundColor = '#ff6b6b';
element.style.transform = 'scale(1.1)';
element.style.transition = 'all 0.3s ease';
// CSS 클래스로 스타일 변경 (권장)
element.classList.add('highlighted');
element.classList.remove('hidden');
element.classList.toggle('active');
// 조건부 클래스 적용
const isActive = someCondition;
element.classList.toggle('active', isActive);
// 여러 클래스 한번에 처리
element.className = 'card active highlighted';
// 계산된 스타일 가져오기
const computedStyle = window.getComputedStyle(element);
const currentColor = computedStyle.getPropertyValue('color');
4. 이미지 갤러리 구현
동적 DOM 조작을 활용한 실용적인 이미지 갤러리를 만들어봅시다.
<!-- HTML 구조 -->
<div class="gallery-container">
<div class="gallery-grid" id="gallery-grid"></div>
<div class="modal" id="image-modal">
<div class="modal-content">
<span class="close">×</span>
<img id="modal-image" src="" alt="">
<div class="modal-nav">
<button id="prev-btn">이전</button>
<button id="next-btn">다음</button>
</div>
</div>
</div>
</div>
// 이미지 갤러리 JavaScript
class ImageGallery {
constructor(containerId, images) {
this.container = document.getElementById(containerId);
this.images = images;
this.currentIndex = 0;
this.modal = document.getElementById('image-modal');
this.modalImage = document.getElementById('modal-image');
this.init();
}
init() {
this.renderGallery();
this.setupEventListeners();
}
renderGallery() {
this.images.forEach((image, index) => {
const imgElement = this.createImageElement(image, index);
this.container.appendChild(imgElement);
});
}
createImageElement(image, index) {
const div = document.createElement('div');
div.className = 'gallery-item';
const img = document.createElement('img');
img.src = image.thumbnail;
img.alt = image.alt;
img.loading = 'lazy';
img.addEventListener('click', () => this.openModal(index));
div.appendChild(img);
return div;
}
openModal(index) {
this.currentIndex = index;
this.modalImage.src = this.images[index].full;
this.modalImage.alt = this.images[index].alt;
this.modal.style.display = 'block';
document.body.style.overflow = 'hidden';
}
closeModal() {
this.modal.style.display = 'none';
document.body.style.overflow = 'auto';
}
nextImage() {
this.currentIndex = (this.currentIndex + 1) % this.images.length;
this.updateModalImage();
}
prevImage() {
this.currentIndex = this.currentIndex === 0 ?
this.images.length - 1 : this.currentIndex - 1;
this.updateModalImage();
}
updateModalImage() {
this.modalImage.src = this.images[this.currentIndex].full;
this.modalImage.alt = this.images[this.currentIndex].alt;
}
setupEventListeners() {
// 모달 닫기
document.querySelector('.close').addEventListener('click', () => {
this.closeModal();
});
// 배경 클릭으로 닫기
this.modal.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.closeModal();
}
});
// 네비게이션 버튼
document.getElementById('prev-btn').addEventListener('click', () => {
this.prevImage();
});
document.getElementById('next-btn').addEventListener('click', () => {
this.nextImage();
});
// 키보드 네비게이션
document.addEventListener('keydown', (e) => {
if (this.modal.style.display === 'block') {
switch(e.key) {
case 'Escape':
this.closeModal();
break;
case 'ArrowLeft':
this.prevImage();
break;
case 'ArrowRight':
this.nextImage();
break;
}
}
});
}
}
// 갤러리 초기화
const images = [
{ thumbnail: 'thumb1.jpg', full: 'full1.jpg', alt: '이미지 1' },
{ thumbnail: 'thumb2.jpg', full: 'full2.jpg', alt: '이미지 2' },
{ thumbnail: 'thumb3.jpg', full: 'full3.jpg', alt: '이미지 3' }
];
const gallery = new ImageGallery('gallery-grid', images);