블로그 목록
frontend

CSS content-visibility로 긴 페이지 렌더링 살리기 — 화면 밖은 그리지 말자

카드 수백 개가 쌓인 목록 페이지를 만들었더니 초기 렌더가 눈에 띄게 느려졌다. 스크롤로 보이지도 않는 아래쪽 카드들까지 브라우저가 전부 레이아웃하고 페인트하느라 시간을 다 쓰고 있었다. 데이터는 이미 다 받아왔는데 화면 그리는 게 병목이었다.

예전엔 이걸 잡으려고 가상 스크롤(react-window 같은)을 붙였다. 화면에 보이는 만큼만 DOM에 그리는 방식인데, 강력하긴 해도 스크롤 위치 계산, 높이 추정, 접근성 같은 걸 다 챙겨야 해서 손이 많이 간다.

content-visibility: auto

이제는 CSS 한 줄로 상당 부분을 넘길 수 있다. 화면 밖에 있는 요소는 렌더링(레이아웃·페인트)을 아예 건너뛰라고 브라우저에 알려주는 속성이다.

.card {
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}

content-visibility: auto를 주면 브라우저가 그 요소가 뷰포트 근처에 올 때까지 내부 렌더링을 미룬다. DOM에는 그대로 남아 있어서 Ctrl+F 검색이나 접근성 트리에는 잡히지만, 화면에 그리는 비용은 스크롤이 가까워질 때까지 발생하지 않는다.

contain-intrinsic-size가 짝이다

문제는 렌더링을 건너뛴 요소는 높이가 0이 된다는 점이다. 그러면 스크롤바가 실제보다 짧아지고, 스크롤하는 도중 높이가 계속 바뀌면서 스크롤바가 요동친다.

그래서 contain-intrinsic-size로 "아직 안 그렸지만 대략 이만한 크기일 거야" 하는 예상 크기를 미리 알려준다. auto 320px처럼 auto를 앞에 붙이면, 한 번 실제로 렌더된 요소는 그 크기를 기억해뒀다가 다시 화면 밖으로 나가도 그 값을 쓴다. 덕분에 스크롤바가 안정적으로 유지된다.

어디에 쓰면 좋나

목록의 각 항목, 긴 문서의 섹션, 댓글 블록처럼 개수가 많고 화면 밖에 잔뜩 쌓이는 반복 요소에 걸 때 효과가 크다. 반대로 화면에 항상 다 보이는 소수의 요소에 걸면 이득이 없다.

측정해보면 차이가 확실하다. 카드 500개짜리 페이지에서 초기 렌더 시간이 절반 아래로 떨어지는 경우도 흔하다. Lighthouse의 "화면 밖 렌더링 미루기" 항목이 바로 이거다.

정리

가상 스크롤이 필요할 만큼 복잡한 게 아니라면, 반복 요소에 content-visibility: autocontain-intrinsic-size 두 줄만 넣어보자. Chrome·Edge·Firefox·Safari 모두 지원하니 지금 바로 써도 된다. JS 라이브러리 없이 긴 페이지 렌더링을 이만큼 손쉽게 살릴 수 있는 방법은 흔치 않다.