바닐라 자바스크립트 로 CSS 제어하기

안녕하세요! 웹 페이지의 동적인 상호작용은 대부분 자바스크립트CSS의 조합으로 이루어집니다. 특히, 라이브러리나 프레임워크 없이 순수한 자바스크립트, 즉 바닐라 자바스크립트만으로 CSS를 제어하는 방법을 아는 것은 프론트엔드 개발의 핵심 역량 중 하나입니다. 이 글에서는 다양한 CSS 제어 기법을 예제와 함께 자세히 살펴보겠습니다.

1. 기본 CSS 속성 직접 조작하기

가장 기본적인 방법은 DOM 요소의 style 프로퍼티를 통해 직접 CSS 속성을 변경하는 것입니다. 이는 인라인 스타일로 적용됩니다.


<!-- HTML -->
<div id="myElement" style="width: 100px; height: 100px; background-color: lightblue;"></div>

// JavaScript
const myElement = document.getElementById('myElement');

// 1. 점(.) 표기법으로 직접 속성 변경 (카멜케이스 사용)
myElement.style.backgroundColor = 'salmon';
myElement.style.width = '200px';
myElement.style.fontSize = '18px'; // font-size는 fontSize로

// 2. setProperty() 메서드를 사용하여 속성 변경 (원래 CSS 속성 이름 사용)
myElement.style.setProperty('border-radius', '10px');
myElement.style.setProperty('opacity', '0.7', 'important'); // !important 적용 가능

2. classList 메서드를 활용한 클래스 제어

classList는 요소의 클래스 목록을 제어하는 데 사용되는 유용한 프로퍼티입니다. 클래스를 추가, 제거, 토글하거나 특정 클래스의 존재 여부를 확인할 수 있습니다. CSS에서는 이 클래스에 대한 스타일을 미리 정의해 둡니다.


<!-- HTML -->
<button id="toggleClassBtn">클래스 토글</button>
<div id="myBox" class="box"></div>

/* CSS */
.box {
  width: 100px;
  height: 100px;
  background-color: steelblue;
  transition: background-color 0.3s ease;
}

.highlight {
  background-color: gold;
  border: 2px solid orange;
}

// JavaScript
const myBox = document.getElementById('myBox');
const toggleClassBtn = document.getElementById('toggleClassBtn');

toggleClassBtn.addEventListener('click', () => {
  // 1. 클래스 추가: add()
  myBox.classList.add('new-class');

  // 2. 클래스 제거: remove()
  myBox.classList.remove('box');

  // 3. 클래스 토글: toggle() (있으면 제거, 없으면 추가)
  myBox.classList.toggle('highlight');

  // 4. 클래스 존재 여부 확인: contains()
  if (myBox.classList.contains('highlight')) {
    console.log('highlight 클래스가 있습니다.');
  }

  // 5. 특정 인덱스의 클래스 이름 가져오기: item(index)
  console.log(myBox.classList.item(0));

  // 6. 클래스 교체: replace(oldClass, newClass)
  // myBox.classList.replace('old-class', 'new-class');
});

3. DOM 요소 선택 메서드와 CSS 조작

CSS를 조작하려면 먼저 대상 DOM 요소를 선택해야 합니다. 자바스크립트는 다양한 선택 메서드를 제공합니다.


<!-- HTML -->
<div id="uniqueId">ID로 선택</div>
<div class="commonClass">첫 번째 클래스</div>
<div class="commonClass">두 번째 클래스</div>

// JavaScript

// 1. getElementById(): ID로 단일 요소 선택
const uniqueElement = document.getElementById('uniqueId');
if (uniqueElement) {
  uniqueElement.style.color = 'red';
}

// 2. querySelector(): CSS 선택자로 첫 번째 일치 요소 선택
const firstCommonElement = document.querySelector('.commonClass');
if (firstCommonElement) {
  firstCommonElement.style.fontWeight = 'bold';
}

// 3. querySelectorAll(): CSS 선택자로 모든 일치 요소(NodeList) 선택
const allCommonElements = document.querySelectorAll('.commonClass');
allCommonElements.forEach(element => {
  element.style.backgroundColor = 'lightgray';
});

4. 이벤트 기반 CSS 변경

사용자의 상호작용(클릭, 마우스 오버, 스크롤 등)에 따라 CSS를 동적으로 변경하는 것은 웹 페이지를 생동감 있게 만듭니다.

클릭 이벤트


<!-- HTML -->
<button id="toggleColorBtn">색상 변경</button>
<p id="textChange">이 텍스트의 색상이 변경됩니다.</p>

// JavaScript
const toggleColorBtn = document.getElementById('toggleColorBtn');
const textChange = document.getElementById('textChange');

toggleColorBtn.addEventListener('click', () => {
  if (textChange.style.color === 'blue') {
    textChange.style.color = 'black';
  } else {
    textChange.style.color = 'blue';
  }
});

마우스 오버/아웃 이벤트


<!-- HTML -->
<div id="hoverBox" style="width: 150px; height: 150px; background-color: purple;"></div>

// JavaScript
const hoverBox = document.getElementById('hoverBox');

hoverBox.addEventListener('mouseover', () => {
  hoverBox.style.backgroundColor = 'orange';
  hoverBox.style.cursor = 'pointer';
});

hoverBox.addEventListener('mouseout', () => {
  hoverBox.style.backgroundColor = 'purple';
});

스크롤 이벤트

스크롤 위치에 따라 요소의 스타일을 변경하여 동적인 효과를 줄 수 있습니다.


<!-- HTML -->
<style>
  body { height: 200vh; margin: 0; } /* 스크롤을 만들기 위해 높이 추가 */
  #scrollRevealBox {
    width: 200px;
    height: 200px;
    background-color: green;
    opacity: 0;
    transform: translateY(50px);
    transition: opacity 1s ease-out, transform 1s ease-out;
    margin-top: 100vh; /* 화면 중간쯤에 위치 */
  }
  .fade-in {
    opacity: 1;
    transform: translateY(0);
  }
</style>
<div id="scrollRevealBox">스크롤하면 나타남</div>

// JavaScript
const scrollRevealBox = document.getElementById('scrollRevealBox');

window.addEventListener('scroll', () => {
  // 요소가 뷰포트 내에 들어왔는지 확인
  const boxTop = scrollRevealBox.getBoundingClientRect().top;
  const windowHeight = window.innerHeight;

  if (boxTop < windowHeight - 100) { // 뷰포트 상단에서 100px 이내로 들어오면
    scrollRevealBox.classList.add('fade-in');
  } else {
    // 다시 스크롤 올릴 때 사라지게 하려면 아래 줄 활성화
    // scrollRevealBox.classList.remove('fade-in');
  }
});

5. CSS 트랜지션 및 애니메이션 제어

자바스크립트만으로 복잡한 애니메이션을 구현할 수도 있지만, 성능과 부드러움을 위해 CSS 트랜지션/애니메이션을 활용하고 자바스크립트는 이를 트리거하는 역할만 하는 것이 권장됩니다.

클래스 토글을 통한 트랜지션/애니메이션 트리거

CSS에서 트랜지션/애니메이션 속성을 정의한 클래스를 만들고, 자바스크립트로 이 클래스를 추가하거나 제거하여 효과를 발생시킵니다.


<!-- HTML -->
<button id="animateBtn">애니메이션 시작</button>
<div id="animatedBox"></div>

/* CSS */
#animatedBox {
  width: 100px;
  height: 100px;
  background-color: dodgerblue;
  margin-left: 0;
  transition: margin-left 1s ease-in-out, background-color 1s ease; /* 트랜지션 정의 */
}

#animatedBox.move {
  margin-left: 300px;
  background-color: tomato;
}

// JavaScript
const animatedBox = document.getElementById('animatedBox');
const animateBtn = document.getElementById('animateBtn');

animateBtn.addEventListener('click', () => {
  animatedBox.classList.toggle('move');
});

// 트랜지션 종료 시점 감지 (추가적인 동작 필요 시)
animatedBox.addEventListener('transitionend', () => {
  console.log('애니메이션 종료!');
  // alert('애니메이션이 끝났습니다!');
});

6. 동적인 요소 생성 및 CSS 적용

자바스크립트를 사용하여 HTML 요소를 동적으로 생성하고, 생성과 동시에 CSS 스타일이나 클래스를 적용할 수 있습니다.


<!-- HTML -->
<button id="createDivBtn">새 DIV 생성</button>
<div id="container" style="border: 1px solid #ccc; padding: 10px; min-height: 50px;"></div>

/* CSS */
.dynamic-box {
  background-color: mediumseagreen;
  color: white;
  padding: 10px;
  margin-top: 10px;
  border-radius: 5px;
  text-align: center;
}

// JavaScript
const createDivBtn = document.getElementById('createDivBtn');
const container = document.getElementById('container');
let count = 0;

createDivBtn.addEventListener('click', () => {
  const newDiv = document.createElement('div');
  newDiv.textContent = '동적으로 생성된 DIV ' + (++count);

  // 1. 인라인 스타일 직접 적용
  newDiv.style.width = '200px';
  newDiv.style.height = '50px';
  newDiv.style.lineHeight = '50px';

  // 2. CSS 클래스 추가
  newDiv.classList.add('dynamic-box');

  container.appendChild(newDiv);
});

7. 성능 최적화: Reflow와 Repaint 최소화, requestAnimationFrame 활용

자바스크립트로 CSS를 조작할 때, 리플로우(Reflow)리페인트(Repaint)는 성능 저하의 주범이 될 수 있습니다.

  • 리플로우: 요소의 크기나 위치 등 레이아웃에 영향을 주는 속성 변경 시 발생하며, 관련 요소들의 레이아웃을 다시 계산합니다. 비용이 많이 듭니다.
  • 리페인트: 색상, 그림자 등 레이아웃에 영향을 주지 않는 스타일 변경 시 발생하며, 화면에 다시 그립니다. 리플로우보다 비용이 적습니다.

성능을 최적화하기 위한 몇 가지 팁:

  • 배치 업데이트 (Batch Updates): 여러 CSS 속성을 변경할 때는 한 번에 모아서 변경합니다.
    
    // 나쁜 예: 매번 리플로우/리페인트 발생 가능
    // element.style.width = '100px';
    // element.style.height = '100px';
    // element.style.backgroundColor = 'red';
    
    // 좋은 예: 클래스 토글 (CSS에서 정의)
    // element.classList.add('new-style');
    
    // 좋은 예: CSSText 사용 (모든 인라인 스타일을 한 번에 설정)
    // element.style.cssText = 'width: 100px; height: 100px; background-color: red;';
        
  • transformopacity 활용: transformopacity 속성은 레이아웃에 영향을 주지 않아 리플로우를 일으키지 않고 리페인트만 발생시키거나, GPU 가속을 활용하여 훨씬 효율적으로 애니메이션을 처리합니다.
  • requestAnimationFrame 사용: 애니메이션과 같이 연속적인 시각적 업데이트가 필요할 때는 setTimeout이나 setInterval 대신 requestAnimationFrame을 사용합니다. requestAnimationFrame은 브라우저의 화면 재생 빈도(보통 60fps)에 맞춰 콜백 함수를 호출하여 부드러운 애니메이션을 보장하고, 백그라운드 탭에서는 동작을 멈춰 리소스를 절약합니다.

// JavaScript - requestAnimationFrame 예제
const animTarget = document.getElementById('animTarget'); // 예시 HTML 요소

let start;
let currentPos = 0;
const speed = 2; // 이동 속도

function animate(timestamp) {
  if (!start) start = timestamp;
  const progress = timestamp - start;

  // 원하는 애니메이션 로직 구현
  currentPos = (progress / 16) * speed; // 대략 16ms마다 speed만큼 이동
  animTarget.style.transform = `translateX(${currentPos}px)`;

  // 애니메이션 종료 조건
  if (currentPos < 300) {
    requestAnimationFrame(animate);
  }
}

// 애니메이션 시작
// requestAnimationFrame(animate);

8. 고급 UI 컴포넌트 구현 시 CSS 제어

바닐라 자바스크립트만으로 탭, 아코디언, 모달 같은 복잡한 UI 컴포넌트를 구현할 때도 CSS 제어는 필수적입니다. 주로 classList를 사용하여 상태를 관리하고, CSS 트랜지션을 통해 부드러운 전환 효과를 만듭니다.

아코디언 (Accordion)


<!-- HTML -->
<div class="accordion">
  <div class="accordion-item">
    <button class="accordion-header">질문 1</button>
    <div class="accordion-content"><p>답변 1입니다.</p></div>
  </div>
  <div class="accordion-item">
    <button class="accordion-header">질문 2</button>
    <div class="accordion-content"><p>답변 2입니다.</p></div>
  </div>
</div>

/* CSS */
.accordion-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out;
}

.accordion-item.active .accordion-content {
  max-height: 200px; /* 충분한 높이 설정 */
}

.accordion-header {
  cursor: pointer;
  background-color: #f2f2f2;
  padding: 10px;
  border: 1px solid #ddd;
  margin-bottom: -1px; /* 테두리 겹침 방지 */
}
.accordion-item:first-child .accordion-header { border-top-left-radius: 5px; border-top-right-radius: 5px; }
.accordion-item:last-child .accordion-header { border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
.accordion-item.active .accordion-header { background-color: #e0e0e0; }

// JavaScript
const accordionHeaders = document.querySelectorAll('.accordion-header');

accordionHeaders.forEach(header => {
  header.addEventListener('click', () => {
    const currentItem = header.parentNode;

    // 현재 클릭된 항목이 이미 활성화되어 있으면 비활성화
    if (currentItem.classList.contains('active')) {
      currentItem.classList.remove('active');
    } else {
      // 다른 모든 항목 비활성화
      document.querySelectorAll('.accordion-item.active').forEach(item => {
        item.classList.remove('active');
      });
      // 현재 항목 활성화
      currentItem.classList.add('active');
    }
  });
});

모달 (Modal)


<!-- HTML -->
<button id="openModalBtn">모달 열기</button>

<div id="myModal" class="modal">
  <div class="modal-content">
    <span class="close-button">×</span>
    <h2>환영합니다!</h2>
    <p>이것은 바닐라 자바스크립트 모달창입니다.</p>
  </div>
</div>

/* CSS */
.modal {
  display: none; /* 기본적으로 숨김 */
  position: fixed;
  z-index: 1;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0,0,0,0.4);
  /* 모달 애니메이션을 위한 트랜지션 */
  transition: opacity 0.3s ease-in-out;
  opacity: 0;
  pointer-events: none; /* 클릭 이벤트 막기 (초기 상태) */
}

.modal.show {
  opacity: 1;
  pointer-events: auto; /* 활성화 시 클릭 이벤트 허용 */
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto; /* 화면 중앙 배치 */
  padding: 20px;
  border: 1px solid #888;
  width: 80%;
  max-width: 500px;
  border-radius: 8px;
  position: relative;
  /* 모달 내용 애니메이션 */
  transform: translateY(-50px);
  transition: transform 0.3s ease-in-out;
}

.modal.show .modal-content {
  transform: translateY(0);
}

.close-button {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close-button:hover,
.close-button:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

// JavaScript
const openModalBtn = document.getElementById('openModalBtn');
const myModal = document.getElementById('myModal');
const closeButton = myModal.querySelector('.close-button');

// 모달 열기
openModalBtn.addEventListener('click', () => {
  myModal.style.display = 'block'; // 일단 보이게 하고
  setTimeout(() => { // 트랜지션을 위한 약간의 딜레이
    myModal.classList.add('show');
  }, 10);
});

// 모달 닫기 함수
function closeModal() {
  myModal.classList.remove('show');
  // 트랜지션 완료 후 display: none 처리
  myModal.addEventListener('transitionend', function handler() {
    myModal.style.display = 'none';
    myModal.removeEventListener('transitionend', handler); // 이벤트 리스너 제거
  });
}

// 닫기 버튼 클릭 시
closeButton.addEventListener('click', closeModal);

// 모달 외부 클릭 시
window.addEventListener('click', (event) => {
  if (event.target === myModal) {
    closeModal();
  }
});

이러한 예제들을 통해 바닐라 자바스크립트만으로도 웹 페이지의 CSS를 매우 유연하고 강력하게 제어할 수 있음을 보여드렸습니다. 각 기법의 장단점을 이해하고 상황에 맞게 적용하면 더욱 효율적이고 반응성 좋은 웹 애플리케이션을 만들 수 있습니다. 궁금한 점이 있으시다면 언제든지 질문해주세요!

댓글 없음:

댓글 쓰기

댓글 폭탄 해결! 자바스크립트 댓글 접기/펼치기로 가독성 200% 높이는 법(Solve Comment Chaos: Elevate Readability 200% with JS Comment Folding/Unfolding)

내 웹사이트에 적용! 초간단 자바스크립트 댓글 펼치기/숨기기 튜토리얼 내 웹사이트에 적용! 초간단 자바스크립트 댓글 펼치기/숨기기 튜토...