July 18, 2025

๐Ÿ“ JavaScript๋กœ ๊ฐ•์ œ๋˜๋Š” ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐ: ์„ฑ๋Šฅ ์ €ํ•˜์™€ ์œ ์ง€๋ณด์ˆ˜์˜ ๋Šช

JavaScript
CSS
์„ฑ๋Šฅ
UX
์•„ํ‚คํ…์ฒ˜
์ปดํฌ๋„ŒํŠธ
์• ๋‹ˆ๋ฉ”์ด์…˜/UI

Summary

JavaScript๋ฅผ ์‚ฌ์šฉํ•ด UI์˜ ํฌ๊ธฐ์™€ ์œ„์น˜๋ฅผ ์ง์ ‘ ๊ณ„์‚ฐํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ๊ฒƒ์€ ์žฆ์€ ๋ ˆ์ด์•„์›ƒ ์žฌ๊ณ„์‚ฐ์œผ๋กœ ์„ฑ๋Šฅ์„ ์ €ํ•˜์‹œํ‚ค๊ณ  ์ฝ”๋“œ ๋ณต์žก์„ฑ์„ ๋†’์—ฌ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. CSS์˜ ๋ ˆ์ด์•„์›ƒ ๊ธฐ๋Šฅ๊ณผ ํด๋ž˜์Šค ํ† ๊ธ€๋ง์„ ํ™œ์šฉํ•˜์—ฌ ์ฑ…์ž„ ๋ถ„๋ฆฌ ์›์น™์„ ์ง€ํ‚ค๊ณ , ํ•„์š”์‹œ CSS Custom Properties๋‚˜ requestAnimationFrame์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Why Wrong?

JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UI ์š”์†Œ์˜ ํฌ๊ธฐ๋‚˜ ์œ„์น˜๋ฅผ ๋™์ ์œผ๋กœ ๊ณ„์‚ฐํ•˜๊ณ  ์ง์ ‘ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•œ ๋ ˆ์ด์•„์›ƒ ์žฌ๊ณ„์‚ฐ(Reflow)๊ณผ ํ™”๋ฉด ์žฌ๊ทธ๋ฆฌ๊ธฐ(Repaint)๋ฅผ ๋นˆ๋ฒˆํ•˜๊ฒŒ ์œ ๋ฐœํ•˜์—ฌ ์›น ์„ฑ๋Šฅ์„ ์‹ฌ๊ฐํ•˜๊ฒŒ ์ €ํ•˜์‹œํ‚ต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋Š” DOM ์š”์†Œ์˜ ํฌ๊ธฐ๋‚˜ ์œ„์น˜๋ฅผ JavaScript๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ offsetWidth, offsetHeight์™€ ๊ฐ™์€ ๊ณ„์‚ฐ๋œ ์Šคํƒ€์ผ ๊ฐ’์„ ์ฝ์„ ๋•Œ๋งˆ๋‹ค ๋ ˆ์ด์•„์›ƒ์„ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๊ฒŒ ๋˜๋ฉฐ, ์ด๋Š” ํŠนํžˆ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋‚˜ ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ๊ฐ™์ด ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์—์„œ ์น˜๋ช…์ ์ธ ๋ณ‘๋ชฉ ํ˜„์ƒ์„ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ์„ ์ฆ๊ฐ€์‹œ์ผœ UI์˜ ์‹œ๊ฐ์  ์š”์†Œ๊ฐ€ JavaScript ๋กœ์ง์— ์„ž์ด๊ฒŒ ๋˜๊ณ , ์ด๋Š” ์ฝ”๋“œ์˜ ์‘์ง‘๋„๋ฅผ ๋–จ์–ด๋œจ๋ฆฌ๊ณ  HTML, CSS, JavaScript ํŒŒ์ผ์„ ๋ชจ๋‘ ํ™•์ธํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์„ ๋งŒ๋“ค์–ด ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์„ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค. CSS๋Š” ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐ๊ณผ ์ตœ์ ํ™”์— ํŠนํ™”๋˜์–ด ์žˆ์œผ๋ฉฐ, GPU ๊ฐ€์†์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์†์„ฑ(์˜ˆ: transform, opacity)์„ ํ†ตํ•ด ๋” ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JavaScript๋กœ ๋ ˆ์ด์•„์›ƒ์„ ๊ฐ•์ œํ•˜๋Š” ๊ฒƒ์€ ์›น ํ‘œ์ค€์˜ ์ฑ…์ž„ ๋ถ„๋ฆฌ ์›์น™(HTML์€ ๊ตฌ์กฐ, CSS๋Š” ํ‘œํ˜„, JavaScript๋Š” ๋™์ž‘)์„ ์œ„๋ฐฐํ•˜์—ฌ ์ฝ”๋“œ์˜ ๋ช…ํ™•์„ฑ์„ ํ•ด์น˜๊ณ  ํ˜‘์—…์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

How to Fix?

๋Œ€๋ถ€๋ถ„์˜ ๋ ˆ์ด์•„์›ƒ๊ณผ ๊ธฐ๋ณธ์ ์ธ ์Šคํƒ€์ผ๋ง์€ ํ•ญ์ƒ CSS๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Flexbox, Grid์™€ ๊ฐ™์€ ๊ฐ•๋ ฅํ•œ CSS ๋ ˆ์ด์•„์›ƒ ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐ˜์‘ํ˜• ๋ฐ ๋™์ ์ธ UI๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ๊ตฌ์ถ•ํ•˜๊ณ , vw, vh, rem, em๊ณผ ๊ฐ™์€ ์ƒ๋Œ€ ๋‹จ์œ„๋กœ ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์— ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์‘ํ•ฉ๋‹ˆ๋‹ค. JavaScript๋Š” UI ์š”์†Œ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ CSS ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์Šคํƒ€์ผ์„ ์ œ์–ดํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, active ํด๋ž˜์Šค๋ฅผ ํ† ๊ธ€ํ•˜์—ฌ ํŠน์ • UI ์š”์†Œ์˜ ํ‘œ์‹œ ์—ฌ๋ถ€๋‚˜ ์ƒํƒœ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ์Šคํƒ€์ผ ๋ณ€๊ฒฝ์„ CSS์—์„œ ์ •์˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•  ์Šคํƒ€์ผ ๊ฐ’(์˜ˆ: ํ…Œ๋งˆ ์ƒ‰์ƒ)์ด ์žˆ๋‹ค๋ฉด, JavaScript์—์„œ CSS Custom Properties(CSS ๋ณ€์ˆ˜)๋ฅผ ์ œ์–ดํ•˜์—ฌ ์Šคํƒ€์ผ์˜ ์ผ๊ด€์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ •๋ง๋กœ JavaScript๋กœ ์‹œ๊ฐ์  ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ณ ์„ฑ๋Šฅ ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ๊ฒฝ์šฐ์—๋„, requestAnimationFrame์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €์˜ ๋‹ค์Œ ๋ฆฌํŽ˜์ธํŠธ ์‹œ์ ์— ๋งž์ถฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์˜ˆ์•ฝํ•จ์œผ๋กœ์จ ๋ถˆํ•„์š”ํ•œ ๋ ˆ์ด์•„์›ƒ ์žฌ๊ณ„์‚ฐ์„ ์ตœ์†Œํ™”ํ•˜๊ณ  ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” CSS ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๋Œ€์ฒด ๋ถˆ๊ฐ€๋Šฅํ•œ ์ƒํ™ฉ์—๋งŒ ๊ตญํ•œ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Before Code (Bad)

// HTML: <div id="myElement" style="position: absolute;"></div>

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

function updatePositionAndSize() {
  // ๋ทฐํฌํŠธ ๋„ˆ๋น„์˜ 10%๋งŒํผ ์™ผ์ชฝ์œผ๋กœ ์ด๋™ (์žฆ์€ Reflow ๋ฐœ์ƒ)
  const newLeft = window.innerWidth * 0.1;
  myElement.style.left = `${newLeft}px`;

  // ๋ทฐํฌํŠธ ๋†’์ด์˜ 20%๋งŒํผ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ด๋™ (์žฆ์€ Reflow ๋ฐœ์ƒ)
  const newTop = window.innerHeight * 0.2;
  myElement.style.top = `${newTop}px`;

  // ๋ทฐํฌํŠธ ๋„ˆ๋น„์˜ 50%๋งŒํผ ๋„ˆ๋น„ ์„ค์ • (์žฆ์€ Reflow ๋ฐœ์ƒ)
  const newWidth = window.innerWidth * 0.5;
  myElement.style.width = `${newWidth}px`;

  // ๊ณ„์‚ฐ๋œ ์Šคํƒ€์ผ ๊ฐ’ ์ฝ๊ธฐ (๊ฐ•์ œ Reflow ๋ฐœ์ƒ)
  console.log('Current offsetWidth:', myElement.offsetWidth);
}

// ์œˆ๋„์šฐ ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ๋งˆ๋‹ค ์œ„์น˜ ๋ฐ ํฌ๊ธฐ ์žฌ๊ณ„์‚ฐ
window.addEventListener('resize', updatePositionAndSize);
updatePositionAndSize(); // ์ดˆ๊ธฐ ์„ค์ •

After Code (Good)

/* CSS: ๋ ˆ์ด์•„์›ƒ ๋ฐ ๋ฐ˜์‘ํ˜• ์Šคํƒ€์ผ์€ CSS๋กœ ์ •์˜ */
#myElement {
  width: 50vw; /* ๋ทฐํฌํŠธ ๋„ˆ๋น„์˜ 50% */
  height: 20vh; /* ๋ทฐํฌํŠธ ๋†’์ด์˜ 20% */
  background-color: lightblue;
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  left: 50%;
  top: 50%;
  /* transform์€ ๋ ˆ์ด์•„์›ƒ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  GPU ๊ฐ€์†์ด ๊ฐ€๋Šฅํ•˜์—ฌ ์„ฑ๋Šฅ ์šฐ์ˆ˜ */
  transform: translate(-50%, -50%);
  transition: transform 0.3s ease-out, background-color 0.3s ease-out;
}

/* JavaScript๋กœ ํ† ๊ธ€๋  ์ƒํƒœ ํด๋ž˜์Šค */
#myElement.active {
  transform: translate(0%, -50%); /* ์™ผ์ชฝ์œผ๋กœ 50% ์ด๋™ */
  background-color: coral;
}

/* JavaScript: ์ƒํƒœ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ํด๋ž˜์Šค ์ œ์–ด */
// HTML: <button id="toggleButton">์œ„์น˜ ๋ฐ ์ƒ‰์ƒ ๋ณ€๊ฒฝ</button>
const myElement = document.getElementById('myElement');
const toggleButton = document.getElementById('toggleButton');

toggleButton.addEventListener('click', () => {
  myElement.classList.toggle('active');
});