⚡ 13. CSS 성능 최적화: '60fps를 향한 렌더링 엔진 정복'
📋 개요
CSS 속성 한 줄이 브라우저의 CPU와 GPU에 어떤 부하를 주는지 이해하고, Reflow와 Repaint를 최소화하여 극한의 부드러움을 구현하는 법을 영철이와 함께 완성해 봅니다.
📌 이 문서를 읽기 전에
⏱️ 예상 읽기 시간: 10분(전체) / 핵심 파트만: 5분
🗺️ 이 문서의 흐름
[렌더링 파이프라인 심화] → [Reflow 유발 범인 검거] → [will-change와 하드웨어 가속]
🎯 이 문서를 다 읽으면 할 수 있는 것
- 브라우저 개발자 도구의 'Rendering' 탭을 활용해 프레임 드랍을 진단합니다.
- 레이아웃 스래싱(Layout Thrashing) 현상을 이해하고 방지합니다.
- GPU 합성(Compositing)이 유리한 상황을 구분해 저사양 기기에서도 성능을 확보합니다.
🗺️ 이 문서의 배경 세계관: '영수네 커뮤니티'
- 🐣 영철 (후반부 실무자): "영호 님, 스크롤하면 헤더 색이 바뀌는 효과를 넣었는데 최신 기기에서는 괜찮고 영수 님의 오래된 갤럭시에서는 끊깁니다. 기기 차이도 있겠지만 제가 어떤 속성을 애니메이션했는지부터 봐야 할 것 같아요."
- 🦁 영호 (리드): "맞아요. 성능 문제는 사용자 기기 탓으로 끝내기보다 브라우저가 어떤 단계를 반복하는지 추적해야 합니다. Layout, Paint, Composite 중 어디까지 다시 실행되는지 보죠."
🤔 왜 알아야 하는가
대부분의 CSS 속성은 성능에 큰 차이가 없습니다. 하지만 반복되는 애니메이션이나 대량의 요소를 다룰 때는 이야기가 달라집니다. CSS 한 줄을 잘못 쓰면 브라우저는 초당 60번씩 페이지 전체의 그림을 다시 그려야 할 수도 있습니다.
5년차 이상의 시니어는 눈에 보이는 결과물만큼이나 '부담 없는 코드' 를 중시합니다. 브라우저가 공간을 계산하는 Layout(Reflow) 단계를 건너뛰고 색칠만 다시 하거나(Repaint), 그마저도 생략하고 이미 그려진 레이어만 움직이게(Composite) 만드는 기법을 선택할 수 있어야 합니다. 차이는 화려한 속성을 아는 데서가 아니라, 어떤 단계가 다시 실행되는지 설명하는 데서 생깁니다.
🏎️ 1. 렌더링 파이프라인의 삼중주
브라우저가 화면을 그리는 3단계 공정입니다. 성능 최적화는 이 단계를 최대한 뒤쪽에서 끝내는 것입니다.
- Layout (Reflow): 요소의 기하학적 형태(위치, 크기)를 계산합니다. 가장 비싼 연산입니다.
👉width,height,margin,top,font-size... - Paint (Repaint): 계산된 영역에 색을 채웁니다. 여전히 비용이 듭니다.
👉color,background-color,box-shadow... - Composite: 레이어들을 합쳐서 사용자에게 보여줍니다. GPU가 처리하며 가장 빠릅니다.
👉transform,opacity...
🦁 영호의 리뷰 기준:
"반복되는 애니메이션은 가능하면 Composite 단계에서 끝내세요.top을 바꾸면 위치 계산이 다시 필요하지만,transform: translateY()는 이미 만든 레이어를 합성하는 쪽으로 처리될 가능성이 큽니다."
🛠️ 2. will-change와 하드웨어 가속
will-change는 브라우저에게 "이 요소의 특정 속성이 곧 바뀔 수 있다"고 미리 알려주는 속성입니다. 준비 시간이 생기면 애니메이션이 부드러워질 수 있지만, 그 준비 자체도 메모리를 사용합니다.
.box {
/* hover나 drag 직전에만 켜고, 상시 적용은 피합니다. */
will-change: transform;
}⚠️ 주의! 준비 비용도 비용입니다:
will-change를 남발하면 브라우저가 많은 요소를 개별 레이어로 준비해 메모리 사용량이 커질 수 있습니다. 꼭 필요한 애니메이션 요소에만 짧게 적용하고, 작업이 끝나면 제거하는 편이 안전합니다.
🚫 3. 레이아웃 스래싱 (Layout Thrashing) 방지
자바스크립트에서 스타일을 읽고 쓰는 순서가 섞이면 브라우저가 레이아웃 계산을 반복해야 합니다.
for (let i = 0; i < 100; i++) {
const width = el.offsetWidth; // 레이아웃 값을 읽으면 최신 위치/크기 계산이 필요합니다.
el.style.width = width + 10 + 'px'; // 곧바로 쓰면 다음 반복에서 다시 계산이 필요해집니다.
}
/* 읽기와 쓰기가 섞여 루프마다 Reflow가 반복될 수 있습니다. */🦁 영호: "읽는 건 한꺼번에 읽고, 쓰는 건 requestAnimationFrame 같은 타이밍에 모아서 처리하세요."
개선한 코드는 먼저 필요한 값을 모두 읽고, 다음 프레임에서 스타일 변경을 한 번에 적용합니다.
const widths = items.map((item) => item.offsetWidth);
requestAnimationFrame(() => {
items.forEach((item, index) => {
item.style.width = `${widths[index] + 10}px`;
});
});이렇게 분리하면 브라우저가 "읽기 때문에 레이아웃을 확정"하고 "쓰기 때문에 다시 무효화"하는 과정을 반복하지 않아도 됩니다. 영철은 이제 애니메이션 코드 리뷰에서 속성 이름뿐 아니라 읽기/쓰기 순서도 함께 확인하게 됩니다.
📝 마무리 퀴즈
Q1. 다음 중 브라우저 렌더링 엔진에게 가장 가벼운 (성능 좋은) 애니메이션 속성은 무엇일까요?
heightmargin-leftpaddingtransform
✅ 정답: 4. transform
💡 상세 해설:
- 원리:
transform은 레이아웃이나 페인트를 다시 실행하지 않고 GPU에서 레이어를 합성(Composite)만 하면 되기 때문입니다. 다른 속성들은 전체 레이아웃을 다시 계산해야 하는 Reflow를 유발합니다. - 오답 피드백: "영철 님, 위치와 크기를 바꾸면 Layout 단계가 다시 필요할 수 있어요.
transform은 레이아웃 흐름을 바꾸지 않고 합성 단계에서 처리하기 쉬워 반복 애니메이션에 더 적합합니다."
Q2. will-change 속성을 남발할 경우 발생할 수 있는 부작용은 무엇인가요?
- 인터넷 속도가 느려진다.
- 이미지가 흐릿하게 보인다.
- 과도한 레이어 생성으로 메모리(RAM) 사용량이 급증한다.
- CSS 파일 용량이 커진다.
✅ 정답: 3. 과도한 레이어 생성으로 메모리(RAM) 사용량이 급증한다.
💡 상세 해설:
- 원리:
will-change는 해당 요소를 별도의 레이어로 분리하여 GPU 가속을 준비시킵니다. 하지만 레이어가 너무 많아지면 메모리 점유율이 높아져 오히려 전반적인 시스템 성능이 떨어질 수 있습니다. - 오답 피드백: "영철 님, 예방 주사도 너무 많이 맞으면 몸에 해로운 것과 같아요. 꼭 필요한 애니메이션 요소에만 '핀포인트'로 사용하세요!"
Q3. [영철이의 테스트 타임: 실무 딜레마]
영수 님이 "무한 스크롤 리스트를 만들었는데, 아래로 내릴수록 점점 화면이 버벅거려요"라고 하십니다. 영철이가 확인해보니 리스트 아이템마다 box-shadow가 아주 복잡하게 들어가 있고, 스크롤할 때마다 모든 아이템이 다시 그려지고 있었습니다. 영철이가 성능 향상을 위해 제안할 수 있는 가장 전략적인 방법은?
✅ 정답: 그림자 효과를 transform이나 opacity를 사용하는 가상 요소로 대체하거나, 스크롤 영역에 contain 속성을 부여해 렌더링 범위를 제한한다.
💡 상세 해설:
- 원리 설명: 복잡한
box-shadow는 Paint 비용을 키울 수 있습니다. 리스트가 많고 스크롤마다 다시 그려진다면 그림자 표현을 단순화하거나, 실제로 화면에 가까운 항목만 렌더링하는 가상화,content-visibility: auto같은 렌더링 범위 제한 전략을 함께 검토해야 합니다. - 오답 피드백: "영철 님, 복잡한 그림자는 예쁘지만 저사양 기기에서는 체감 성능을 낮출 수 있어요. 디자인 의도와 성능 예산을 같이 보고 대안을 제안하세요."
- 📌 핵심 기억법: "시각 효과는 성능 예산 안에서 설계한다."
🐣 영철이의 퇴근 일기
오늘은 CSS 한 줄의 '값어치'가 얼마나 무거운지 알게 됐다.
top: 0을 translateY(0)로 바꾼다고 뭐가 달라질까 싶었는데, 실제 모바일 기기에서 프레임 차이가 나는 걸 보니 확실히 체감됐다.
💡 "성능은 기능이다. 아무리 화려한 기능도 반복해서 끊긴다면 사용자는 기능을 신뢰하기 어렵다."
영호 님이 "시니어는 코드를 칠할 때 브라우저가 다시 계산할 단계를 떠올린다"라고 하셨던 게 인상 깊었다.
집에 가는 길에 내가 즐겨 쓰는 앱들의 스크롤링이 왜 부드러운지, 혹은 왜 끊기는지 분석해보는 습관이 생겼다.
내일은 '최신 CSS 스펙'들을 배운다는데, 이제 곧 가이드도 마지막이다. 끝까지 힘내자! ⚡