DOM 제어하기
1. DOM 제어 명령어
DOM을 제어하는 것은 웹 페이지의 동적 콘텐츠를 생성하고 변경하는 데 필수적입니다. DOM 트리에 접근해서 가져온 요소에 수행할 수 있는 이벤트 추가, 클래스 제어, 요소 생성 및 삭제, 속성 제어 등 다양한 DOM 제어 방법을 살펴보겠습니다.
1.1. 이벤트 삽입
target.addEventListener(type, listener)
메서드를 사용하여 요소에 이벤트를 추가할 수 있습니다. 아래 코드는 버튼을 클릭할 때마다 콘솔에 'hello world'를 출력합니다. 여기서 click
은 이벤트 타입이고, function () { console.log('hello world'); }
는 리스너 함수(또는 이벤트 핸들러 함수)입니다. 이 함수는 이벤트가 발생했을 때 실행됩니다. 핸들러 함수
라는 용어는 실무에서 대화할 때에도 자주 등장하니 잘 기억해두세요.
HTML 파일을 만들어서 아래 코드를 실행해보고 콘솔에 출력되는 값을 확인해보세요.
<button>HELLO!</button>
<script>
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
console.log('Hello world');
});
</script>
<button>HELLO!</button>
<script>
const myBtn = document.querySelector('button');
myBtn.addEventListener('click', function () {
console.log('Hello world');
});
</script>
이벤트의 타입에는 click
, mouseover
, mouseout
, wheel
등 다양한 이벤트가 있습니다. 리스너 함수의 인수에는 이벤트에 대한 정보가 담겨 있습니다.
1.2 클래스 제어
classList
객체를 사용하여 요소의 클래스 속성을 제어할 수 있습니다. 토글은 클래스가 없으면 추가하고, 있으면 제거합니다. 아래 코드를 html 파일로 만들어 실행해보세요. 버튼을 클릭할 때마다 버튼 색상이 변경됩니다.
<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. 요소 제어
DOM API를 이용하면 요소를 새롭게 생성하고, 위치하고, 제거할 수 있습니다.
document.createElement(target)
: target 요소를 생성합니다.element.appendChild(target)
: target 요소를 element의 자식으로 위치합니다.element.append(target)
: target 요소를 element의 자식으로 위치합니다. appendChild 와 다른점은 노드 뿐만 아니라 여러개의 노드를 한번에, 그리고 텍스트도 자식 노드로 포함시킬 수 있다는것 입니다.element.removeChild(target)
: element의 target 자식 요소를 제거합니다.target.remove()
: target 요소를 제거합니다.
1.3.1. 요소 생성
document.createElement()
메서드를 사용하여 새로운 요소를 동적으로 생성할 수 있습니다. 이렇게 생성한 요소에 클래스를 추가하거나 이벤트를 등록할 수 있습니다. 생성한 요소를 화면에 출력하기 위해서는 요소를 document에 추가해주어야 합니다.
<ul></ul>
<button>Make me MORE!</button>
<script>
const myBtn = document.querySelector('button');
const myUl = document.querySelector('ul');
myBtn.addEventListener('click', function () {
for (let i = 0; i < 5; i++) {
const myLi = document.createElement('li');
myLi.textContent = `Item ${myUl.children.length + 1}`;
myUl.appendChild(myLi);
}
});
</script>
<ul></ul>
<button>Make me MORE!</button>
<script>
const myBtn = document.querySelector('button');
const myUl = document.querySelector('ul');
myBtn.addEventListener('click', function () {
for (let i = 0; i < 5; i++) {
const myLi = document.createElement('li');
myLi.textContent = `Item ${myUl.children.length + 1}`;
myUl.appendChild(myLi);
}
});
</script>
1.3.2. 요소 추가
createElement로 생성한 요소를 화면에 출력하기 위해서는 요소를 추가해주어야 합니다. 자바스크립트에서는 다양한 메서드를 이용하여 요소를 추가할 수 있습니다.
-
appendChild()
부모 노드에 마지막 요소로 자식 노드를 추가할 수 있습니다.const parent = document.querySelector('.parent'); const child = document.createElement('div'); parent.appendChild(child);
const parent = document.querySelector('.parent'); const child = document.createElement('div'); parent.appendChild(child);
-
append()
부모 노드에 여러 개의 요소를 추가할 때 사용합니다.const parent = document.querySelector('.parent'); const child1 = document.createElement('div'); const child2 = document.createElement('span'); parent.appendChild(child1, child2);
const parent = document.querySelector('.parent'); const child1 = document.createElement('div'); const child2 = document.createElement('span'); parent.appendChild(child1, child2);
-
insertBefore()
특정 위치의 앞에 노드를 추가할 수 있습니다. 두 번째 인수로 전달된 노드 앞에 첫 번째 인수로 전달된 노드를 추가합니다. 만약 두 번째 인수로null
값이 전달되면appendChild
처럼 동작하여 부모 요소의 마지막 자식 노드로 추가됩니다.const parent = document.querySelector('.parent'); const child = document.createElement('div'); const target = document.querySelector('.target'); parent.insertBefore(child, target);
const parent = document.querySelector('.parent'); const child = document.createElement('div'); const target = document.querySelector('.target'); parent.insertBefore(child, target);
-
insertAdjacentHTML()
insertAdjacentHTML()
메서드를 사용하여 요소 노드를 대상의 인접한 주변에 배치할 수 있습니다. 이 메서드는 HTML 문자열을 사용하여 요소를 추가할 수 있습니다.<strong class="target">-target-</strong> <script> const target = document.querySelector('.target'); target.insertAdjacentHTML('beforebegin', '<span>안녕하세요 저는</span>'); target.insertAdjacentHTML('afterbegin', '<span>재현입니다</span>'); target.insertAdjacentHTML('beforeend', '<span>면접오시면</span>'); target.insertAdjacentHTML('afterend', '<span>치킨사드릴게요</span>'); </script>
<strong class="target">-target-</strong> <script> const target = document.querySelector('.target'); target.insertAdjacentHTML('beforebegin', '<span>안녕하세요 저는</span>'); target.insertAdjacentHTML('afterbegin', '<span>재현입니다</span>'); target.insertAdjacentHTML('beforeend', '<span>면접오시면</span>'); target.insertAdjacentHTML('afterend', '<span>치킨사드릴게요</span>'); </script>
위치를 나타내는 값에서 begin
이란 여는 태그를, end
란 닫는 태그를 의미합니다.
1.3.3. 요소 제거
-
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); } });
또는 innerHTML을 이용하여 모든 자식 요소를 삭제할 수도 있습니다.
myUl.innerHTML = '';
myUl.innerHTML = '';
-
remove()
자식 요소가 아닌 요소 자체를 삭제하기 위해서는element.remove()
메서드를 사용할 수 있습니다.<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.lastElementChild.remove(); } });
<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.lastElementChild.remove(); } });
1.4 요소 값 제어
DOM API를 이용하면 요소 안의 값에 접근하여 값을 가져오거나, 변경할 수 있습니다. 아래 코드를 html 파일을 만들어 실행해보세요. 입력창에 값을 입력하고 버튼을 클릭하면 입력한 값이 p 태그에 출력됩니다. 주석을 해제하면 실시간으로 값이 반영됩니다.
1.4.1. innerHTML
innerHTML 은 요소(element) 내에 포함된 HTML 마크업을 가져오거나 설정합니다. 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을 사용할 때는 보안에 주의해야 합니다. 위의 코드처럼 사용자의 입력을 받아 문서를 변경할 때, 사용자가 입력한 값에 <script>
요소가 포함되어 있다면 악의적인 스크립트가 실행될 수 있습니다.
HTML Living Standard에서는 이러한 보안 이슈를 방지하기 위해 innerHTML을 통해 삽입된 <script>
요소는 실행하지 않도록 정의되어 있습니다.
그러나 <script>
를 사용하지 않고도 자바스크립트를 실행할 수 있는 방법이 존재합니다. 다음과 같은 코드를 innerHTML로 삽입하면 자바스크립트 코드가 실행됩니다. 앞서 실행했던 예시의 입력창에 다음 코드를 입력하고 버튼을 클릭해보세요.
<img src="" onerror='alert("Hello User 😈")' />
<img src="" onerror='alert("Hello User 😈")' />
innerHTML은 템플릿 리터럴과 함께 조합하여 복잡한 HTML 구조를 동적으로 생성할 수 있다는 장점이 있으나, 이러한 보안 위험을 방지하기 위해서 사용자 입력값을 사용할 때는 innerText나 textContent를 사용하는 것이 좋습니다.
innerHTML 사용시 주의 사항1.4.2. innerText
innerText는 요소의 렌더링된 텍스트 콘텐츠를 나타냅니다. HTML 태그를 제외한 순수한 텍스트만 반환하며, CSS에 의해 숨겨진 텍스트는 반환하지 않습니다.
<div id="example">
<p>
이것은 <strong>굵은</strong><br />
텍스트입니다.
</p>
<p style="display: none;">이 텍스트는 보이지 않습니다.</p>
</div>
<script>
const example = document.getElementById('example');
console.log(example.innerText);
</script>
<div id="example">
<p>
이것은 <strong>굵은</strong><br />
텍스트입니다.
</p>
<p style="display: none;">이 텍스트는 보이지 않습니다.</p>
</div>
<script>
const example = document.getElementById('example');
console.log(example.innerText);
</script>
1.4.3. textContent
textContent 속성은 노드의 텍스트 콘텐츠를 가져옵니다. innerText와 비슷하지만, textContent는 숨겨진 텍스트를 포함하여 요소의 모든 텍스트 콘텐츠를 반환합니다.
<div id="example">
이것은 <strong>굵은</strong> 텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const element = document.getElementById('example');
console.log(element.textContent);
// 출력:
// "이것은 굵은 텍스트입니다.
// 숨겨진 텍스트
// console.log('스크립트');"
</script>
<div id="example">
이것은 <strong>굵은</strong> 텍스트입니다.
<span style="display: none;">숨겨진 텍스트</span>
<script>
console.log('스크립트');
</script>
</div>
<script>
const element = document.getElementById('example');
console.log(element.textContent);
// 출력:
// "이것은 굵은 텍스트입니다.
// 숨겨진 텍스트
// console.log('스크립트');"
</script>
innerText 더 생각해보기
innerText 속성은 요소의 렌더링된 텍스트 콘텐츠를 나타낸다고 합니다. mdn 에 따르면 innerText는 사용자가 커서를 이용해 요소의 콘텐츠를 선택하고 클립보드에 복사했을 때 얻을 수 있는 텍스트의 근삿값을 제공한다고 합니다.
이 말은 즉, 예를 들어서 네이버 메인페이지의 특정 텍스트를 복사에서 메모장에 붙여넣기한다고 가정했을때, 텍스트의 줄 바꿈은 가져오지만 폰트의 굵기나 색상 등의 스타일 정보는 가져오지 않는 것과 같다고 할 수 있겠습니다.
따라서 innerText는 display
1.5 요소 탐색하기
요소 간의 관계를 이용하여 다양한 방법으로 요소에 접근할 수 있ㅅ브니다. DOM 트리를 효과적으로 순회하고 필요한 요소에 접근할 수 있습니다.
<article class="cont">
<h1>안녕하세요 저는 이런 사람입니다.</h1>
<p>지금부터 자기소개 올리겠습니다</p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt incidunt
voluptates laudantium fugit, omnis dolore itaque esse exercitationem quam
culpa praesentium, quisquam repudiandae aut. Molestias qui quas ea iure
officiis.
<strong>감사합니다!</strong>
</article>
<script>
const cont = document.querySelector('.cont');
console.log(cont.firstElementChild); // 첫번째 자식을 찾습니다.
console.log(cont.lastElementChild); // 마지막 자식을 찾습니다.
console.log(cont.nextElementSibling); // 다음 형제요소를 찾습니다.
console.log(cont.previousSibling); // 이전 형제노드를 찾습니다.
console.log(cont.children); // 모든 자식요소를 찾습니다.
console.log(cont.childNodes); // 모든 자식노드를 찾습니다.
console.log(cont.parentElement); // 부모 요소를 찾습니다.
// 자기 자신부터 시작해 부모로 타고 올라가며 조상 요소에서 가장 가까운 cont 클래스 요소를 찾습니다.
console.log(cont.querySelector('strong').closest('.cont').innerHTML);
</script>
<article class="cont">
<h1>안녕하세요 저는 이런 사람입니다.</h1>
<p>지금부터 자기소개 올리겠습니다</p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt incidunt
voluptates laudantium fugit, omnis dolore itaque esse exercitationem quam
culpa praesentium, quisquam repudiandae aut. Molestias qui quas ea iure
officiis.
<strong>감사합니다!</strong>
</article>
<script>
const cont = document.querySelector('.cont');
console.log(cont.firstElementChild); // 첫번째 자식을 찾습니다.
console.log(cont.lastElementChild); // 마지막 자식을 찾습니다.
console.log(cont.nextElementSibling); // 다음 형제요소를 찾습니다.
console.log(cont.previousSibling); // 이전 형제노드를 찾습니다.
console.log(cont.children); // 모든 자식요소를 찾습니다.
console.log(cont.childNodes); // 모든 자식노드를 찾습니다.
console.log(cont.parentElement); // 부모 요소를 찾습니다.
// 자기 자신부터 시작해 부모로 타고 올라가며 조상 요소에서 가장 가까운 cont 클래스 요소를 찾습니다.
console.log(cont.querySelector('strong').closest('.cont').innerHTML);
</script>
1.6 속성 제어하기
자바스크립트를 사용하여 요소의 속성을 제어하는 방법은 다양합니다. 몇 가지 방법을 알아보겠습니다.
- 요소의 스타일을 제어하는
style
객체 - 요소의 속성을 제어하는
setAttribute()
메서드 - 요소의 속성을 제거하는
removeAttribute()
메서드
1.6.1. style 객체
요소는 그 안에 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.6.2. attribute 제어
요소의 속성을 설정하기 위해서 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>
1.6.3. 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>