July 9, 2025

🐌 λ ˆμ΄μ•„μ›ƒμ„ κ°•μ œλ‘œ λ°œμƒμ‹œν‚€λŠ” CSS 속성 λ‚¨μš©: 뚝뚝 λŠκΈ°λŠ” μ• λ‹ˆλ©”μ΄μ…˜κ³Ό μ„±λŠ₯ μ €ν•˜

CSS
μ„±λŠ₯
UX
μ• λ‹ˆλ©”μ΄μ…˜/UI
λ Œλ”λ§μ „λž΅
μ›Ήν‘œμ€€

Summary

μž¦μ€ λ ˆμ΄μ•„μ›ƒ μž¬κ³„μ‚°κ³Ό 페인트 μž‘μ—…μ€ μ›Ή μ„±λŠ₯을 μ €ν•˜μ‹œν‚€κ³  UIλ₯Ό λ²„λ²…κ±°λ¦¬κ²Œ λ§Œλ“­λ‹ˆλ‹€. transformκ³Ό opacity 같은 속성을 μ‚¬μš©ν•˜μ—¬ λ ˆμ΄μ•„μ›ƒκ³Ό 페인트λ₯Ό κ±΄λ„ˆλ›°κ³  GPU 가속을 ν™œμš©ν•˜λŠ” 것이 λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜κ³Ό 졜적의 μ„±λŠ₯을 μœ„ν•œ ν•΅μ‹¬μž…λ‹ˆλ‹€.

Why Wrong?

CSS μ• λ‹ˆλ©”μ΄μ…˜μ΄λ‚˜ 동적인 UI λ³€ν™”λ₯Ό κ΅¬ν˜„ν•  λ•Œ left, top, width, height, margin, padding λ“± λ ˆμ΄μ•„μ›ƒμ„ μž¬κ³„μ‚°ν•˜κ³  페인트λ₯Ό μœ λ°œν•˜λŠ” 속성을 κ³Όλ„ν•˜κ²Œ μ‚¬μš©ν•˜λ©΄ λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ νŒŒμ΄ν”„λΌμΈμ—μ„œ Layout(λ ˆμ΄μ•„μ›ƒ) 및 Paint(페인트) 단계λ₯Ό κ°•μ œλ‘œ λ°œμƒμ‹œν‚΅λ‹ˆλ‹€. 이 두 λ‹¨κ³„λŠ” 맀우 λΉ„μš©μ΄ 많이 λ“œλŠ” μž‘μ—…μœΌλ‘œ, 특히 μ• λ‹ˆλ©”μ΄μ…˜μ΄λ‚˜ 슀크둀 μ΄λ²€νŠΈμ™€ 같이 λΉˆλ²ˆν•œ λ³€ν™”κ°€ 일어날 λ•Œλ§ˆλ‹€ 반볡되면 메인 μŠ€λ ˆλ“œλ₯Ό λΈ”λ‘ν•˜μ—¬ UIκ°€ 버벅거리고 '뚝뚝 λŠκΈ°λŠ”' λ“―ν•œ μ‚¬μš©μž κ²½ν—˜μ„ μ΄ˆλž˜ν•©λ‹ˆλ‹€. μ΄λŠ” **λ ˆμ΄μ•„μ›ƒ μŠ€λž˜μ‹±(Layout Thrashing)**으둜 이어져 μ „λ°˜μ μΈ μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ„±λŠ₯을 μ‹¬κ°ν•˜κ²Œ μ €ν•˜μ‹œν‚΅λ‹ˆλ‹€. λΈŒλΌμš°μ €λŠ” CSS 속성 λ³€κ²½ μ‹œ Style(μŠ€νƒ€μΌ 계산) β†’ Layout(λ ˆμ΄μ•„μ›ƒ 계산) β†’ Paint(페인트) β†’ Composite(ν•©μ„±)의 과정을 κ±°μΉ˜λŠ”λ°, leftλ‚˜ width와 같은 속성 변경은 λ ˆμ΄μ•„μ›ƒμ— 영ν–₯을 미쳐 μ•žμ„  Layout 및 Paint 단계λ₯Ό λ°˜λ“œμ‹œ 거치게 λ§Œλ“­λ‹ˆλ‹€. 반면, transformμ΄λ‚˜ opacity와 같은 속성듀은 λ ˆμ΄μ•„μ›ƒμ— 영ν–₯을 μ£Όμ§€ μ•Šμ•„ Layout 및 Paint 단계λ₯Ό κ±΄λ„ˆλ›°κ³  GPU 가속을 ν™œμš©ν•  수 μžˆλŠ” Composite λ‹¨κ³„μ—μ„œ μ²˜λ¦¬λ©λ‹ˆλ‹€.

How to Fix?

λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ νŒŒμ΄ν”„λΌμΈμ„ μ΄ν•΄ν•˜κ³ , μ• λ‹ˆλ©”μ΄μ…˜μ΄λ‚˜ 동적인 μœ„μΉ˜/크기 λ³€ν™”μ—λŠ” λΉ„μš©μ΄ 적게 λ“œλŠ” CSS 속성을 μš°μ„ μ μœΌλ‘œ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. 특히 transform (예: translateX, translateY, scale, rotate) 및 opacity 속성은 λ ˆμ΄μ•„μ›ƒκ³Ό 페인트 단계λ₯Ό κ±΄λ„ˆλ›°κ³  GPU 가속을 ν™œμš©ν•  수 μžˆλŠ” Composite(ν•©μ„±) λ‹¨κ³„μ—μ„œ μ²˜λ¦¬λ©λ‹ˆλ‹€. 이λ₯Ό 톡해 메인 μŠ€λ ˆλ“œμ˜ λΆ€ν•˜λ₯Ό 쀄이고 더 λΆ€λ“œλŸ½κ³  μ„±λŠ₯ μΉœν™”μ μΈ μ• λ‹ˆλ©”μ΄μ…˜κ³Ό UI λ³€ν™”λ₯Ό κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, will-change 속성을 μ‚¬μš©ν•˜μ—¬ λΈŒλΌμš°μ €μ—κ²Œ νŠΉμ • μš”μ†Œμ˜ 속성이 변경될 κ²ƒμž„μ„ 미리 μ•Œλ €μ£Όμ–΄ μ΅œμ ν™”λ₯Ό μœ λ„ν•  수 μžˆμ§€λ§Œ, λ‚¨μš©μ€ 였히렀 μ„±λŠ₯을 μ €ν•˜μ‹œν‚¬ 수 μžˆμœΌλ―€λ‘œ ν•„μš”ν•œ κ³³μ—λ§Œ μ‹ μ€‘ν•˜κ²Œ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Before Code (Bad)

/* Bad: 'left'와 'top'은 λ ˆμ΄μ•„μ›ƒ μž¬κ³„μ‚°μ„ μœ λ°œν•©λ‹ˆλ‹€. */
.moving-box {
  position: absolute;
  left: 0;
  top: 0;
  transition: left 0.3s ease, top 0.3s ease;
}

.moving-box.animate {
  left: 100px;
  top: 50px;
}

/* Bad: 'width'와 'height' 변경도 λ ˆμ΄μ•„μ›ƒ μž¬κ³„μ‚°μ„ μœ λ°œν•©λ‹ˆλ‹€. */
.resizing-element {
  width: 100px;
  height: 100px;
  transition: width 0.3s ease, height 0.3s ease;
}

.resizing-element.enlarge {
  width: 200px;
  height: 200px;
}

After Code (Good)

/* Good: 'transform'은 λ ˆμ΄μ•„μ›ƒ μž¬κ³„μ‚° 없이 Composite λ‹¨κ³„μ—μ„œ μ²˜λ¦¬λ©λ‹ˆλ‹€. */
.moving-box {
  transform: translate(0, 0);
  transition: transform 0.3s ease;
}

.moving-box.animate {
  transform: translate(100px, 50px);
}

/* Good: 'transform'의 scale을 μ‚¬μš©ν•˜μ—¬ 크기 λ³€ν™”λ₯Ό μ΅œμ ν™”ν•©λ‹ˆλ‹€. */
.resizing-element {
  transform: scale(1);
  transition: transform 0.3s ease;
}

.resizing-element.enlarge {
  transform: scale(2);
}

/* Good: μ• λ‹ˆλ©”μ΄μ…˜μ΄ 일어날 속성에 will-changeλ₯Ό μ μš©ν•˜μ—¬ λΈŒλΌμš°μ €μ— 힌트λ₯Ό μ€λ‹ˆλ‹€. */
/* 주의: λ‚¨μš© μ‹œ 였히렀 μ„±λŠ₯ μ €ν•˜λ₯Ό μœ λ°œν•  수 μžˆμœΌλ―€λ‘œ ν•„μš”ν•œ κ³³μ—λ§Œ μ‹ μ€‘ν•˜κ²Œ μ‚¬μš©ν•©λ‹ˆλ‹€. */
.moving-box,
.resizing-element {
  will-change: transform;
}