DOM 제어하기
1. 이벤트 기초
1.1 이벤트 등록
DOM 요소에는 이벤트를 등록할 수 있습니다. 이벤트는 사용자의 행동을 감지하여 특정 작업을 수행하는 기능입니다. 자바스크립트에서는 addEventListener()
메서드를 사용하여 이벤트를 추가할 수 있습니다. 이 메서드는 두 개의 인수를 받습니다. 첫 번째 인수는 이벤트 타입(예: click
, mouseover
, keydown
등)이고, 두 번째 인수는 이벤트가 발생했을 때 실행될 함수(리스너 함수 또는 핸들러 함수)입니다.
target.addEventListener(type, listener);
target.addEventListener(type, listener);
<button>Click me!</button>
<script>
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
alert('Hello World!');
});
</script>
<button>Click me!</button>
<script>
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
alert('Hello World!');
});
</script>
이벤트 타입
이벤트 타입은 사용자가 웹 페이지에서 발생시키는 다양한 행동을 나타냅니다. 자주 사용되는 이벤트 타입은 다음과 같습니다.
이벤트 타입 | 설명 |
---|---|
click | 마우스 클릭 이벤트 |
dblclick | 마우스 더블 클릭 이벤트 |
mousedown | 마우스 버튼을 누를 때 발생하는 이벤트 |
mouseup | 마우스 버튼을 뗄 때 발생하는 이벤트 |
mousemove | |
mouseover | 마우스가 요소에 진입했을 때 발생하는 이벤트 |
mouseout | 마우스가 요소에서 벗어났을 때 발생하는 이벤트 |
mouseenter | 마우스가 요소에 진입했을 때 발생하는 이벤트 |
mouseleave | 마우스가 요소에서 벗어났을 때 발생하는 이벤트 |
contextmenu | 마우스 오른쪽 버튼 클릭 시 컨텍스트 메뉴가 열릴 때 발생하는 이벤트 |
keydown | 키보드 키를 누를 때 발생하는 이벤트 |
keyup | 키보드 키를 뗄 때 발생하는 이벤트 |
submit | |
input | 입력 필드의 값이 변경될 때 발생하는 이벤트 |
change | 입력 필드의 값이 변경될 때 발생하는 이벤트 |
focus | 입력 필드에 포커스가 갈 때 발생하는 이벤트 |
blur | 입력 필드에서 포커스가 벗어날 때 발생하는 이벤트 |
load | 페이지가 로드될 때 발생하는 이벤트 |
resize | 브라우저 창 크기가 변경될 때 발생하는 이벤트 |
scroll | 페이지가 스크롤될 때 발생하는 이벤트 |
1.2 클래스 제어
classList
객체를 사용하여 요소의 클래스 속성을 제어할 수 있습니다. 클래스 별로 스타일을 적용할 수 있기 때문에, 자바스크립트를 사용하여 동적으로 클래스를 추가하거나 제거하여 스타일을 변경할 수 있습니다.
add(className)
: 클래스를 추가합니다.remove(className)
: 클래스를 제거합니다.toggle(className)
: 클래스가 있으면 제거하고, 없으면 추가합니다.contains(className)
: 클래스가 있는지 확인합니다.
<style>
.blue {
background-color: blue;
color: white;
}
</style>
<button>Make me BLUE!</button>
<script>
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
// myBtn.classList.add("blue"); // 클래스를 추가합니다.
// myBtn.classList.remove("blue"); // 클래스를 제거합니다.
myBtn.classList.toggle('blue'); // 클래스를 토글합니다.
console.log(myBtn.classList.contains('blue')); // 클래스가 있는지 확인합니다.
});
</script>
<style>
.blue {
background-color: blue;
color: white;
}
</style>
<button>Make me BLUE!</button>
<script>
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
// myBtn.classList.add("blue"); // 클래스를 추가합니다.
// myBtn.classList.remove("blue"); // 클래스를 제거합니다.
myBtn.classList.toggle('blue'); // 클래스를 토글합니다.
console.log(myBtn.classList.contains('blue')); // 클래스가 있는지 확인합니다.
});
</script>
1.3 스타일 제어
요소는 그 안에 CSSStyleDeclaration 객체라 불리는 style 객체가 존재합니다. 이 객체는 요소의 스타일 정보를 가지고 있으며, 스타일과 관련한 프로퍼티와 메소드를 지원합니다.
<p>Hello World!</p>
<button>Change Style</button>
<script>
const myP = document.querySelector('p');
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
myP.style.color = 'red'; // 현재 스타일 정보를 변경합니다.
myP.style.fontWeight = 'bold'; // 현재 스타일 정보에 font-weight 속성이 없다면 추가합니다.
});
// 현재 스타일 정보를 가져옵니다.
// const txtColor = myP.style.color;
// console.log(txtColor); // 빈 문자열 (초기 상태에서는 스타일이 직접 설정되지 않았기 때문)
// 현재 스타일 정보를 제거(초기화)합니다.
// myP.style.color = null;
</script>
<p>Hello World!</p>
<button>Change Style</button>
<script>
const myP = document.querySelector('p');
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
myP.style.color = 'red'; // 현재 스타일 정보를 변경합니다.
myP.style.fontWeight = 'bold'; // 현재 스타일 정보에 font-weight 속성이 없다면 추가합니다.
});
// 현재 스타일 정보를 가져옵니다.
// const txtColor = myP.style.color;
// console.log(txtColor); // 빈 문자열 (초기 상태에서는 스타일이 직접 설정되지 않았기 때문)
// 현재 스타일 정보를 제거(초기화)합니다.
// myP.style.color = null;
</script>
style 객체의 속성 식별자 규칙
- 속성 이름이 한 글자라면 그대로 사용합니다. (
height
,color
등) - 속성 이름이 대쉬(-) 를 통해 여러 단어로 나눠져있는 경우는 카멜케이스로 사용합니다. (
background-image
⇒backgroundImage
) float
속성의 경우 이미 자바스크립트의 예약어로 존재하기 때문에cssFloat
으로 사용됩니다.
style 객체를 통해 설정된 스타일은 CSS inline 스타일과 동일한 가중치를 가집니다. 때문에 CSS를 통해 수정의 여지가 있는 스타일에는 많이 사용되지 않는 편입니다. 이럴경우 classList를 이용한 클래스 제어가 더 효과적입니다.
1.4 속성 제어
요소의 속성을 설정하기 위해서 setAttribute()
메서드를 사용할 수 있습니다. 아래 코드를 html 파일로 만들어 실행해보세요. 버튼을 클릭할 때마다 이미지의 속성이 변경됩니다. 속성을 제거할 때는 removeAttribute()
메서드에 제거하고자 하는 속성명을 전달합니다.
이미지가 로딩되는 시간이 있기 때문에 이미지가 변경되는 것을 확인하기 위해서는 버튼을 바로 누르지 마시고 로딩이 끝난 후 버튼을 클릭해주세요.
<button>Change Image</button>
<img
src="https://paullab.co.kr/images/weniv-licat.png"
alt="라이캣"
id="image"
/>
<script>
const myImg = document.getElementById('image');
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
myImg.setAttribute('src', 'https://paullab.co.kr/images/weniv-soulgom.png');
myImg.setAttribute('alt', '소울곰');
});
// alt 속성 제거
// myImg.removeAttribute('alt');
</script>
<button>Change Image</button>
<img
src="https://paullab.co.kr/images/weniv-licat.png"
alt="라이캣"
id="image"
/>
<script>
const myImg = document.getElementById('image');
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
myImg.setAttribute('src', 'https://paullab.co.kr/images/weniv-soulgom.png');
myImg.setAttribute('alt', '소울곰');
});
// alt 속성 제거
// myImg.removeAttribute('alt');
</script>
data 속성
data-* 속성을 사용하면 HTML 요소에 추가적인 정보를 저장하여 마치 프로그램 가능한 객체처럼 사용할 수 있게 합니다. 단, data 속성의 이름에는 콜론(:) 이나 영문 대문자가 들어가서는 안됩니다. 자바스크립트로 data 속성을 가져오기 위해서는 dataset
객체를 사용합니다.
<img
class="terran battle-cruiser"
src="battle-cruiser.png"
data-ship-id="324"
data-weapons="laser"
data-health="400"
data-mana="250"
/>
<script>
const img = document.querySelector('img');
console.log(img.dataset);
console.log(img.dataset.shipId);
</script>
<img
class="terran battle-cruiser"
src="battle-cruiser.png"
data-ship-id="324"
data-weapons="laser"
data-health="400"
data-mana="250"
/>
<script>
const img = document.querySelector('img');
console.log(img.dataset);
console.log(img.dataset.shipId);
</script>
1.5 콘텐츠 수정
innerHTML
innerHTML은 요소 내에 포함된 HTML 마크업을 가져오거나 설정합니다.
<div id="example">
이것은 <strong>굵은</strong> 텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const example = document.getElementById('example');
console.log(example.innerHTML);
</script>
<div id="example">
이것은 <strong>굵은</strong> 텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const example = document.getElementById('example');
console.log(example.innerHTML);
</script>
innerHTML에 값을 할당했을 때 마크업으로 변환할 수 있는 문자열이 있으면 실제 요소로 렌더링하여 화면에 출력합니다.
<input type="text" id="inp-text" />
<button type="button" class="submit-btn">요소 추가하기</button>
<ul id="result"></ul>
<script>
const result = document.getElementById('result');
const inpText = document.getElementById('inp-text');
const submitBtn = document.querySelector('.submit-btn');
submitBtn.addEventListener('click', () => {
result.innerHTML += `<li>${inpText.value}</li>`;
});
</script>
<input type="text" id="inp-text" />
<button type="button" class="submit-btn">요소 추가하기</button>
<ul id="result"></ul>
<script>
const result = document.getElementById('result');
const inpText = document.getElementById('inp-text');
const submitBtn = document.querySelector('.submit-btn');
submitBtn.addEventListener('click', () => {
result.innerHTML += `<li>${inpText.value}</li>`;
});
</script>
innerHTML을 사용하면 HTML 마크업을 문자열로 작성하여 요소에 삽입할 수 있습니다. 위의 예시에서는 사용자가 입력한 값을 <li>
태그로 감싸서 result
요소에 추가합니다. 이때, innerHTML
은 기존의 HTML 마크업을 덮어쓰지 않고, 새로운 마크업을 추가합니다.
이처럼 입력된 문자열을 HTML로 파싱하기 때문에 신뢰할 수 없는 사용자의 데이터가 삽입되어 악성 스크립트가 실행될 위험이 있습니다. HTML Living Standard에서는 innerHTML을 통해 삽입된 <script>
요소는 실행하지 않도록 정의되어 있습니다. 하지만 다음과 같은 방법으로도 자바스크립트를 실행할 수 있습니다. 아래 코드를 입력하고 버튼을 클릭해보세요.
<img src="" onerror='alert("Hello User 😈")' />
<img src="" onerror='alert("Hello User 😈")' />
템플릿 리터럴과 함께 조합하여 복잡한 HTML 구조를 동적으로 생성할 수 있다는 장점이 있으나 보안 상의 위험을 방지하기 위해서 innerText 또는 textContent를 사용하는 것을 권장합니다.
innerHTML 사용시 주의 사항innerText
innerText는 요소의 렌더링된 텍스트 콘텐츠를 나타냅니다. 즉, 화면에 실제로 표시되는 모든 텍스트 콘텐츠를 표현합니다. HTML 태그를 제외한 순수한 텍스트만 반환하며, CSS에 의해 숨겨진 텍스트는 반환하지 않습니다. innerText와 textContent는 할당된 값은 순수 텍스트로 처리됩니다.
<div id="example">
이것은 <strong>굵은</strong> <br />텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const example = document.getElementById('example');
console.log(example.innerText);
</scrip>
<div id="example">
이것은 <strong>굵은</strong> <br />텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const example = document.getElementById('example');
console.log(example.innerText);
</scrip>
textContent
textContent 속성은 노드의 모든 텍스트 콘텐츠를 가져옵니다. innerText와 비슷하지만, textContent는 화면 렌더링과 상관없이 숨겨진 텍스트를 포함하여 요소의 모든 텍스트 콘텐츠를 반환합니다.
<div id="example">
이것은 <strong>굵은</strong> <br />텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const element = document.getElementById('example');
console.log(element.textContent);
</script>
<div id="example">
이것은 <strong>굵은</strong> <br />텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const element = document.getElementById('example');
console.log(element.textContent);
</script>
1.6 요소 생성 및 조작
createElement
createElement(tagName)
메서드를 사용하여 새로운 요소를 생성할 수 있습니다. 이 메서드는 HTML 태그 이름을 인수로 받아 해당 태그의 요소를 생성합니다. 생성된 요소는 DOM에 추가되기 전까지는 화면에 표시되지 않습니다.
<input type="text" id="inp-text" />
<button type="button" class="submit-btn">요소 추가하기</button>
<ul id="result"></ul>
<input type="text" id="inp-text" />
<button type="button" class="submit-btn">요소 추가하기</button>
<ul id="result"></ul>
const result = document.getElementById('result');
const inpText = document.getElementById('inp-text');
const submitBtn = document.querySelector('.submit-btn');
submitBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = inpText.value;
result.appendChild(li);
});
const result = document.getElementById('result');
const inpText = document.getElementById('inp-text');
const submitBtn = document.querySelector('.submit-btn');
submitBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = inpText.value;
result.appendChild(li);
});
appendChild
appendChild()
메서드를 사용하여 생성된 요소를 DOM에 추가할 수 있습니다. 이 메서드는 부모 요소의 마지막에 자식 요소를 추가합니다. appendChild()
메서드는 추가된 요소를 반환합니다.
submitBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = inpText.value;
result.appendChild(li);
});
submitBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = inpText.value;
result.appendChild(li);
});
append
여러 개의 요소를 추가할 때는 append()
메서드를 사용할 수 있습니다. 이 메서드는 여러 개의 요소를 한 번에 추가할 수 있습니다.
submitBtn.addEventListener('click', () => {
const li1 = document.createElement('li');
li1.textContent = inpText.value;
const li2 = document.createElement('li');
li2.textContent = '추가된 리스트 아이템';
result.append(li1, li2);
});
submitBtn.addEventListener('click', () => {
const li1 = document.createElement('li');
li1.textContent = inpText.value;
const li2 = document.createElement('li');
li2.textContent = '추가된 리스트 아이템';
result.append(li1, li2);
});
insertBefore
특정 위치에 요소를 추가할 수도 있습니다. insertBefore()
메서드를 사용하여 특정 요소 앞에 새로운 요소를 추가할 수 있습니다. 이 메서드는 두 개의 인수를 받습니다. 첫 번째 인수는 추가할 요소이고, 두 번째 인수는 기준이 되는 요소입니다. 두 번째 인수가 null이면 appendChild()
처럼 동작하여 부모 요소의 마지막 자식 노드로 추가됩니다.
<input type="text" id="inp-text" />
<button type="button" class="submit-btn">요소 추가하기</button>
<ul id="result">
<li class="target">📌 기준 아이템</li>
</ul>
<input type="text" id="inp-text" />
<button type="button" class="submit-btn">요소 추가하기</button>
<ul id="result">
<li class="target">📌 기준 아이템</li>
</ul>
const result = document.getElementById('result');
const inpText = document.getElementById('inp-text');
const submitBtn = document.querySelector('.submit-btn');
const targetNode = result.querySelector('li.target');
submitBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = inpText.value;
result.insertBefore(li, targetNode);
});
const result = document.getElementById('result');
const inpText = document.getElementById('inp-text');
const submitBtn = document.querySelector('.submit-btn');
const targetNode = result.querySelector('li.target');
submitBtn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = inpText.value;
result.insertBefore(li, targetNode);
});
insertAdjacentHTML
inserAdjacentHTML()
을 사용하여 특정 위치에 요소를 추가할 수 있습니다. 첫 번째 인수는 삽입할 위치를 나타내는 문자열이고, 두 번째 인수는 삽입할 HTML 문자열입니다.
target.insertAdjacentHTML('beforebegin', newElement);
target.insertAdjacentHTML('beforebegin', newElement);
| 위치 | 설명 | | beforebegin| 시작 태그의 바로 앞에 삽입 | | afterbegin | 시작 태그의 바로 뒤에 삽입 | | beforeend | 끝 태그의 바로 앞에 삽입 | | afterend | 끝 태그의 바로 뒤에 삽입 |
<p id="target">--target--</p>
<script>
const target = document.getElementById('target');
target.insertAdjacentHTML('beforebegin', '<span>안녕하세요</span>');
target.insertAdjacentHTML('afterbegin', '<span>위니브입니다.</span>');
target.insertAdjacentHTML('beforeend', '<span>제주도로</span>');
target.insertAdjacentHTML('afterend', '<span>놀러오세요!</span>');
</script>
<p id="target">--target--</p>
<script>
const target = document.getElementById('target');
target.insertAdjacentHTML('beforebegin', '<span>안녕하세요</span>');
target.insertAdjacentHTML('afterbegin', '<span>위니브입니다.</span>');
target.insertAdjacentHTML('beforeend', '<span>제주도로</span>');
target.insertAdjacentHTML('afterend', '<span>놀러오세요!</span>');
</script>
DocumentFagment
DOM 요소에 여러 개의 자식 요소를 추가할 때, DocumentFragment
를 사용하면 성능을 향상시킬 수 있습니다. DocumentFragment
는 메모리 상에 존재하는 가상의 DOM 노드입니다. 이 노드는 실제 DOM에 추가되지 않으며, 여러 개의 요소를 그룹화하여 한 번에 DOM에 추가할 수 있습니다.
<ul id="result"></ul>
<script>
const members = ['라이캣', '빙키', '뮤라', '소울곰'];
const ul = document.getElementById('result');
members.forEach((member) => {
const li = document.createElement('li');
li.textContent = member;
ul.appendChild(li);
});
</script>
<ul id="result"></ul>
<script>
const members = ['라이캣', '빙키', '뮤라', '소울곰'];
const ul = document.getElementById('result');
members.forEach((member) => {
const li = document.createElement('li');
li.textContent = member;
ul.appendChild(li);
});
</script>
위와 같이 반복적으로 DOM에 요소를 추가할 경우 성능이 저하될 수 있습니다. 대신 DocumentFragment
를 사용하여 성능을 향상시킬 수 있습니다.
const members = ['라이캣', '빙키', '뮤라', '소울곰'];
const ul = document.getElementById('result');
const fragment = document.createDocumentFragment();
members.forEach((member) => {
const li = document.createElement('li');
li.textContent = member;
fragment.appendChild(li);
});
ul.appendChild(fragment);
const members = ['라이캣', '빙키', '뮤라', '소울곰'];
const ul = document.getElementById('result');
const fragment = document.createDocumentFragment();
members.forEach((member) => {
const li = document.createElement('li');
li.textContent = member;
fragment.appendChild(li);
});
ul.appendChild(fragment);
1.7 요소 제거하기
removeChild
element.removeChild()
메서드를 사용하여 부모 노드에서 자식 노드를 제거할 수 있습니다. 아래 코드를 html 파일로 만들어 실행해보세요. 버튼을 클릭할 때마다 마지막 리스트 아이템이 제거됩니다.
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
</ul>
<button id="remove">Remove Last Item</button>
<script>
const removeBtn = document.getElementById('remove');
const myUl = document.querySelector('ul');
removeBtn.addEventListener('click', function () {
if (myUl.lastElementChild) {
myUl.removeChild(myUl.lastElementChild);
}
});
</script>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
</ul>
<button id="remove">Remove Last Item</button>
<script>
const removeBtn = document.getElementById('remove');
const myUl = document.querySelector('ul');
removeBtn.addEventListener('click', function () {
if (myUl.lastElementChild) {
myUl.removeChild(myUl.lastElementChild);
}
});
</script>
모든 자식 요소를 삭제하고자 할 때, 반복문을 활용할 수 있습니다.
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
</ul>
<button id="remove">Remove All Items</button>
<script>
const removeBtn = document.getElementById('remove');
const myUl = document.querySelector('ul');
removeBtn.addEventListener('click', function () {
while (myUl.firstChild) {
myUl.removeChild(myUl.firstChild);
}
});
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
</ul>
<button id="remove">Remove All Items</button>
<script>
const removeBtn = document.getElementById('remove');
const myUl = document.querySelector('ul');
removeBtn.addEventListener('click', function () {
while (myUl.firstChild) {
myUl.removeChild(myUl.firstChild);
}
});
removeChild() 메서드 사용 시 주의 사항
removeChild()
메서드는 부모 요소에서 자식 요소를 제거할 때 사용됩니다. 이 메서드는 제거된 요소를 반환합니다. 만약 제거하려는 요소가 부모 요소의 자식이 아니라면 오류가 발생합니다. 따라서, removeChild()
메서드를 사용할 때는 반드시 해당 요소가 부모 요소의 자식인지 확인해야 합니다.
innerHTML
innerHTML을 이용하여 모든 자식 요소를 삭제할 수도 있습니다.
myUl.innerHTML = '';
myUl.innerHTML = '';
remove()
메서드를 사용하여 요소 자체를 제거할 수 있습니다. 이 메서드는 요소를 DOM에서 제거합니다.
const result = document.getElementById('result');
result.remove();
const result = document.getElementById('result');
result.remove();