Published on

Chrome 렌더링 깨짐? contain·will-change 함정

Authors

서버나 데이터 문제처럼 보이는데 UI만 이상하게 깨지는 경우가 있습니다. 특히 Chrome에서만 다음 같은 증상이 간헐적으로 나타납니다.

  • 텍스트가 갑자기 흐려지거나(서브픽셀 안티앨리어싱이 꺼진 듯) 두께가 달라짐
  • 스크롤 중 요소가 1px씩 떨리거나 깜빡임
  • position: sticky나 툴팁/드롭다운이 잘리거나, z-index가 먹히지 않는 것처럼 보임
  • transform 애니메이션 중 경계가 깨지거나, 배경이 비쳐 보이는 합성 아티팩트

이런 경우 “GPU 가속”이나 “Chrome 버그”로 뭉뚱그리기 쉽지만, 실제로는 우리가 성능 최적화 목적으로 넣은 containwill-change새로운 레이어/격리 컨텍스트를 만들면서 부작용을 일으키는 경우가 많습니다. 이 글은 그 함정들을 증상 중심으로 정리하고, 안전하게 되돌리는 방법까지 다룹니다.

관련해서 스크롤/레이아웃 특이점은 브라우저별로도 자주 다릅니다. iOS Safari의 sticky 이슈는 별도로 정리해 둔 글도 참고하세요: Safari iOS에서 position - sticky 깨짐 원인·해결

contain과 will-change가 ‘깨짐’을 만드는 이유

contain은 “격리”를 강제한다

contain은 브라우저에 “이 요소는 외부에 영향을 덜 주니 최적화해도 된다”고 힌트를 주는 속성입니다. 대표적으로 다음을 분리합니다.

  • contain: layout 레이아웃 계산 범위 축소
  • contain: paint 페인팅 범위 격리(자식이 밖으로 그리기 어려움)
  • contain: size 크기 계산을 독립적으로 처리
  • contain: strict 위를 모두 강하게 적용

문제는 contain: paint 계열이 들어가면 오버플로우/클리핑/스택킹 컨텍스트가 예상과 달라지고, sticky/fixed/포털 UI가 잘리는 형태로 나타날 수 있다는 점입니다.

will-change는 “곧 바뀔 테니 레이어를 준비하라”는 신호다

will-change: transform 같은 값을 주면 Chrome은 성능을 위해 해당 요소를 별도 합성 레이어로 올리려는 경향이 있습니다. 이때:

  • 텍스트 렌더링이 레이어 합성 방식으로 바뀌며 흐려 보일 수 있음
  • 불필요한 레이어가 늘어나면 오히려 프레임 드랍/깜빡임이 생김
  • 레이어 경계에서 1px seam, aliasing, 클리핑 등 아티팩트가 드러날 수 있음

즉, containwill-change는 “최적화” 도구지만, UI 일관성페인팅/합성 안정성을 희생할 수 있습니다.

빠른 진단 체크리스트(Chrome DevTools)

  1. DevTools Rendering 패널에서 Paint flashing을 켜서 과도한 repaint가 있는지 확인
  2. Layers 패널로 레이어가 과도하게 생성되는지 확인
  3. 문제 요소의 computed style에서 containwill-change 적용 여부 확인
  4. 문제가 “스크롤 중만” 발생한다면, 스크롤 컨테이너에 transform 또는 contain: paint가 있는지 확인

특히 will-change가 상시 적용되어 있으면, 레이어가 항상 유지되며 부작용이 지속됩니다.

대표 증상 1: 텍스트가 흐려짐(특히 transform/will-change)

재현 예시

아래처럼 카드에 will-change: transform을 상시 적용하고, hover 시 transform을 주면 일부 환경에서 텍스트가 살짝 흐려지거나 두께가 달라 보일 수 있습니다.

<div class="card">Chrome에서 글자가 흐려질 수 있는 카드</div>
.card {
  font-size: 18px;
  padding: 16px;
  border-radius: 12px;
  background: white;
  box-shadow: 0 8px 30px rgba(0,0,0,0.12);

  /* 함정: 상시 will-change */
  will-change: transform;
  transition: transform 200ms ease;
}

.card:hover {
  transform: translateY(-2px);
}

왜 이런가

텍스트가 그려지는 경로가 바뀌며(레이어 합성), 서브픽셀 렌더링이 제한되거나 안티앨리어싱이 달라질 수 있습니다. “버그”라기보다는 합성 정책 변화의 부작용에 가깝습니다.

해결 패턴

1) will-change를 상시 주지 말고 “필요할 때만”

const el = document.querySelector('.card');

el.addEventListener('mouseenter', () => {
  el.style.willChange = 'transform';
});

el.addEventListener('mouseleave', () => {
  // 애니메이션이 끝난 뒤 제거
  setTimeout(() => {
    el.style.willChange = 'auto';
  }, 250);
});

2) 애니메이션 대상 최소화

카드 전체가 아니라 “그림자/배경”만 transform하는 식으로 레이어 영향 범위를 줄입니다.

3) 정말 필요할 때만 레이어 승격

will-change는 “성능 핫스팟”에만 제한적으로 쓰는 것이 안전합니다. 무분별한 적용은 레이어 폭증으로 이어집니다.

대표 증상 2: 드롭다운/툴팁이 잘림(특히 contain: paint)

재현 예시

<div class="panel">
  <button class="btn">메뉴</button>
  <div class="menu">드롭다운이 잘릴 수 있음</div>
</div>
.panel {
  position: relative;
  padding: 24px;
  border: 1px solid #ddd;

  /* 함정: paint 격리로 인해 자식이 밖으로 그리기 어려워짐 */
  contain: paint;
}

.menu {
  position: absolute;
  top: 48px;
  left: 0;
  width: 240px;
  padding: 12px;
  background: white;
  border: 1px solid #ccc;
  box-shadow: 0 12px 40px rgba(0,0,0,0.18);
}

해결 패턴

1) 오버레이 UI는 contain: paint 영역 밖으로 “포털”

React라면 createPortalbody 하위에 렌더링해 클리핑/스택킹 영향을 피합니다.

import { createPortal } from 'react-dom';

export function MenuPortal({ open, anchorRect }: { open: boolean; anchorRect: DOMRect }) {
  if (!open) return null;

  return createPortal(
    <div
      style={{
        position: 'fixed',
        top: anchorRect.bottom,
        left: anchorRect.left,
        width: 240,
        background: 'white',
        border: '1px solid #ccc',
        boxShadow: '0 12px 40px rgba(0,0,0,0.18)',
        padding: 12,
        zIndex: 9999
      }}
    >
      드롭다운
    </div>,
    document.body
  );
}

2) contain을 더 약하게

가능하면 contain: content나 특정 축만 격리하는 방식으로 줄입니다. “페인트 격리”가 꼭 필요한지부터 재검토하세요.

대표 증상 3: sticky가 이상해짐(스크롤 컨테이너 + contain/transform)

position: sticky는 “가까운 스크롤 컨테이너”와 “조상 요소의 페인팅/변환 컨텍스트” 영향을 강하게 받습니다. 조상에 contain: painttransform이 들어가면 sticky가 기대와 다르게 동작하거나, 경계에서 깜빡일 수 있습니다.

점검 포인트

  • sticky 요소의 조상 중 overflow: hidden/auto/scroll이 있는지
  • 조상 중 transform: translateZ(0) 같은 레이어 트릭이 있는지
  • 조상 중 contain: paint 또는 contain: strict가 있는지

sticky 이슈는 브라우저별 차이가 커서, 동일한 CSS라도 Safari/iOS에서 더 자주 터집니다. 위에서 링크한 글도 함께 보면 원인 분리가 쉬워집니다.

대표 증상 4: 1px 틈(seam), 경계 깨짐, 깜빡임

합성 레이어 경계에서 1px seam이 생기는 경우가 있습니다. 특히 다음 조합에서 빈도가 올라갑니다.

  • fractional pixel(예: translateY(0.5px) 같은 소수점 이동)
  • filter, backdrop-filter, mask, clip-path 같이 합성 비용이 큰 효과
  • contain으로 페인트 영역이 잘게 쪼개짐

해결 패턴

  • transform 이동 값을 정수 픽셀로 맞추기(가능하면)
  • filter/backdrop-filter 적용 범위를 최소화
  • 필요 없는 레이어 승격(will-change) 제거
  • 경계가 보이는 요소에 background-color를 명시해 블렌딩 아티팩트 줄이기

contain을 안전하게 쓰는 가이드

contain은 “성능 개선”이 목적이지만, UI 컴포넌트 구조와 충돌하면 비용이 더 커집니다. 다음 원칙을 권장합니다.

1) 오버레이/포털 UI가 있는 컨테이너에는 contain: paint를 피한다

드롭다운, 툴팁, 모달, 컨텍스트 메뉴가 컨테이너 밖으로 나가야 한다면 paint 격리는 거의 항상 문제를 만듭니다.

2) contain: layout만으로도 충분한지 먼저 확인

레이아웃 계산 최적화가 목적이라면 contain: layout 또는 contain: content로도 효과를 보는 경우가 많습니다.

3) contain: size는 skeleton/placeholder 전략과 같이 써라

contain: size는 크기 계산을 독립시키므로, 콘텐츠 크기에 따라 레이아웃이 변해야 하는 UI에는 부작용이 큽니다. 고정 높이 skeleton을 두거나, 최소/최대 크기를 명시하는 식으로 설계를 동반해야 합니다.

will-change를 안전하게 쓰는 가이드

1) 상시 적용 금지, 짧게 쓰고 지운다

will-change는 “곧 바뀐다”는 힌트입니다. 항상 바뀌지 않는 요소에 상시 적용하면 레이어가 계속 유지됩니다.

2) 대상 속성 최소화

will-change: transform, opacity 정도가 일반적입니다. will-change: contents 같은 광범위한 값은 의도치 않은 비용을 만들 수 있습니다.

3) 애니메이션은 가능한 transformopacity로 끝내되, 텍스트 품질을 확인한다

성능만 보고 넣었다가 텍스트가 흐려지면 UX 손해가 큽니다. 특히 본문 텍스트가 들어간 카드 전체를 transform하는 패턴은 주의하세요.

실전 디버깅 절차: “원인 CSS를 격리”하는 방법

렌더링 깨짐은 재현이 어렵고, 원인이 여러 개 겹치기 쉽습니다. 다음 순서로 범위를 좁히면 빠릅니다.

  1. 문제 요소에서 contain을 모두 제거해보기
  2. 조상까지 포함해 will-change를 모두 제거해보기
  3. 조상에 있는 transform/filter/backdrop-filter를 하나씩 끄기
  4. 스크롤 컨테이너의 overflow와 sticky 관계 확인
  5. 레이어 수를 줄인 뒤(불필요한 승격 제거) 다시 효과를 최소 단위로 추가

이 과정은 장애 대응의 “진단 루틴”과 유사합니다. 예를 들어 네트워크/서버 장애에서 재시도와 폴백을 설계하듯, 프런트 렌더링 문제도 원인을 단계적으로 격리해야 합니다. 접근법 자체는 비슷하니 관심 있으면 OpenAI Responses API 503 멈춤 - 재시도·폴백 설계도 참고할 만합니다.

결론: 최적화 힌트는 ‘계약’이 아니라 ‘요청’이다

containwill-change는 브라우저에게 최적화 여지를 주는 강력한 도구지만, 컴포넌트의 페인팅/합성/스택킹 규칙을 바꿔 버릴 수 있습니다. Chrome 렌더링 깨짐이 보이면 다음을 우선 의심하세요.

  • contain: paint로 인한 클리핑/스택킹 변화
  • will-change 상시 적용으로 인한 레이어 승격 및 텍스트 렌더링 변화
  • 스크롤 컨테이너 조상에 transform/contain이 섞이며 sticky/overlay가 흔들리는 케이스

성능 최적화는 “측정 가능한 병목”에만 국소적으로 적용하고, 오버레이/텍스트 품질/스크롤 안정성 같은 UX 핵심 경로에서는 보수적으로 접근하는 것이 장기적으로 더 안전합니다.