[TECH-QA] λ¦¬ν”Œλ‘œμš°(Reflow)와 리페인트(Repaint)λŠ” λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ κ³Όμ •

λ¦¬ν”Œλ‘œμš°(Reflow)와 리페인트(Repaint)λŠ” λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ κ³Όμ •μ—μ„œ λ°œμƒν•˜λŠ” 두 κ°€μ§€ μ€‘μš”ν•œ λ‹¨κ³„λ‘œ, μ›Ή νŽ˜μ΄μ§€μ˜ μ‹œκ°μ  μ—…λ°μ΄νŠΈμ™€ 관련이 μžˆμŠ΅λ‹ˆλ‹€. 이듀은 DOMμ΄λ‚˜ CSS의 λ³€κ²½μœΌλ‘œ 인해 μš”μ†Œμ˜ λ ˆμ΄μ•„μ›ƒμ΄λ‚˜ μŠ€νƒ€μΌμ΄ μˆ˜μ •λ  λ•Œ λΈŒλΌμš°μ €κ°€ 화면을 λ‹€μ‹œ κ·Έλ¦¬λŠ” κ³Όμ •μ—μ„œ λ°œμƒν•©λ‹ˆλ‹€. λ¨Όμ € λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ 과정을 μ„€λͺ…ν•˜κ² μŠ΅λ‹ˆλ‹€. λΈŒλΌμš°μ € λ Œλ”λ§μ€ μ›Ή νŽ˜μ΄μ§€λ₯Ό 화면에 ν‘œμ‹œν•˜κΈ° μœ„ν•΄ μ—¬λŸ¬ 단계λ₯Ό κ±°μΉ©λ‹ˆλ‹€. μ•„λž˜λŠ” μ£Όμš” 단계λ₯Ό κ°„λ‹¨νžˆ μ„€λͺ…ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.

λΈŒλΌμš°μ € λ Œλ”λ§ κ³Όμ •

πŸ“ DOM 및 CSSOM 생성

  • HTML을 νŒŒμ‹±ν•˜μ—¬ DOM(Document Object Model) 트리λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
  • CSSλ₯Ό νŒŒμ‹±ν•˜μ—¬ CSSOM(CSS Object Model) 트리λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

πŸ“ λ Œλ” 트리(Render Tree) 생성

  • DOMκ³Ό CSSOM을 κ²°ν•©ν•˜μ—¬ μ‹€μ œ 화면에 ν‘œμ‹œν•  μš”μ†Œλ“€λ‘œ κ΅¬μ„±λœ λ Œλ” 트리λ₯Ό λ§Œλ“­λ‹ˆλ‹€. ν‘œμ‹œλ˜μ§€ μ•ŠλŠ” μš”μ†Œ(예: display: none)λŠ” ν¬ν•¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

πŸ“ λ ˆμ΄μ•„μ›ƒ(Layout, Reflow)

  • λ Œλ” 트리의 각 μš”μ†Œμ˜ μœ„μΉ˜μ™€ 크기λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ 뷰포트 크기, μš”μ†Œμ˜ μŠ€νƒ€μΌ 등이 λ°˜μ˜λ©λ‹ˆλ‹€.

πŸ“ 페인트(Paint, Repaint)

κ³„μ‚°λœ λ Œλ” 트리λ₯Ό 기반으둜 ν”½μ…€ λ‹¨μœ„λ‘œ 화면에 κ·Έλ¦¬λŠ” 과정을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. 색상, 이미지, ν…μŠ€νŠΈ 등이 ν¬ν•¨λ©λ‹ˆλ‹€.

πŸ“ μ»΄ν¬μ§€νŒ…(Compositing)

페인트된 λ ˆμ΄μ–΄λ₯Ό ν•©μ„±ν•˜μ—¬ μ΅œμ’… 화면을 λ Œλ”λ§ν•©λ‹ˆλ‹€. GPUλ₯Ό ν™œμš©ν•˜μ—¬ λ ˆμ΄μ–΄ 이동, λ³€ν˜•(예: transform: translateX(100px)) 등을 효율적으둜 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

λ¦¬ν”Œλ‘œμš°(Reflow)λž€?

λ¦¬ν”Œλ‘œμš°λŠ” λΈŒλΌμš°μ €κ°€ μš”μ†Œμ˜ λ ˆμ΄μ•„μ›ƒ(크기, μœ„μΉ˜ λ“±)을 λ‹€μ‹œ κ³„μ‚°ν•˜λŠ” κ³Όμ •μž…λ‹ˆλ‹€. μ΄λŠ” λ Œλ” 트리의 κ΅¬μ‘°λ‚˜ μš”μ†Œμ˜ κΈ°ν•˜ν•™μ  속성(예: λ„ˆλΉ„, 높이, μœ„μΉ˜)이 변경될 λ•Œ λ°œμƒν•©λ‹ˆλ‹€. λ¦¬ν”Œλ‘œμš°λŠ” λ ˆμ΄μ•„μ›ƒ λ‹¨κ³„μ—μ„œ μˆ˜ν–‰λ˜λ©°, λ³€κ²½λœ μš”μ†ŒλΏλ§Œ μ•„λ‹ˆλΌ 그에 영ν–₯을 λ°›λŠ” λ‹€λ₯Έ μš”μ†Œ(예: λΆ€λͺ¨, μžμ‹, ν˜•μ œ μš”μ†Œ)의 λ ˆμ΄μ•„μ›ƒλ„ μž¬κ³„μ‚°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ¦¬ν”Œλ‘œμš°μ˜ λΉ„μš©

λ¦¬ν”Œλ‘œμš°λŠ” κ³„μ‚°λŸ‰μ΄ λ§Žμ•„ μ„±λŠ₯에 큰 영ν–₯을 λ―ΈμΉ©λ‹ˆλ‹€. 특히, λ³€κ²½λœ μš”μ†Œκ°€ λ‹€λ₯Έ μš”μ†Œ(예: λΆ€λͺ¨λ‚˜ μžμ‹)에 영ν–₯을 미치면 전체 λ Œλ” 트리 λ˜λŠ” 큰 뢀뢄을 μž¬κ³„μ‚°ν•΄μ•Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€. νŽ˜μ΄μ§€κ°€ λ³΅μž‘ν•˜κ±°λ‚˜ μš”μ†Œκ°€ λ§Žμ„μˆ˜λ‘ λ¦¬ν”Œλ‘œμš° λΉ„μš©μ΄ μ¦κ°€ν•©λ‹ˆλ‹€.
  • κΈ°ν•˜ν•™μ  속성 λ³€κ²½: width, height, margin, padding, border, top, left, position λ“±.
  • λ°•μŠ€ λͺ¨λΈ κ΄€λ ¨ 속성 λ³€κ²½: box-sizing, display (예: block β†’ none).

리페인트(Repaint)λž€?

λ¦¬νŽ˜μΈνŠΈλŠ” μš”μ†Œμ˜ μ‹œκ°μ  μŠ€νƒ€μΌ(예: 색상, λ°°κ²½, 그림자 λ“±)이 λ³€κ²½λ˜μ—ˆμ„ λ•Œ, λΈŒλΌμš°μ €κ°€ ν•΄λ‹Ή μš”μ†Œλ₯Ό 화면에 λ‹€μ‹œ κ·Έλ¦¬λŠ” κ³Όμ •μž…λ‹ˆλ‹€. λ¦¬νŽ˜μΈνŠΈλŠ” νŽ˜μΈνŒ… λ‹¨κ³„μ—μ„œ μˆ˜ν–‰λ˜λ©°, λ ˆμ΄μ•„μ›ƒ(예: ν¬κΈ°λ‚˜ μœ„μΉ˜) λ³€κ²½ 없이 μŠ€νƒ€μΌλ§Œ μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.
  • color, background-color, box-shadow, border-color, visibility λ“±.
  • opacity λ³€κ²½ (단, GPU 가속이 적용되면 리페인트λ₯Ό ν”Όν•  수 있음).

리페인트의 λΉ„μš©

  • λ¦¬νŽ˜μΈνŠΈλŠ” λ¦¬ν”Œλ‘œμš°λ³΄λ‹€ 계산 λΉ„μš©μ΄ μ μŠ΅λ‹ˆλ‹€. λ ˆμ΄μ•„μ›ƒμ„ μž¬κ³„μ‚°ν•˜μ§€ μ•Šκ³  ν”½μ…€λ§Œ λ‹€μ‹œ 그리기 λ•Œλ¬Έμž…λ‹ˆλ‹€.
κ·ΈλŸ¬λ‚˜ νŽ˜μ΄μ§€μ— λ§Žμ€ μš”μ†Œκ°€ μžˆκ±°λ‚˜ λ³΅μž‘ν•œ μŠ€νƒ€μΌ(예: 그림자, κ·ΈλΌλ””μ–ΈνŠΈ)이 적용된 경우 λ¦¬νŽ˜μΈνŠΈλ„ μ„±λŠ₯에 영ν–₯을 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.

λ¦¬ν”Œλ‘œμš°μ™€ 리페인트의 관계

  • λ¦¬ν”Œλ‘œμš° β†’ 리페인트: λ¦¬ν”Œλ‘œμš°λŠ” λ ˆμ΄μ•„μ›ƒμ„ λ³€κ²½ν•˜λ―€λ‘œ, λ ˆμ΄μ•„μ›ƒμ΄ 바뀐 μš”μ†ŒλŠ” λ‹€μ‹œ κ·Έλ €μ Έμ•Ό ν•˜λ―€λ‘œ λ¦¬νŽ˜μΈνŠΈκ°€ λ°œμƒν•©λ‹ˆλ‹€.
  • 리페인트만 λ°œμƒ: λ ˆμ΄μ•„μ›ƒμ— 영ν–₯을 μ£Όμ§€ μ•ŠλŠ” μŠ€νƒ€μΌ λ³€κ²½(예: color, background)은 리페인트만 μœ λ°œν•©λ‹ˆλ‹€.
  • μ»΄ν¬μ§€νŒ…: ν˜„λŒ€ λΈŒλΌμš°μ €μ—μ„œλŠ” transformμ΄λ‚˜ opacity 같은 속성 변경은 λ ˆμ΄μ•„μ›ƒμ΄λ‚˜ νŽ˜μΈνŒ… 없이 μ»΄ν¬μ§€νŒ… λ‹¨κ³„μ—μ„œ μ²˜λ¦¬λ©λ‹ˆλ‹€. μ΄λŠ” λ¦¬ν”Œλ‘œμš°μ™€ 리페인트λ₯Ό λͺ¨λ‘ ν”Όν•  수 μžˆμ–΄ μ„±λŠ₯이 λ›°μ–΄λ‚©λ‹ˆλ‹€.

πŸ“ λ¦¬ν”Œλ‘œμš°μ™€ 리페인트의 μ˜ˆμ‹œ

λ‹€μŒμ€ React μ»΄ν¬λ„ŒνŠΈμ—μ„œ λ²„νŠΌ 클릭 μ‹œ λ°•μŠ€μ˜ μŠ€νƒ€μΌμ„ λ³€κ²½ν•˜μ—¬ λ¦¬ν”Œλ‘œμš°μ™€ 리페인트λ₯Ό μœ λ°œν•˜λŠ” μ˜ˆμ œμž…λ‹ˆλ‹€.
// src/App.jsx
				
import { useState } from 'react';

function App() {
  const [boxStyle, setBoxStyle] = useState({
    width: '200px',
    height: '100px',
    margin: '20px',
    backgroundColor: 'lightblue',
  });

  const changeStyle = () => {
    setBoxStyle({
      width: '300px', // λ¦¬ν”Œλ‘œμš° 유발 (λ ˆμ΄μ•„μ›ƒ λ³€κ²½)
      height: '150px', // λ¦¬ν”Œλ‘œμš° 유발
      margin: '30px', // λ¦¬ν”Œλ‘œμš° 유발
      backgroundColor: 'lightgreen', // 리페인트 유발
    });
  };

  return (
    <div>
      <h1>λ¦¬ν”Œλ‘œμš°μ™€ 리페인트 예제</h1>
      <div style={boxStyle}>
        ν…ŒμŠ€νŠΈ λ°•μŠ€
      </div>
      <button onClick={changeStyle}>μŠ€νƒ€μΌ λ³€κ²½</button>
    </div>
  );
}

export default App;
  • λ¦¬ν”Œλ‘œμš° 유발 : width, height, margin 변경은 μš”μ†Œμ˜ λ ˆμ΄μ•„μ›ƒμ„ μˆ˜μ •ν•˜λ―€λ‘œ λ¦¬ν”Œλ‘œμš°κ°€ λ°œμƒν•©λ‹ˆλ‹€. μ΄λŠ” λ°•μŠ€ λͺ¨λΈμ˜ 크기와 μœ„μΉ˜λ₯Ό μž¬κ³„μ‚°ν•©λ‹ˆλ‹€.
  • 리페인트 유발 : backgroundColor 변경은 λ ˆμ΄μ•„μ›ƒμ— 영ν–₯을 μ£Όμ§€ μ•ŠμœΌλ―€λ‘œ 리페인트만 λ°œμƒν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ μœ„μ—μ„œ λ¦¬ν”Œλ‘œμš°κ°€ λ°œμƒν–ˆμœΌλ―€λ‘œ λ¦¬νŽ˜μΈνŠΈλŠ” ν¬ν•¨λ©λ‹ˆλ‹€.
  • 문제점
    - μ—¬λŸ¬ μŠ€νƒ€μΌ 변경이 κ°œλ³„μ μœΌλ‘œ 적용되면 React의 μƒνƒœ μ—…λ°μ΄νŠΈλ‘œ 인해 λΆˆν•„μš”ν•œ λ¦¬λ Œλ”λ§μ΄ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    - λ ˆμ΄μ•„μ›ƒ 변경은 μ„±λŠ₯ λΉ„μš©μ΄ ν¬λ―€λ‘œ λΉˆλ²ˆν•œ λ³€κ²½ μ‹œ μ‚¬μš©μž κ²½ν—˜μ΄ μ €ν•˜λ  수 μžˆμŠ΅λ‹ˆλ‹€.

μ»΄ν¬μ§€νŒ… ν™œμš© 및 React μ΅œμ ν™”

이 μ˜ˆμ œλŠ” λ¦¬ν”Œλ‘œμš°μ™€ 리페인트λ₯Ό μ΅œμ†Œν™”ν•˜κΈ° μœ„ν•΄ transformκ³Ό 같은 μ»΄ν¬μ§€νŒ… 속성을 μ‚¬μš© ν•˜κ³ , React의 λ Œλ”λ§ μ΅œμ ν™” 기법을 μ μš©ν•©λ‹ˆλ‹€.
// src/App.jsx

import { useState, useCallback } from 'react';
import './App.css';

function App() {
  const [isTransformed, setIsTransformed] = useState(false);
  const [isColorChanged, setIsColorChanged] = useState(false);

  const toggleTransform = useCallback(() => {
    setIsTransformed((prev) => !prev);
  }, []);

  const toggleColor = useCallback(() => {
    setIsColorChanged((prev) => !prev);
  }, []);

  return (
    <div>
      <h1>μ΅œμ ν™”λœ λ¦¬ν”Œλ‘œμš°/리페인트 예제</h1>
      <div
        className={"box ${isTransformed ? "transformed" : ""} ${
          isColorChanged ? "color-changed" : ""
        }"}
      >
        ν…ŒμŠ€νŠΈ λ°•μŠ€
      </div>
      <button onClick={toggleTransform}>μœ„μΉ˜ 이동 (Transform)</button>
      <button onClick={toggleColor}>색상 λ³€κ²½</button>
    </div>
  );
}

export default App;
/* src/App.css */
.box {
  width: 200px;
  height: 100px;
  margin: 20px;
  background-color: lightblue;
  transition: transform 0.3s ease, background-color 0.3s ease;
}

.transformed {
  transform: translateX(100px); /* μ»΄ν¬μ§€νŒ…λ§Œ 유발 */
}

.color-changed {
  background-color: lightgreen; /* 리페인트만 유발 */
}

πŸ“ μ»΄ν¬μ§€νŒ… ν™œμš©

transform: translateX(100px)λŠ” λ ˆμ΄μ•„μ›ƒμ΄λ‚˜ 픽셀을 λ‹€μ‹œ κ³„μ‚°ν•˜μ§€ μ•Šκ³  GPUλ₯Ό ν™œμš©ν•΄ μ»΄ν¬μ§€νŒ… λ‹¨κ³„μ—μ„œ μ²˜λ¦¬λ©λ‹ˆλ‹€. μ΄λŠ” λ¦¬ν”Œλ‘œμš°μ™€ 리페인트λ₯Ό ν”Όν•©λ‹ˆλ‹€.

πŸ“ 리페인트만 유발

background-color 변경은 리페인트만 μœ λ°œν•˜λ©°, λ ˆμ΄μ•„μ›ƒμ— 영ν–₯을 μ£Όμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

πŸ“ React μ΅œμ ν™”

  • 클래슀 기반 μŠ€νƒ€μΌ λ³€κ²½: style 객체λ₯Ό 직접 λ³€κ²½ν•˜λŠ” λŒ€μ‹  CSS 클래슀λ₯Ό ν† κΈ€ν•˜μ—¬ λ¦¬λ Œλ”λ§ λΉ„μš©μ„ μ€„μž…λ‹ˆλ‹€.
  • CSS μ• λ‹ˆλ©”μ΄μ…˜: transition 속성을 μ‚¬μš©ν•΄ λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•˜λ©°, λΈŒλΌμš°μ €μ˜ μ΅œμ ν™”(GPU 가속)λ₯Ό ν™œμš©ν•©λ‹ˆλ‹€.

πŸ“ λΈŒλΌμš°μ € 개발자 도ꡬ ν™œμš©

  • Chrome DevTools의 Performance νƒ­μ—μ„œ λ¦¬ν”Œλ‘œμš°μ™€ 리페인트λ₯Ό κΈ°λ‘ν•˜μ—¬ μ–΄λ–€ 속성이 μ„±λŠ₯ 병λͺ©μ„ μΌμœΌν‚€λŠ”μ§€ 확인.
  • Rendering νƒ­μ—μ„œ "Paint Flashing"을 ν™œμ„±ν™”ν•˜λ©΄ 리페인트 μ˜μ—­μ„ μ‹œκ°μ μœΌλ‘œ 확인 κ°€λŠ₯.

πŸ“ Core Web Vitals κ³ λ €

λ¦¬ν”Œλ‘œμš°λŠ” CLS(Cumulative Layout Shift)λ₯Ό μœ λ°œν•  수 μžˆμœΌλ―€λ‘œ, λ ˆμ΄μ•„μ›ƒ 이동을 μ΅œμ†Œν™”. λ¦¬νŽ˜μΈνŠΈλŠ” LCP(Largest Contentful Paint)에 영ν–₯을 쀄 수 μžˆμœΌλ―€λ‘œ, μ€‘μš”ν•œ μ½˜ν…μΈ μ˜ λ Œλ”λ§μ„ μ΅œμ ν™” ν•˜μ—¬ μ‚¬μš© ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.

πŸ“ 라이브러리 μ‚¬μš©

Reactμ—μ„œ μ• λ‹ˆλ©”μ΄μ…˜μ„ μœ„ν•΄ framer-motionμ΄λ‚˜ react-spring 같은 라이브러리λ₯Ό μ‚¬μš©ν•˜λ©΄ μ»΄ν¬μ§€νŒ…μ„ μžλ™μœΌλ‘œ ν™œμš© κ°€λŠ₯ν•©λ‹ˆλ‹€.