June 25, 2025

πŸš€ λ ˆμ΄μ•„μ›ƒμ„ μœ λ°œν•˜λŠ” CSS μ• λ‹ˆλ©”μ΄μ…˜

CSS
μ„±λŠ₯
μ• λ‹ˆλ©”μ΄μ…˜/UI

Summary

CSS μ• λ‹ˆλ©”μ΄μ…˜ μ‹œ width, height, top, left λ“± λ ˆμ΄μ•„μ›ƒμ„ μœ λ°œν•˜λŠ” 속성 λŒ€μ‹  transformκ³Ό opacityλ₯Ό μ‚¬μš©ν•˜μ—¬ μ„±λŠ₯ μ €ν•˜ 없이 λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Why Wrong?

ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ μ• λ‹ˆλ©”μ΄μ…˜ μ„±λŠ₯은 μ‚¬μš©μž κ²½ν—˜μ— 큰 영ν–₯을 λ―ΈμΉ©λ‹ˆλ‹€. CSS μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•  λ•Œ width, height, top, left, margin, paddingκ³Ό 같이 **λ ˆμ΄μ•„μ›ƒ(Layout)**을 μœ λ°œν•˜λŠ” 속성을 μ• λ‹ˆλ©”μ΄μ…˜ν•˜λŠ” 것은 λŒ€ν‘œμ μΈ μ•ˆν‹°νŒ¨ν„΄μž…λ‹ˆλ‹€.

λΈŒλΌμš°μ €λŠ” μ›Ή νŽ˜μ΄μ§€λ₯Ό λ Œλ”λ§ν•  λ•Œ λ‹€μŒκ³Ό 같은 단계λ₯Ό κ±°μΉ©λ‹ˆλ‹€:

  1. Style (μŠ€νƒ€μΌ 계산): CSS μŠ€νƒ€μΌμ„ μš”μ†Œμ— μ μš©ν•©λ‹ˆλ‹€.
  2. Layout (λ ˆμ΄μ•„μ›ƒ / Reflow): 각 μš”μ†Œμ˜ 크기와 μœ„μΉ˜λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€. width, height, top, left λ“± κΈ°ν•˜ν•™μ  속성이 λ³€κ²½λ˜λ©΄ 이 단계가 λ‹€μ‹œ λ°œμƒν•˜λ©°, μ’…μ†λœ λͺ¨λ“  μš”μ†Œλ“€μ˜ μœ„μΉ˜λ₯Ό μž¬κ³„μ‚°ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ΄λŠ” CPUλ₯Ό 많이 μ‚¬μš©ν•˜λŠ” κ³ λΉ„μš© μž‘μ—…μž…λ‹ˆλ‹€.
  3. Paint (페인트 / Repaint): μš”μ†Œμ˜ λ°°κ²½, 색상, 그림자 λ“± μ‹œκ°μ  뢀뢄을 ν”½μ…€λ‘œ μ±„μ›λ‹ˆλ‹€.
  4. Composite (ν•©μ„±): 각각의 λ ˆμ΄μ–΄λ₯Ό κ²°ν•©ν•˜μ—¬ μ΅œμ’… 화면을 λ§Œλ“­λ‹ˆλ‹€.

width, height, left, top λ“±μ˜ 속성을 μ• λ‹ˆλ©”μ΄μ…˜ν•˜λ©΄ λ§€ ν”„λ ˆμž„λ§ˆλ‹€ Layout 단계가 κ°•μ œλ‘œ μž¬μ‹€ν–‰λ©λ‹ˆλ‹€. 이λ₯Ό 'λ ˆμ΄μ•„μ›ƒ μŠ€λž˜μ‹±(Layout Thrashing)'이라고 λΆ€λ₯΄λ©°, 특히 λ³΅μž‘ν•œ νŽ˜μ΄μ§€μ—μ„œλŠ” **μ„±λŠ₯ μ €ν•˜(Jank, 버벅거림)**와 ν”„λ ˆμž„ λ“œλ‘­μ„ μœ λ°œν•˜μ—¬ λΆ€μžμ—°μŠ€λŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜μ²˜λŸΌ 보이게 λ©λ‹ˆλ‹€.

반면 transform (예: translateX, translateY, scale, rotate)μ΄λ‚˜ opacity와 같은 속성은 Layoutμ΄λ‚˜ Paint 단계λ₯Ό κ±΄λ„ˆλ›°κ³  λŒ€λΆ€λΆ„ Composite λ‹¨κ³„μ—μ„œ GPU의 도움을 λ°›μ•„ 직접 처리될 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ νŒŒμ΄ν”„λΌμΈμ—μ„œ κ°€μž₯ 효율적인 경둜이며, λΆ€λ“œλŸ¬μš΄ 60fps μ• λ‹ˆλ©”μ΄μ…˜μ„ κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

How to Fix?

width, height, top, left λŒ€μ‹  transform 속성을 μ‚¬μš©ν•˜μ—¬ μš”μ†Œμ˜ ν¬κΈ°λ‚˜ μœ„μΉ˜λ₯Ό λ³€κ²½ν•΄μ•Ό ν•©λ‹ˆλ‹€. opacityλŠ” νŽ˜μ΄λ“œ 인/아웃 νš¨κ³Όμ— μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 속성듀은 λ ˆμ΄μ•„μ›ƒμ„ μž¬κ³„μ‚°ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ 훨씬 높은 μ„±λŠ₯을 κΈ°λŒ€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • μœ„μΉ˜ λ³€κ²½: left/top λŒ€μ‹  transform: translateX(), translateY() μ‚¬μš©.
  • 크기 λ³€κ²½: width/height λŒ€μ‹  transform: scale() μ‚¬μš©.
  • νŽ˜μ΄λ“œ 효과: opacity μ‚¬μš©.

λ˜ν•œ, μ• λ‹ˆλ©”μ΄μ…˜μ΄ 적용될 μš”μ†Œμ— will-change CSS 속성을 μ‚¬μš©ν•˜μ—¬ λΈŒλΌμš°μ €μ— ν•΄λ‹Ή μš”μ†Œμ˜ νŠΉμ • 속성이 변경될 κ²ƒμž„μ„ 미리 μ•Œλ €μ£ΌλŠ” 것도 μ„±λŠ₯ μ΅œμ ν™”μ— 도움이 될 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ will-changeλŠ” λ‚¨μš©λ  경우 였히렀 μ„±λŠ₯ μ €ν•˜λ₯Ό μΌμœΌν‚¬ 수 μžˆμœΌλ―€λ‘œ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‹ μ€‘ν•˜κ²Œ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Before Code (Bad)

.animated-box {
  width: 100px;
  height: 100px;
  background-color: dodgerblue;
  position: relative;
  animation: slide-left-bad 2s infinite alternate;
}

@keyframes slide-left-bad {
  from { left: 0; }
  to { left: 200px; }
}

.resizable-box {
  width: 50px;
  height: 50px;
  background-color: tomato;
  animation: resize-bad 2s infinite alternate;
}

@keyframes resize-bad {
  from { width: 50px; height: 50px; }
  to { width: 200px; height: 200px; }
}
```
```html
<div class="animated-box"></div>
<div class="resizable-box"></div>

After Code (Good)

.animated-box {
  width: 100px; /* κ³ μ • κ°’ */
  height: 100px; /* κ³ μ • κ°’ */
  background-color: dodgerblue;
  /* position: relative; */ /* transform μ‚¬μš© μ‹œ λΆˆν•„μš” */
  animation: slide-right-good 2s infinite alternate;
  /* will-change: transform; /* ν•„μš”μ‹œ μΆ”κ°€ */
}

@keyframes slide-right-good {
  from { transform: translateX(0); }
  to { transform: translateX(200px); }
}

.resizable-box {
  width: 50px; /* κ³ μ • κ°’ */
  height: 50px; /* κ³ μ • κ°’ */
  background-color: tomato;
  animation: resize-good 2s infinite alternate;
  /* will-change: transform; /* ν•„μš”μ‹œ μΆ”κ°€ */
}

@keyframes resize-good {
  from { transform: scale(1); }
  to { transform: scale(4); } /* 50px * 4 = 200px */
}

.fade-element {
  background-color: lightgreen;
  width: 100px;
  height: 100px;
  animation: fade-good 2s infinite alternate;
  /* will-change: opacity; /* ν•„μš”μ‹œ μΆ”κ°€ */
}

@keyframes fade-good {
  from { opacity: 0; }
  to { opacity: 1; }
}
```
```html
<div class="animated-box"></div>
<div class="resizable-box"></div>
<div class="fade-element"></div>