자바스크립트는 웹 문서 안에 있는 텍스트, 이미지, 표 등 모든 요소를 객체화시켜 조작 할 수 있다. 이를 문서 객체 모델, DOM(Document Object Model)이라고 한다. 웹 문서를 DOM으로 해석하여 웹 문서 안의 모든 요소를 제어할 수 있는 방법을 정리해 보려고 한다.
DOM을 조작하기 위해서는 문서의 구조를 먼저 파악해야 한다. 웹 문서의 구조는 나무처럼 생겼다. 부모 요소에서 가지를 뻗어 자식요소와 연결 된 것으로 이해하면 쉽다. 더불어 웹 문서의 구조를 명확히 알고 있어야 자바스크립트로 원하는 요소에 접근하고자 할 때 정리가 쉽다.
body
, h1
, 'contents'
등 웹 문서 안에 있는 요소나 속성을 '노드(Node)'라고 한다.body
와 h1
, p
는 부모 노드와 자식 노드의 관계이다.h1
, p
태그 노드 안의 텍스트 노드('Title', 'Contents')는 해당 요소의 자식 노드이다.DOM요소에 접근하여 자바스크립트를 활용하고자 할 때 CSS에서 사용했던 선택자(Selector)를 활용할 수 있다.
<body>
<h1 id="heading">제목</h1>
<p>
글을 쓸 때 강조하고 싶은 내용이 있다면
<span class="accent">다양한 방법</span>을 사용할 수 있다.
먼저 <span class="accent">폰트의 두께를 두껍게</span> 만드는 방법도 있고,
<span class="accent">글씨체를 두껍게</span> 설정할 수도 있다.
</p>
</body>
h1
태그에 접근하고자 할 때 아래와 같이 id 속성으로 접근할 수 있다.
document.getElementById("heading") // id="heading" 인 요소에 접근
위와 같이 접근 후 함수를 활용하여 접근한 요소에만 함수가 적용되게 할 수 있다.
document.getElementById("heading").onclick = function() {
this.stlye.color = "red" // 클릭 시 h1의 폰트 컬러를 "red"로 변경
}
id 선택자를 활용하여 요소에 접근한 것과 유사한 방식으로 class 선택자로도 요소에 접근할 수 있다.
document.getElementsByClassName("accent") // class="accent" 인 요소에 접근
위와 같이 class 요소로 접근할 경우 여러개의 요소들에 동시에 접근 되어 해당하는 요소들이 배열의 형태로 접근 된다. 즉, Array[index]
를 통해 원하는 요소에만 접근 할 수 있다는 것이다. 'accent'를 class로 갖는 요소를 출력하면 아래와 같다.
console.log(document.getElementsByClassName("accent"));
[span.accent, span.accent, span.accent]
0: <span class="accent">다양한 방법</span>
1: <span class="accent">폰트의 두께를 두껍게</span>
2: <span class="accent">글씨체를 두껍게</span>
class 전체에 밑줄을 긋고 첫 번째 요소만 폰트 사이즈를 4em으로 변경하고자 한다면 아래와 같이 작성하면 된다.
document.getElementsByClassName("accent").style.textDecoration = "underline";
document.getElementsByClassName("accent")[0].style.fontsize = "4em";
id나 class가 없는 경우 tag로 접근이 가능하며 id, class 선택자로 접근한 것과 동일하게 해당 요소의 변경 등이 가능하다.
document.getElementsByTagName("p").style.backgroundColor = "gray";
// <p> 요소에 접근하여 배경 컬러 'gray'로 변경
선택자에 관계없이 동일한 함수를 사용하여 DOM요소를 찾을 수 있는 것이 querySelector()
이다. 다만 선택자를 입력할 때 class는 '.', id는 '#'을 붙여 구분하여야 한다. 또한 class 선택자 처럼 같은 선택자를 가진 요소가 많다면 querySelectorAll()
을 사용하면 된다.
document.querySelector("p") // p태그 요소에 접근
document.querySelector("#heading") // id="heading" 요소에 접근
document.querySelecortAll(".accent") // class="accent"인 '모든' 요소에 접근
쓰임새는 같지만 접근이 가능한 범위가 다르다. getElement~()함수는 요소 노드까지만 접근이 가능하고 querySelector() 함수는 요소, 텍스트, 속성 노드까지 접근이 가능하다.
DOM 요소의 속성 노드에 접근하여 속성값을 가져오거나 수정하는 함수로 요소 뿐만 아니라 속성도 활용 할 수 있다.
<div id="main-img">
<img src="images/fall-image.jpg" alt="가을 풍경 이미지">
</div>
속성 값을 변경하기 위해 먼저 속성값을 가져올 때 사용하는 함수이다.
document.querySelector('#main-img > img').getAttribute("src")
// id="main-img" 내의 img 태그 요소를 선택하여 "src"속성을 가져옴
// "images/fall-image.jpg"이 결과값이 됨
속성 값을 변경하는 함수이다.
document.querySelector('#main-img > img').setAttribute("src","images/summer-image.jpg")
// id="main-img" 내의 img 태그 요소를 선택하여 "src"속의 값을 "images/summer-image.jpg"으로 변경
DOM에서 이벤트를 연결하는 방법엔 세 가지가 있다.
<div>
<img id="origin-pic" src="images/origin-pic.jpg" alt="" onclick="change()">
<!----이미지 클릭 시 change 함수 실행---->
</div>
<script>
let pic = document.querySelector('#pic');
function change() {
pic.src = "images/change-pic.jpg"; // 이미지 경로 변경
}
</script>
<div>
<img id="origin-pic" src="images/origin-pic.jpg" alt="">
</div>
<script>
let pic = document.querySelector('#pic');
pic.onclick = change;
function change() {
pic.src = "images/change-pic.jpg"; // 이미지 경로 변경
}
</script>
HTML 태그와 DOM 요소에 이벤트를 연결할 경우 한 요소에 한 이벤트만 연결할 수 있다는 단점을 보완 할 수 있는 함수이다.
DOM.addEventListener("type", event, capturing)
<div>
<img id="origin-pic" src="images/origin-pic.jpg" alt="">
</div>
<script>
let pic = document.querySelector('#pic');
pic.addEventListener("click", changePic, false);
pic.addEventListener("click", changeStyle, false);
// 클릭 시 changePic과 changeStyle 2개의 함수 실행
// pic.onclick = changePic, pic.onclick = changeStyle 로 함수 지정 시 하나의 이벤트 함수만 실행
function changePic() {
pic.src = "images/change-pic.jpg"; // 이미지 경로 변경
}
function changeStyle() {
pic.style.border = 5px solid #ccc;
}
</script>
온라인 쇼핑몰 등에서 상품 옵션(수량, 사이즈 등)을 선택하면 그 내용이 화면 아래에 표시 되는 것처럼 DOM으로 웹 문서 상에 없던 요소를 추가해서 화면에 표시할 수 있다.
- createElement() : 새로운 요소 노드 생성
- createTextNode() : 텍스트가 있을 경우 텍스트 노드 생성
- appendChild() : 텍스트 노드를 요소 노드에 자식 노드로 추가
- createAttribute() : 요소에 속성이 있을 경우 속성 노드 생성
- setAttributeNode() : 속성 노드를 요소 노드에 연결
- appendChild() : 새로 만든 요소 노드를 부모 노드에 추가
새로운 노드를 추가 할 때는 노드의 생성 뿐만 아니라 부모-자식 노드의 연결도 필요하다.
예를 들어 body
태그 안에 다음과 같은 요소, 텍스트, 속성을 추가한다면 아래와 같이 작성할 수 있다.
<h1 id="heading">문서 객체 모델</h1>
<script>
var newP = document.createElement("p") // p 요소 노드 생성
var newText = document.createTextNode("문서 객체 모델") // 텍스트 노드 생성
var newId = document.createAttribute("id") // id 속성 노드 생성
newId.value = "heading" // id 속성노드의 값 지정
document.body.appendChild(newP) // body의 자식노드로 p요소 노드 추가
newP.appendChild(newText) // p요소 노드의 자식노드로 텍스트 노드 추가
newP.setAttributeNode(newId) // p요소 노드의 자식노드로 id속성 노드 추가
</script>
특정 노드에 자식 노드가 있는지 확인하는 함수이다. 자식 노드가 있으면 true, 없으면 false를 반환한다.
document.querySelectorAll("div")[1].hasChildNodes()
// 노드리스트 중 2번 째 div 요소노드에 자식노드가 있는지 확인
childNodes
는 자식 노드에 접근하는 함수인데 이때 요소 노드 뿐만 아니라 태그와 태그 사이의 줄바꿈도 빈 텍스트 노드인 자식 노드로 인식하여 NodeList를 만든다. children
을 사용하면 요소 노드만 가져와 HTMLCollection이라는 자료형을 만든다.
<div id="userList">
<p>AAA</p>
<p>BBB</p>
<p>CCC</p>
</div>
<script>
document.querySelector('#userList').childNodes
// NodeList(7) [text, p, text, p, text, p, text]
// <div> 다음의 줄바꿈, <p>태그 사이의 줄바꿈, </div> 앞의 줄 바꿈을 노드로 인식
document.querySelector('userList').children
// HTMLCollection(3) [p, p, p]
</script>
** 노드리스트 접근
여러개의 노드를 한꺼번에 가져와 배열형식에 여러 값을 저장하듯 여러 노드가 하나의 변수에 저장되는 것을 말한다. 노드 리스트는 특정 위치의 노드에 접근하기 위해 인덱스 값과 함께 많이 쓰인다.
document.querySelectorAll("div")
// NodeList(3) [div, div, div]
document.querySelectorAll("div")[1]
// 노드리스트 중 2번 째 div 값에 접근한다.
appendChild()
함수는 부모 노드에 자식 노드가 있을 경우 새로운 노드를 마지막 노드로 추가한다. 하지만 insertBefore()
함수는 자식 노드를 추가할 기준 노드를 지정하고 그 앞에 자식 노드를 추가한다.
<div id="userList">
<p>AAA</p>
<p>BBB</p>
<p>CCC</p>
</div>
<script>
let userList = document.querySelector('#userList')
userList.inserBefore(userList.children[2], userList.children[1])
// userList의 3번째 자식 요소를 2번째 자식 요소 앞에 추가
// userList가 ['AAA', 'CCC', 'BBB']로 변경 됨
</script>
removeChild()
함수를 실행하면 자식노드를 삭제 할 수 있다. 이 때, 노드는 스스로 삭제할 수 없기 때문에 부모 노드에 접근해서 삭제해야 한다. 혹시 부모 요소가 무엇인지 모른다면 먼저 parentNode
속성으로 부모 노드를 찾을 수 있다.
<div id="userList">
<p>AAA</p>
<p>BBB</p>
<p>CCC</p>
</div>
<script>
document.querySelectorAll('p')[0].parentNode
// p요소는 div요소가 부모 요소라는 것을 알 수 있다.
</script>
<div id="userList">
<p>AAA</p>
<p>BBB</p>
<p>CCC</p>
</div>
<script>
const del1 = document.querySelectorAll('p')[0] // 첫 번째 p요소
const userList = document.querySelector('#userList')
userList.removeChild(del1) // div요소에 있는 첫 번째 p요소 삭제 후 삭제 된 요소 반환
</script>