2020년 3월 11일

스타일과 클래스

자바스크립트를 사용해 어떻게 스타일과 클래스를 다룰 수 있는지 알아보기 전에, 중요한 규칙을 하나 집고 넘어가야 할 것 같습니다. 핵심만 요약했기 때문에 충분할진 모르겠지만 꼭 언급하고 넘어가야 하기 때문입니다.

요소에 스타일을 적용할 수 있는 방법은 두 가지가 있습니다.

  1. CSS에 클래스를 만들고, 요소에 <div class="...">처럼 클래스 추가하기
  2. <div style="...">처럼 프로퍼티를 style에 바로 써주기

자바스크립트를 사용하면 클래스와 style 프로퍼티 둘 다를 수정할 수 있습니다.

두 방법 중 하나를 택하라면 style 보다 CSS 클래스를 수정하는 것을 더 우선시해야 합니다. style은 클래스를 '다룰 수 없을 때’만 사용해야 합니다.

style은 아래와 같이 자바스크립트를 사용해 요소의 좌표를 동적으로 계산하고, 계산한 좌표를 설정해주고자 할 때 사용하면 좋습니다.

let top = /* 복잡한 계산식 */;
let left = /* 복잡한 계산식 */;

elem.style.left = left; // 예시: '123px', 런타임으로 좌표를 계산할 수 있습니다.
elem.style.top = top; // 예시: '456px'

텍스트를 빨간색으로 만든다거나 배경에 이미지를 추가하는 등의 작업은 CSS에 관련 스타일을 명시한 클래스를 만들고, 자바스크립트를 사용해 이 클래스를 요소에 추가해 주는 방식이 좋습니다. 이렇게 하면 유연성이 확보돼 유지보수가 쉬워집니다.

className과 classList

클래스 변경은 스크립트를 통해 자주 하게 되는 동작 중 하나입니다.

아주 오래전 자바스크립트엔 "class"같은 예약어는 객체의 프로퍼티가 될 수 없다는 제약사항이 있었습니다. 지금은 이런 제약사항이 사라졌지만, 과거엔 "class" 프로퍼티를 사용할 수 없었기 때문에 elem.class를 사용하는 것 역시 불가능했습니다.

이런 배경 때문에 클래스를 위한 프로퍼티 "className"가 도입되게 되었습니다. elem.className"class" 속성에 대응합니다.

예시:

<body class="main page">
  <script>
    alert(document.body.className); // main page
  </script>
</body>

elem.className에 무언가를 대입하면 클래스 문자열 전체가 바뀝니다. 그런데 이렇게 속성값 전체를 바꾸는 게 아니고 클래스 하나만 추가하거나 제거하고 싶은 경우도 있기 마련입니다.

이럴 때 elem.classList라는 프로퍼티를 사용할 수 있습니다.

elem.classList엔 클래스 하나만 조작하게 해주는 메서드인 add/remove/toggle가 구현되어 있습니다.

예시:

<body class="main page">
  <script>
    // 클래스 추가
    document.body.classList.add('article');

    alert(document.body.className); // main page article
  </script>
</body>

이렇게 클래스 속성값 전체를 바꾸고 싶을 때는 className으로, 개별 클래스를 조작하고 싶을 때는 classList를 사용하면 됩니다. 필요에 따라 취사선택하면 되죠.

classList에 구현된 메서드는 다음과 같습니다.

  • elem.classList.add/remove("class")class를 추가하거나 제거
  • elem.classList.toggle("class")class가 존재할 경우 class를 제거하고, 그렇지 않은 경우엔 추가
  • elem.classList.contains("class")class 존재 여부에 따라 true/false를 반환

이 외에 classList는 이터러블 객체이기 때문에 아래 예시와 같이 for..of를 사용해 클래스를 나열할 수 있다는 특징이 있습니다.

<body class="main page">
  <script>
    for (let name of document.body.classList) {
      alert(name); // main과 page가 출력됨
    }
  </script>
</body>

요소의 스타일

프로퍼티 elem.style은 속성 "style"에 쓰인 값에 대응되는 객체입니다. elem.style.width="100px"style 속성값을 문자열 width:100px로 설정한 것과 같죠.

여러 단어를 이어서 만든 프로퍼티는 다음와 같이 카멜 표기법을 사용해 이름 짓습니다.

background-color  => elem.style.backgroundColor
z-index           => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth

예시:

document.body.style.backgroundColor = prompt('배경을 무슨 색으로 바꿀까요?', 'green');
특정 브라우저 전용 프로퍼티

-moz-border-radius, -webkit-border-radius같이 브라우저 관련 접두사가 붙은 프로퍼티(browser-prefixed property) 역시 카멜 표기법을 사용합니다. 대시(-)는 대문자를 의미합니다.

예시:

button.style.MozBorderRadius = '5px';
button.style.WebkitBorderRadius = '5px';

style 프로퍼티 재지정하기

style 프로퍼티에 값을 할당했다가 시간이 지나 이를 제거해야 할 때가 종종 있습니다.

요소를 숨기기 위해 elem.style.display = "none"를 적용했다고 가정해봅시다.

시간이 지나 처음부터 style.display를 설정하지 않았던 것처럼 되돌리고 싶어졌습니다. 이럴 땐 delete elem.style.display를 사용해 프로퍼티를 삭제하는 대신 elem.style.display = ""같이 빈 문자열을 할당해주어야 합니다.

// 예시를 실행하면 페이지의 <body>가 깜빡입니다.
document.body.style.display = "none"; // hide

setTimeout(() => document.body.style.display = "", 1000); // 1초 후 다시 원래 상태로 돌아옵니다.

이렇게 style.display에 빈 문자열을 할당하면 브라우저는 마치 처음부터 style.display 프로퍼티가 없었던 것처럼 CSS 클래스와 브라우저 내장 스타일을 페이지에 적용합니다.

style.cssText로 완전히 다시 쓰기

개별 스타일 프로퍼티를 적용할 때는 보통 style.*를 사용합니다. 그런데 div.style 은 객체이고 읽기 전용이기 때문에 div.style="color: red; width: 100px"같은 방식으론 전체 스타일을 설정할 수 없습니다.

문자열을 사용해 전체 스타일을 설정하려면 프로퍼티 style.cssText를 사용해야 합니다.

<div id="div">버튼</div>

<script>
  // cssText를 사용하면 'important' 같은 규칙도 설정할 수 있습니다.
  div.style.cssText=`color: red !important;
    background-color: yellow;
    width: 100px;
    text-align: center;
  `;

  alert(div.style.cssText);
</script>

style.cssText를 사용하면 기존 스타일에 스타일을 추가하는 게 아니라 전체를 교체해버리기 때문에 잘 쓰이지 않습니다. 잘 사용하고 있는 스타일이 실수로 지워진다는 위험이 있죠. 그렇지만 요소를 새로 만들고, 여기에 스타일을 적용할 때는 기존 스타일이 없기 때문에 style.cssText를 사용할 수 있습니다.

div.setAttribute('style', 'color: red...')를 사용해 속성을 설정해도 style.cssText과 같은 효과를 볼 수 있습니다.

단위에 주의하기

자바스크립트를 사용해 스타일 값을 설정할 때는 단위를 붙여주는 걸 잊지 말아야 합니다.

elem.style.top에 값을 설정할 때 10px이 아닌 10을 설정하면 제대로 동작하지 않습니다.

<body>
  <script>
    // 동작하지 않습니다!
    document.body.style.margin = 20;
    alert(document.body.style.margin); // '' (값이 제대로 할당되지 않았기 때문에 빈 문자열이 출력됩니다.)

    // CSS 단위(px)를 추가해봅시다. 제대로 동작하네요!
    document.body.style.margin = '20px';
    alert(document.body.style.margin); // 20px

    alert(document.body.style.marginTop); // 20px
    alert(document.body.style.marginLeft); // 20px
  </script>
</body>

참고로 브라우저는 위 예시와 같이 style.margin를 ‘분석’ 및 추론한 결과를 style.marginLeftstyle.marginTop에 할당해줍니다.

getComputedStyle로 계산된 스타일 얻기

지금까지 배운 내용을 응용하면 스타일을 쉽게 수정할 수 있습니다. 그럼 어떻게 하면 스타일을 읽을 수 있을까요?

요소의 크기와 마진, 색을 알고 싶다면 어떻게 해야 할까요?

style 프로퍼티 값을 읽으면 되지 않을까 생각할 수 있겠지만, style 프로퍼티는 "style" 속성의 값을 읽을 때만 사용할 수 있습니다. style 프로퍼티만으론 CSS 종속(CSS cascade)값을 다루지 못합니다.

이런 특징 때문에 elem.style만으로는 CSS 클래스를 사용해 적용한 스타일을 읽을 수 없습니다.

style 프로퍼티를 사용해도 마진값을 읽을 수 없다는 것을 직접 확인해봅시다.

<head>
  <style> body { color: red; margin: 5px } </style>
</head>
<body>

  붉은 글씨
  <script>
    alert(document.body.style.color); // 빈 문자열
    alert(document.body.style.marginTop); // 빈 문자열
  </script>
</body>

그렇다면 마진을 현재 크기보다 20px 더 크게 하려면 어떻게 해야 할까요? 원하는 작업을 하려먼 먼저 현재 크기를 알 수 있어야 합니다.

이럴 때 getComputedStyle 메서드를 사용할 수 있습니다.

getComputedStyle 메서드의 문법은 다음과 같습니다.

getComputedStyle(element, [pseudo])
element
값을 읽을 요소
pseudo
::before같이 의사 요소(pseudo-element)가 필요한 경우 명시해줌. 빈 문자열을 넘겨주거나 아무런 값을 입력하지 않은 경우 요소 자체를 의미함

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>
계산 값과 결정 값

CSS에는 속성과 관련된 두 가지 개념이 있습니다.

  1. 계산 값(computed style value) – CSS 규칙과 CSS 상속이 모두 적용된 후의 값을 의미합니다. 값의 형태는 height:1emfont-size:125% 같이 생겼습니다.
  2. 결정 값(resolved style value) – 요소에 최종적으로 적용되는 값을 의미합니다. 계산 값에서 사용한 1em125%은 상대 단위를 사용하는 상댓값인데, 브라우저는 계산 값을 받아 단위를 전환해 height:20pxfont-size:16px같이 고정 단위를 사용하는 값(절댓값)으로 값을 변환합니다. 기하 관련 프로퍼티의 결정 값에는 width:50.5px같이 소수점 단위가 있을 수 있습니다.

getComputedStyle은 계산 값을 얻기 위해서 만들어진 아주 오래된 메서드입니다. 그런데 계산 값보다는 결정 값을 사용하는 게 훨씬 편리하기 때문에 표준이 개정되었습니다.

따라서 지금은 getComputedStyle을 호출하면 프로퍼티의 결정 값이 반환됩니다. 기하 프로퍼티의 경우 주로 px가 단위로 사용됩니다.

getComputedStyle엔 프로퍼티 전체 이름이 필요합니다.

getComputedStyle을 사용할 때는 paddingLeft, marginTop, borderTopWidth같이 프로퍼티 이름 전체를 정확히 알고 있어야 합니다. 그렇지 않으면 원하는 값을 얻을 수 없는 경우가 생깁니다.

paddingLeftpaddingTop엔 값이 지정되어있는데 getComputedStyle(elem).padding을 사용해 값을 얻으려 하는 경우를 생각해 봅시다. 어떤 값을 얻을 수 있을까요? 아무것도 얻지 못할까요 아니면 값이 설정되어 있는 paddingLeftpaddingTop에서 값을 가져올까요? 이런 상황에 적용할만한 표준은 아직 제정되어있지 않습니다.

따라서 브라우저마다 동작 방식이 다릅니다. Chrome 같은 몇몇 브라우저는 아래 예시와 같이 10px을 출력해주는데 Firefox에서 빈 문자열이 출력됩니다.

<style>
  body {
    margin: 10px;
  }
</style>
<script>
  let style = getComputedStyle(document.body);
  alert(style.margin); // Firefox에선 빈 문자열이 출력됩니다.
</script>
:visited 링크 관련 스타일은 숨겨져 있습니다.

방문한 적이 있는 링크엔 :visited라는 CSS 의사 클래스를 사용해 색을 입힐 수 있습니다.

그런데 getComputedStyle을 사용해서 이 색을 얻을 수 없습니다. 색을 얻을 수 있도록 하면 임의의 페이지에서 사용자가 해당 링크를 방문했는지 아닌지를 getComputedStyle을 사용해 알아낼 수 있기 때문입니다.

자바스크립트로는 :visited에 적용된 스타일을 얻지 못합니다. 여기에 더하여 CSS에는 :visited에 기하학적 변화를 가져오는 스타일을 적용하는 것을 금지하는 제약도 있습니다. 이런 제약들은 악의를 가진 페이지가 사용자가 링크를 방문했는지 여부를 테스트 하고, 방문 여부에 따라 사생활을 침해할 만한 어떠한 것도 하지 못하도록 하려고 만들어졌습니다.

요약

클래스를 관리할 수 있게 해주는 DOM 프로퍼티:

  • className – 클래스 전체를 문자열 형태로 반환해주는 프로퍼티로 클래스 전체를 관리할 때 유용합니다.
  • classList – 클래스 하나를 관리할 수 있게 해주는 메서드입니다. add/remove/toggle/contains가 구현된 객체를 반환합니다. 개별 클래스를 조작할 때 유용합니다.

스타일 변경 방법:

  • style 프로퍼티 – 카멜 표기법을 이용해 변경한 스타일이 있는 객체로, 이 객체를 조작하는 것은 "style" 속성과 대응하는 개별 프로퍼티를 조작하는 것과 같습니다. important 등의 규칙을 어떻게 적용할 수 있는지 알아보려면 MDN에서 관련 메서드를 살펴보시기 바랍니다.

  • style.cssText 프로퍼티는 "style" 속성 전체에 대응하므로 스타일 전체에 대한 문자열이 저장됩니다.

요소의 스타일 결정 값을 읽는 방법:

  • 스타일 정보가 들어 있는 객체를 반환해주는 메서드 getComputedStyle(elem, [pseudo])를 사용합니다. 이 메서드는 읽기 전용입니다.

과제

중요도: 5

샌드박스를 열었을 때 보이는 콘텐츠를 기반으로 <div class="notification"> 알림을 만들어주는 함수 showNotification(options)를 작성해봅시다. 이 알림은 1.5초 후에 자동으로 사라집니다.

옵션은 다음과 같습니다.

// 창의 오른쪽 상단에 "Hello"라는 텍스트가 담긴 요소를 보여줍니다.
showNotification({
  top: 10, // 창의 상단에서 10px 떨어진 위치에 표시합니다(기본값은 0px).
  right: 10, // 창의 오른쪽 가장자리에서 10px 떨어진 위치에 표시합니다(기본값은 0px).
  html: "Hello!", // 알림의 HTML입니다.
  className: "welcome" // div에 대한 추가 클래스입니다(선택 사항).
});

새 창에서 데모 보기

CSS 포지셔닝을 사용하여 주어진 좌표(오른쪽 상단)에 알림을 표시하세요. 필요한 스타일들은 링크를 열면 찾아볼 수 있습니다.

샌드박스를 열어 정답을 작성해보세요.

튜토리얼 지도

댓글

댓글을 달기 전에 마우스를 올렸을 때 나타나는 글을 먼저 읽어주세요.
  • 추가 코멘트, 질문 및 답변을 자유롭게 남겨주세요. 개선해야 할 것이 있다면 댓글 대신 이슈를 만들어주세요.
  • 잘 이해되지 않는 부분은 구체적으로 언급해주세요.
  • 댓글에 한 줄짜리 코드를 삽입하고 싶다면 <code> 태그를, 여러 줄로 구성된 코드를 삽입하고 싶다면 <pre> 태그를 이용하세요. 10줄 이상의 코드는 plnkr, JSBin, codepen 등의 샌드박스를 사용하세요.