본 포스팅은 여기에 올라온 게시글을 바탕으로 작성되었습니다.
파트와 카테고리 동일한 순서로 모든 내용을 소개하는 것이 아닌, 몰랐거나 새로운 내용 위주로 다시 정리하여 개인공부 목적으로 작성합니다.
중간중간 개인 판단 하에 필요하다고 생각될 시, 기존 내용에 추가로 보충되는 내용이 있을 수 있습니다.
브라우저는 HTML 문서를 파싱하여 DOM
객체를 생성한다. 앞서 요소 노드의 대부분은 표준 HTML 속성(attribute
)을 DOM
객체의 프로퍼티로 가지는 것을 살펴보았다.
이쯤에서 용어를 다시 한 번 재정립하고 넘어가자. 프로퍼티(property
)는 속성으로 번역되기도 하는데 해당 파트에서는 프로퍼티와 속성을 구분한다.
속성(attribute
) : HTML 표준 속성으로 id
, class
와 같은 속성. 태그마다 고유한 속성을 가지는 경우도 있음.
프로퍼티(property
) : HTML을 파싱해 DOM
객체로 생성할 때 각 객체가 가지고 있는 프로퍼티. 대부분 HTML 표준 속성과 동일하지만 다른 경우도 있음.
이번 챕터에서는 속성과 프로퍼티가 어떤 연관관계를 가지고 있고, 어떤 상황에서 일대일로 매핑이 되는지, 또 언제 매핑되지 않는지에 대해 살펴보도록 하자.
앞서 내장 DOM
프로퍼티를 살펴보며 많은 종류가 있다는 것을 보았다. 만약 이런 내장 프로퍼티만으로 충분하다고 판단되지 않는 경우엔 자신만의 커스텀 프로퍼티를 만들 수도 있다. DOM
은 결국 자바스크립트 객체이기 때문에 객체 프로퍼티를 추가하는 방식으로 얼마든지 설정이 가능하다.
document.body.myData = {
name: "Caesar',
title: "Imperator",
};
console.log( document.body.myData.title ); // Imperator
물론 메서드 역시 추가할 수 있다.
document.body.sayTagName = function() {
console.log(this.tagName);
}
document.body.sayTagName(); // BODY
DOM
프로퍼티로 지정되는 값은 대소문자를 구분하는 것에 주의하자. 이는 앞서 객체 파트에서부터 다뤄온 내용이기에 크게 낯설지 않을 것이다.
HTML에서 태그는 복수의 속성을 가질 수 있다. 브라우저는 이러한 HTML을 파싱해서 DOM
객체를 만들 때 HTML 표준 속성을 인식하고, 이 표준 속성을 사용해 DOM
프로퍼티를 만들게 된다.
따라서 요소가 id
와 같은 표준 속성으로만 구성되어 있는 경우엔 이에 해당되는 프로퍼티 역시 자동으로 생성된다. 그러나 표준이 아닌 속성이 사용된 경우에는 프로퍼티로 전환되지 않는다.
<body id='test' something='non-standard'>
<script>
console.log(document.body.id); // test
console.log(document.body.something); // undefined
</script>
</body>
또한 한 요소에서는 표준 속성이지만, 다른 요소에서는 표준이 아닌 경우도 있다. 예를 들어 type
속성은 <input>
요소에서는 표준 속성이지만 다른 태그에서는 표준이 아니다.
이처럼 표준 속성이 아닌 경우엔 이에 대해 일대일로 매핑되는 DOM
프로퍼티가 생성되지 않는다. 그렇지만 DOM
객체에서는 비표준 속성에 접근할 수 있는 메서드를 제공한다.
elem.hasAttribute(name)
: 속성 존재 여부 확인elem.getAttribute(name)
: 속성 값 접근elem.setAttribute(name, value)
: 속성 값 설정elem.removeAttribute(name)
: 속성 값 제거elem.attributes
: 모든 속성 값을 내장 클래스 Attr
형태로 반환<body something='non-standard'>
<script>
console.log(document.body.getAttribute('something');
</script>
</body>
이때 HTML 속성은 DOM
프로퍼티와 달리 다음 특징을 보인다.
id
와 ID
는 동일한 값에 접근한다.표준 속성이 갱신되는 경우엔 대응하는 DOM
프로퍼티 역시 자동으로 갱신된다. 몇몇 경우를 제외하곤 프로퍼티가 변하는 경우에도 속성 역시 마찬가지로 같이 반영된다.
<input />
<script>
let input = document.querySelector('input');
// 속성 추가 => 프로퍼티 동시 갱신
input.setAttribute('id', 'id');
console.log(input.id);
// 프로퍼티 변경 => 속성 동시 갱신
input.id = 'newId';
console.log(input.getAttribute('id'));
</script>
하지만 input.value
와 같은 일부 프로퍼티는 동기화가 속성에서 프로퍼티 방향으로만 일어나는 예외상황도 존재한다.
<input />
<script>
let input = document.querySelector('input');
// 속성 추가 => 프로퍼티 동시 갱신
input.setAttribute('value', 'text');
console.log(input.value); // text
// 프로퍼티 갱신 => 그러나 속성은 변경되지 않음
input.value = 'newValue';
console.log(input.getAttribute('value')); // text
</script>
이런 기능은 간혹 유용하게 사용될 수 있다. 유저의 어떤 행동으로 인해 value
가 수정되었을 때 수정 전의 원래 값으로 복구하고 싶은 경우에 속성에 저장된 값을 가지고 오는 식으로 복구할 수 있기 때문이다.
DOM
프로퍼티는 HTML 속성과 달리 항상 문자열이 아니다. 예를 들어 체크 박스 타입에서 사용되는 input.checked
프로퍼티의 경우 Boolean
타입의 값을 가지고 있다.
<input id="input" type="checkbox" checked> checkbox
<script>
alert(input.getAttribute('checked')); // 속성 값: 빈 문자열
alert(input.checked); // 프로퍼티 값: true
</script>
또한 DOM
객체의 style
프로퍼티 역시 문자열이 아닌 객체를 값으로 가지고 있다.
<div id="div" style="color:red; font-size:120%">Hello</div>
<script>
// string
alert(div.getAttribute('style')); // color:red;font-size:120%
// object
alert(div.style); // [object CSSStyleDeclaration]
alert(div.style.color); // red
</script>
그렇지만 대부분의 프로퍼티 값은 HTML 속성과 같이 문자열을 가진다. 이때 동일하게 문자열을 공유하더라도 속성과 프로퍼티의 값이 다른 경우도 있다. 예를 들어 <a>
태그의 href
속성이 그러한 경우이다. href
속성이 상대 URL 주소이거나 #hash
처럼 해시링크를 이용하는 경우에도 DOM
객체의 href
프로퍼티는 항상 URL 전체가 저장된다.
<a id="a" href="#hello">link</a>
<script>
// 속성
alert(a.getAttribute('href')); // #hello
// 프로퍼티
alert(a.href );
// 실행되는 사이트 주소에 따라 http://site.com/page#hello 형태로 값이 저장
</script>
HTML을 작성할 때는 대부분 표준 속성을 이용하여 작성한다. 그렇지만 표준이 아닌 속성도 존재하고, 비표준 속성은 개발자가 임의로 정의하여 사용할 수 있다.
이러한 비표준 속성은 보통 사용자가 직접 지정한 데이터를 HTML에서 자바스크립트로 바로 넘기고 싶은 경우나 자바스크립트를 사용해 조작할 HTML 요소를 표시하기 위해 사용한다.
<!-- 이름(name) 정보를 보여주는 div라고 표시 -->
<div show-info='name'></div>
<!-- 나이(age) 정보를 보여주는 div라고 표시 -->
<div show-info='age'></div>
<script>
let user = {
name: 'KG',
age: 27,
};
for (let div of document.querySelectorAll('[show-info]')) {
let field = div.getAttribute('show-info');
div.innerHTML = user[field];
// show-info 값을 기준으로 name엔 'KG', age엔 27이 삽입
}
</script>
또한 비표준 속성은 요소에 스타일을 적용할 때도 사용이 가능하다.
<style>
.order[order-state="new"] {
color: green;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
A new order.
</div>
<div class="order" order-state="pending">
A pending order.
</div>
<div class="order" order-state="canceled">
A canceled order.
</div>
이렇게 커스텀 속성을 사용하는 것이 클래스를 통해 일일이 구분된 클래스명을 사용하는 것보다 더 선호되곤 한다. 왜냐하면 속성이 클래스보다 더 다루기 간편하기 때문이다. 속성의 상태는 새 클래스를 추가 또는 제거하는 것 보다 제공되는 메서드를 통해 손쉽게 변경할 수 있다. 물론 클래스를 통해 이러한 설정을 적용하는 것 역시 다른 메서드를 통해 가능하다.
div.setAttribute('order-state', 'canceled');
커스텀 속성에는 근본적으로 한 가지 문제점이 대두되는데, 바로 개발자가 임의로 지정한 속성이 추후 표준으로 등재될 수 있다는 점이다. HTML은 계속해서 표준안이 개정되기 때문에 내가 사용한 커스텀 속성이 언제든지 표준으로 변경될 가능성이 있다. 이런 경우에는 예기치 못한 부작용이 발생할 수 있다.
따라서 이런 충돌 상황을 방지하기 위해 HTML에는 기본적으로 data-*
라는 속성을 제공한다. 이 속성은 개발자가 용도에 맞게 사용할 수 있도록 별도로 예약된 키워드이다. 그리고 해당 속성은 DOM
객체에서 dataset
프로퍼티로 접근할 수 있다.
<body data-about='Elephants'>
<script>
console.log(document.body.dataset.about); // Elephants
</script>
</body>
먄약 data-order-state
와 같이 여러 단어로 구성된 속성은 자바스크립트 객체 내 프로퍼티에서는 카멜 표기법(Camel Case
)로 자동으로 변환된다.
<style>
.order[data-order-state="new"] {
color: green;
}
.order[data-order-state="pending"] {
color: blue;
}
</style>
<div id="order" class="order" data-order-state="new">
A new order.
</div>
<script>
console.log(order.dataset.orderState);
order.dataset.orderState = 'pending';
</script>
이처럼 비표준 커스텀 속성을 사용할 때는 data-*
속성을 통해 안전하고 유효하게 관리할 수 있다.
DOM
조작 메서드를 사용하여 HTML 문서에 새로운 요소를 추가 및 제거하거나 기존 콘텐츠를 수정하는 방법에 대해 살펴보자.
DOM
노드를 만들때 사용하는 메서드는 두 가지이다.
document.createElement(tag)
태그 이름을 이용해 새로운 요소 노드를 만든다.
let div = document.createElement('div');
document.createTextNode(text)
주어진 텍스트를 사용해 새로운 텍스트 노드를 만든다.
let textNode = document.createTextNode('hello');
노드를 생성했다면 해당 노드에 DOM
프로퍼티를 지정해주어 HTML 속성값을 만들어줄 수 있다.
let div = document.createElement('div');
div.className = 'alert';
div.innerHTML = "<strong>hello!</strong>";
위 과정을 거치면 해당 내용과 클래스명을 가진 div
요소 노드가 하나 생성된다. 하지만 아직 이 요소 노드는 변수에만 담겨있을 뿐 브라우저 페이지에 추가되지 않은 상태이다.
위에서 만들어준 div
가 페이지에 나타나기 위해서는 document
어딘가에 해당 노드가 삽입되어야 한다. 이때 사용할 수 있는 요소 삽입 메서드로는 append
가 있다. 해당 메서드를 사용하여 <body>
태그 안 쪽에 위에서 만들어준 요소 노드를 삽입해보자.
let div = document.createElement('div');
div.className = 'alert';
div.innerHTML = "<strong>hello!</strong>";
document.body.append(div);
이때 삽입하고 싶은 위치에 따라 <body>
태그가 아닌 다른 요소를 지정해 줄 수 있다. 자바스크립트에서 지원하는 노드 삽입 메서드는 다음과 같다. 적절한 메서드를 사용하면 직접 삽입 위치를 지정할 수 있다.
node.append(노드 또는 문자열)
: node
끝 부분에 삽입node.prepend(노드 또는 문자열)
: node
맨 앞에 삽입node.before(노드 또는 문자열)
: node
이전에 삽입node.after(노드 또는 문자열)
: node
다음에 삽입node.replaceWith(노드 또는 문자열)
: node
를 새로운 노드 또는 문자열로 대체삽입 메서드에 문자열을 넘겨주는 경우에는 요소 노드가 아닌 자동으로 텍스트 노드가 만들어진다.
또한 해당 메서드들을 이용해 여러 개의 노드와 문자열을 한 번에 삽입하는 것 역시 가능하다.
<div id="div"></div>
<script>
div.before('<p>hello</p>', document.createElement('hr'));
</script>
이때 문자열 '<p>hello</p>'
의 경우엔 텍스트 노드로 변환된다는 점을 주의하자. 즉 이 경우는 elem.textContent
를 사용할 때 처럼 순수 텍스트가 전달된다. 즉 태그로 지정된 <p></p>
의 경우는 요소 노드가 아닌 텍스트로 지정된다. 따라서 최종 결과는 다음과 같아진다.
<p>hello</p>
<hr>
<div id="div"></div>
만약 elem.innerHTML
을 사용한 것 처럼 문자열로 태그를 전달하더라도 이를 정상적으로 파싱할 수 있게끔 하려면 다른 메서드를 사용해야 한다.
elem.insertAdjacentHTML(where, html)
메서드를 사용하면 원하는 위치에 항상 HTML 그 자체를 삽입할 수 있다. 기준이 되는 elem
객체는 삽입 위치의 기준이 되는 대상이고, 이때 첫 번째 매겨변수 where
은 항상 다음 값 중 하나여야 한다.
beforebegin
: elem
바로 앞에 html
삽입afterbegin
: elem
의 첫 번째 자식 요소 바로 앞에 html
삽입beforeend
: elem
의 마지막 요소 바로 다음에 html
삽입afterend
: elem
바로 다음에 html
삽입해당 메서드를 이용하면 문자열 형태로 HTML 구조를 전달하더라도 온전한 HTML 구조로 요소 노드를 만들어 삽입이 이루어진다.
<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>hello</p>');
div.insertAdjacentHTML('afterend', '<p>bye</p>');
</script>
<p>hello</p>
<div id="div"></div>
<p>bye</p>
소제목과 같이 insertAdjacentHTML
메서드는 형제 메서드가 있다. 각각의 형제 메서드의 이름에서 유추할 수 있듯이 insertAdjacentText
는 문자열을 그대로 삽입하고, insertAdjacentElement
는 요소만을 취급한다. 보통 요소나 문자열을 삽입하는 경우엔 append/prepend/before/after
과 같은 메서드를 이용하므로 실무에선 대부분 insertAdjacentHTML
메서드를 사용한다.
node.remove()
를 사용해서 생성한 노드 또는 기존 노드를 제거할 수 있다. 다음은 div
태그를 하나 생성하고 페이지에 추가한 뒤 1초후 다시 제거하는 코드이다.
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>안녕하세요!</strong> 중요 메시지를 확인하셨습니다.";
document.body.append(div);
setTimeout(() => div.remove(), 1000);
</script>
참고로 요소 노드를 다른 곳으로 옮길 때엔 기존에 있던 노드를 지울 필요가 없다. 모든 노드 삽입 메서드는 자동으로 기존에 있던 노드를 삭제 후에 새로운 곳으로 노드를 옮기기 때문이다.
기존에 만들어진 노드를 복제하고, 복제한 노드의 내용을 수정하는 것 역시 가능하다. 기존에 노드를 생성하는 것을 함수로 만들어 활용할 수도 있지만, 만약 복제하려는 요소가 매우 크고 복잡한 경우에는 단순히 복제를 하는 방법이 더 빠르고 간단할 수 있다.
elem.cloneNode(true)
: elem
의 깊은 복제본을 만든다. 속성 전체와 후손 요소 전부 복사한다.elem.cloneNode(false)
: 후손 요소까지는 복사하지 않고 elem
자체만 복사한다. DocumentFragment
는 특별한 DOM
노드 타입으로, 여러 노드로 구성된 그룹을 감싸 다른 곳으로 전달하게 해주는 래퍼의 역할을 수행한다.
HTML의 태그는 중첩 구조로 계속 깊이가 깊어질 수 있는데 DocumentFragment
를 사용해 감싸진 노드를 전달하면, 삽입 시점에서 DocumentFragment
는 사라지고 전달된 노드만 오롯이 추가된다.
<ul id="ul"></ul>
<script>
function getListContent() {
let fragment = new DocumentFragment();
for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
fragment.append(li);
}
return fragment;
}
ul.append(getListContent()); // (*)
</script>
getListContent()
함수가 실행되었을 때 만들어지는 HTML 문서의 구조는 아래와 같다.
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
DocumentFragment
의 자체적인 기능은 사실 노드가 담긴 배열을 전개 연산자로 전달해 직접 반환하는 것으로도 구현이 가능하기 때문에 직접 사용하는 일은 흔치않다. 보통 template
요소 같이 DocumentFragment
를 기반으로 하는 문법에서 사용되는 편이다.
React
에서도 이와 유사한 역할을 하는JSX
문법이 있다.React.Fragment
또는 단순히<></>
을 통해 이 같은 기능을 수행할 수 있는데 리액트에서는deps
가 깊어지는 것을 방지하기 위해 종종 사용되곤 한다.
하위 호환성을 유지하기 위해 남아있는 구식 조작 메서드도 있다. 요즘엔 앞서 언급한 메서드를 통해 조금 더 유연하게 DOM
을 조작하기 때문에 이들은 잘 사용하지 않는다. 때문에 어떤 메서드가 있는지만 간단하게 짚고 넘어가자.
parentElem.appendChild(node)
: parentElem
의 마지막 자식으로 node
를 추가
parentElem.insertBefore(node, nextSibling)
: node
를 parentElem
안의 nextSibling
앞에 추가
parentElem.replaceChild(node, oldChild)
: parentElem
의 자식 노드 중 oldChild
를 node
로 교체
parentElem.removeChild(node)
: node
가 parentElem
의 자식 노드라는 가정하에 parentElem
에서 node
를 제거
해당 메서드들은 모두 그 기능을 수행하고 삽입 또는 삭제한 노드 자체를 반환한다. 그런데 이 반환된 값을 굳이 사용하는 경우가 없기 때문에 메서드를 그냥 실행만 하는 편이다.
document.write
는 웹페이지에 무엇인가 더할 때 쓰이는 아주 오래된 메서드이다.
<p>페이지 어딘가...</p>
<script>
document.write('<b>자바스크립트를 사용해 Hello 입력</b>');
</script>
<p>끝</p>
document.write(html)
을 호출하면 html
이 페이지 그 자리에 즉시 추가된다. 문자열을 동적으로 만들어 사용할 수 있기 때문에 꽤나 유연한 동작을 가지고 있다.
하지만 document.write
는 DOM
객체도 없고 그 어떤 표준도 없던 머나먼 과거에 만들어진 메서드이다. 오늘날 표준에 정의되지 않았지만, 과거에 만들어진 웹 페이지의 하위 호환성을 위해 아직 살아남은 것 뿐이다. 때문에 근래에는 document.write
를 사용해 작성된 코드를 만나기 어렵다.
그 이유 말고도 document.write
는 치명적인 단점이 있는데, 바로 페이지를 불러오는 도중에만 작동한다는 점이다. 만약 페이지가 모두 로드를 마친 후에 document.write
를 호출한다면 기존에 있던 문서 내용은 전부 사라지고 새롭게 내용을 쓰게 된다. 때문에 기존 내용에 요소를 추가만 하려는 경우에는 해당 메서드를 이용할 수 없다.
물론 이러한 특성을 이용할 수도 있다. 브라우저는 HTML을 파싱하는 도중에 document.write(HTML)
을 만다게 되면 텍스트 형식의 HTML을 마치 원래 페이지에 있는 것처럼 해석한다. 중간에 DOM
조작을 하지 않기 때문에 그 속도가 매우 빠르다. 따라서 만약 엄청나게 많은 글자를 HTML에 동적으로 추가해야 하는데 아직 페이지를 불러오고 있는 중이고 속도가 중요한 상황에서는 document.write
가 유용할 수 있다.
그렇지만 이러한 요구 상황은 일반적인 상황이 아닐뿐더러 보통 document.write
가 쓰였다면 오래된 스크립트이기에 그럴 확률이 높다.
요소에 스타일을 적용할 수 있는 방법은 크게 두 가지가 있다.
id
) 요소 자체에 속성을 추가<div style="...">
처럼 프로퍼티를 style
에 바로 기입자바스크립트를 이용하면 클래스와 style
프로퍼티 둘 다 수정이 가능하다. 만약 두 방법 중 하나를 택해야 한다면 CSS 클래스를 수정하는 것을 더 우선시 하는 것이 좋다. style
은 클래스를 다룰 수 없는 경우에 취급하는 편이 좋다.
예를 들어 아래와 같이 자바스크립트를 사용해 요소의 좌표를 동적으로 계산하고, 계산한 좌표를 직접 설정하고자 하는 경우에는 style
을 사용하는 편이 낫다.
let top = /* 복잡한 계산식 */;
let left = /* 복잡한 계산식 */;
elem.style.left = left;
elem.style.top = top;
클ㄹ스 변경은 스크립트를 통해 자주하는 동작 중에 하나이다. 아주 오래전 자바스크립트엔 class
와 같은 예약어가 객체의 프로퍼티가 될 수 없다는 제약이 있었는데, 그러한 이유로 className
이라는 프로퍼티를 통해 클래스를 접근할 수 있었다. 지금은 이러한 제약이 사라졌음에도 불구하고 여전히 하위 호환성을 위해 className
프로퍼티를 사용한다.
비슷한 이유로
React
에서도JSX
내에서class
대신className
을 사용한다.
elem.className
은 HTML 속성 class
에 대응한다.
<body class="main page">
<script>
alert(document.body.className); // main page
</script>
</body>
이때 className
에서 주의해야 할 점이있다. className
에 무엇인가 대입하면 클래스 문자열 전체가 바뀐다. 따라서 클래스 전체를 바꾸는 것이 아니라 어떤 클래스명 하나만 추가한다거나 제거하는 경우에는 다른 프로퍼티를 사용하는 것이 더 간편하다.
elem.classList
라는 프로퍼티를 사용하면 자체적으로 제공하는 add/toggle/remove
메서드 등을 통해 이 같은 조작을 간단하게 할 수 있다.
elem.classList.add("class")
: class
를 추가elem.classList.remove("class")
: class
를 제거elem.classList.toggle("class")
: class
가 존재하면 class
를 제거, 없다면 추가elem.classList.contains("class")
: class
존재 여부에 따라 true/false
반환또한 classList
는 이터러블 객체를 반환하기 때문에 for...of
를 통해 순회할 수 있는 특징이 있다.
만약 클래스 속성값 전체를 바꾸고 싶은 경우에는 classNames
으로, 개별 클래스를 조작하고 싶은 경우에는 classList
를 사용하면 된다.
프로퍼티 elem.style
은 속성 style
에 쓰인 값과 대응되는 객체이다. elem.style.width='10px'
은 style
속성값을 문자열 width='10px'
로 설정한 것과 같다.
실제 CSS 스타일 속성과 달리 자바스크립트 또는 style
속성 내부에서는 여러 단어로 이루어진 스타일 속성을 모두 카멜 표기법으로 표현한다.
background-color => elem.style.backgroundColor
z-index => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth
간혹 브라우저마다 CSS 스타일을 달리하는 경우가 있다. 이때는 스타일 속성에 -moz
또는 -webkit
과 같은 접두어가 추가되는데 이러한 특정 브라우저 전용 프로퍼티 역시 모두 카멜 표기법으로 접근하거나 값을 쓸 수 있다.
button.style.MozBorderRadius = '5px';
button.style.WebkitBorderRadius = '5px';
style
프로퍼티에 값을 할당했다가 시간이 지나 이를 제거해야할 경우가 종종 생길 수 있다. 예를 들어 어떤 요소 하나를 숨기기 위해 elem.style.display = "none"
을 적용했다고 가정해보자. 해당 요소를 어느 시점에 다시 표시해야할 순간이 와서 되돌리고 싶다면 어떻게 해야할까?
자바스크립트 객체의 관점에서만 생각한다면 delete
를 이용해 속성을 제거해야 한다고 생각할 수 있다. 그렇지만 delete elem.style.display
처럼 그 값을 제거하는 것이 아니라 elem.style.display = ""
처럼 다시 빈 문자열을 할당해주는 것으로 원래 상태로 되돌릴 수 있다.
이처럼 style.display
에 빈 문자열을 할당하면 브라우저는 마치 처음부터 style.display
프로퍼티가 없었던 것처럼 CSS 클래스와 브라우저 내장 스타일을 페이지에 적용한다.
개별 스타일 프로퍼티를 적용할 때는 보통 위와 같이 style.*
를 사용한다. 그런데 사실 style
은 객체이고 읽기 전용이기 때문에 복수의 옵션을 동시에 적용할 수 없다.
// 아래는 유효하지 않은 방식
div.style = "color: red; width: 100px"
만약 이렇게 복수의 옵션을 한번에 설정하고 싶다면 style.cssText
프로퍼티를 사용할 수 있다.
div.cssText=`color: red !important;
width: 100px;
`;
style.cssText
프로퍼티를 사용하면 기존 스타일에 스타일을 추가하는 것이 아닌 전체를 교체하는 방식이기 때문에 이미 만들어진 요소에는 잘 적용하지 않는다. 그렇지만 새로운 요소를 만들고 스타일을 부여하는 상황에서는 기존 스타일이 없기 때문에 적용해봄직 하다.
또는 div.setAttribute('style', 'color: red...)
과 같은 방식으로도 style.cssText
과 동일한 기능을 할 수 있다.
자바스크립트를 사용해 스타일 값을 설정할 땐 단위를 항상 붙여주는 것에 주의해야 한다. 예를 들어 elem.style.top
에 값을 설정할 때 10px
이 아닌 10
과 같이 숫자만 지정하는 경우엔 제대로 동작하지 않는다.
style
프로퍼티로 값을 읽는 것은 오직 HTML 요소에 style
속성이 지정되어 그 값을 가져오는 것만 유효하다. 즉 style
프로퍼티를 통해서는 CSS 스타일링으로 지정된 종속값(CSS Cascade
)을 다루지 못한다.
<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>
붉은 글씨
<script>
alert(document.body.style.color); // 빈 문자열
alert(document.body.style.marginTop); // 빈 문자열
</script>
</body>
위의 코드를 통해 CSS 스타일링을 통해 지정된 값은 style
프로퍼티로 가져오지 못하고 있는 것을 확인할 수 있다.
그렇다면 만약 현재 지정된 마진값보다 20px
만큼 더 큰 값을 부여하고 싶다면 어떻게 해야할까? 이 작업을 하기 위해서는 먼저 지정된 현재 마진값을 얻을 수 있어야 한다. 이때 getComputedStyle
메서드를 통해 값을 가져올 수 있다. 해당 메서드의 문법은 다음과 같다.
getComputedStyle(element, [pseudo]);
element
: 값을 읽을 요소pseudo
: ::before
같이 의사 요소가 필요한 경우 명시. 빈 문자열 또는 입력하지 않을 경우엔 요소 자체를 의미getComputedStyle
를 호출하면 elem.style
과 같이 스타일 정보가 들어있는 객체가 반환되는데 여기엔 elem.style
과는 달리 전체 CSS 클래스 정보도 함께 담기게 된다.
<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>
<script>
let computedStyle = getComputedStyle(document.body);
// 이제 마진과 색 정보를 얻을 수 있다.
alert( computedStyle.marginTop ); // 5px
alert( computedStyle.color ); // rgb(255, 0, 0)
</script>
</body>
이때 getComputedStyle
에는 프로퍼티 전체 이름이 필요하다. 즉 paddingLeft
, marginTop
, borderTopWidth
와 같이 프로퍼티 이름 전체를 정확히 알고 있어야 한다. 그렇지 않은 경우에는 원하는 값을 얻을 수 없을 수도 있다. 예를 들어 paddingLeft
에 값이 지정되어 있는데 getComputedStyle(elem).padding
을 사용해 값을 얻으려고 하는 경우를 생각해보자.
이때엔 아무 값을 읽지 못할까 아니면 paddingLeft
에 담긴 값이라도 가져올까? 이에 대한 표준은 아직 만들어진 것이 없다. 따라서 브라우저마다 상이한 결과를 나타낸다. 크롬 브라우저에서는 이 상황에서 paddingLeft
의 값이라도 가져오지만 파이어폭스 브라우저에서는 빈 문자열을 출력한다.
CSS 속성과 관련된 두 가지 개념이 있다.
Computed Style Value
)height: 1rem
또는 font-size: 125%
등의 형태Resolved Style Value
)height: 16px
또는 font-size: 12.5px
등의 형태getComputedStyle
메서드는 계산값을 얻기 위해 만들어진 오래된 메서드이다. 그렇지만 계산값 보다는 결정값을 사용하는게 훨씬 유연하고 정확하기 때문에 표준이 개정되었다. 따라서 지금은 getComputedStyle
를 호출하면 결정값을 반환한다.
getComputedStyle
메서드의 매개변수로 넘겨주는 의사 요소 중에:visited
CSS 의사 클래스 관련 스타일은 숨겨져 있다. 예를 들어 방문한 적이 있는 링크엔:visited
속성을 통해 CSS 스타일링에서는 색을 입힐 수 있다. 그러나getComputedStyle
을 통해서는 해당 값에 접근할 수 없다. 이는 브라우저에 진입한 사용자가 해당 링크에 접근했는지 아닌지를 색상이 변화한 것에 따라 파악할 수 있기 때문에 이를 악용하려는 시도를 방지하기 위해 이러한 제약을 두고 있다.