사전 리페인트
개념

리페인트

gabury1고친 사람 github-actions[bot]

리페인트는 웹 페이지에서 생김새가 바뀐 부분을 브라우저가 다시 칠하는 일입니다. 글자 색이나 배경색처럼 위치와 크기는 그대로인 변경에서 일어납니다. 요소의 배치를 다시 계산하지 않아서 배치까지 새로 하는 리플로우보다 할 일이 적습니다. 데스크톱 프로그램이 창의 일부를 다시 그리는 일도 같은 이름으로 부릅니다.

쉽고 빠른 이해

리페인트는 위치는 그대로 두고 색만 다시 칠하는 일입니다. 버튼에 마우스를 올려 배경이 파랑에서 남색으로 바뀌면 브라우저는 그 버튼 영역만 다시 칠합니다.

화면은 한 번 그리고 끝나지 않습니다. 바뀔 때마다 처음부터 다시 그리면 느립니다. 위치와 크기가 그대로면 배치 계산을 건너뛰고 칠하기부터 다시 하면 됩니다.

  1. 코드나 사용자 동작이 요소의 색 같은 생김새를 바꿉니다
  2. 브라우저가 그 요소의 영역에 다시 칠할 곳이라는 표시를 해 둡니다
  3. 다음 화면을 그릴 때 표시된 영역만 칠해 새 그림을 냅니다

대가는 칠하는 시간입니다. 칠할 영역이 넓거나 그림자처럼 계산이 많이 드는 효과가 붙으면 오래 걸립니다. 이런 변경을 애니메이션으로 쉬지 않고 되풀이하면 화면이 끊겨 보입니다.

상세

방 벽의 색만 바꾸려는 사람은 가구를 옮기지 않습니다. 줄자를 꺼내 가구 배치를 다시 잴 일도 없습니다. 붓을 들고 벽을 다시 칠하면 끝납니다.

브라우저의 리페인트도 칠하기만 다시 합니다. 요소의 위치와 크기는 그대로이고 색 같은 생김새만 바뀌었을 때 일어납니다. 버튼에 마우스를 올려 배경색이 바뀌는 순간이 흔한 예입니다.

리페인트가 건너뛰는 단계

리페인트의 할 일이 적은 까닭은 건너뛰는 단계가 있어서입니다. 무엇을 건너뛰는지 보려면 브라우저가 화면을 그리는 순서부터 알아야 합니다.

브라우저는 서버가 보낸 글을 받아 화면을 그립니다. 내용과 구조는 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)로 적혀 옵니다. 색과 크기 같은 생김새는 CSS(Cascading Style Sheets, 캐스케이딩 스타일 시트)로 적혀 옵니다.

이 글을 그림으로 바꾸는 일이 렌더링입니다. 브라우저 안에서 이 일을 맡은 부분은 렌더링 엔진입니다. 렌더링 엔진은 일을 다섯 단계로 나눠 차례로 처리합니다.

단계 하는 일
스타일 계산 요소마다 어떤 CSS 규칙이 붙는지 가려 최종 색·크기 값을 정한다
레이아웃 요소마다 화면의 어느 좌표에 얼마만 한 크기로 놓일지 계산한다
페인트 무엇을 어떤 순서로 칠할지 그리기 명령을 적는다
래스터화 그리기 명령을 실제 픽셀로 바꾼다
합성 칠한 결과를 겹쳐 화면 한 장을 만든다

처음 그릴 때는 다섯 단계를 전부 거칩니다. 리페인트는 이 가운데 레이아웃 하나를 건너뜁니다. 아래 그림의 점선이 리페인트가 지나가는 길입니다.

flowchart TD
    S["스타일 계산"] --> L["레이아웃"]
    L --> P["페인트"]
    S -.->|"리페인트는 레이아웃을 건너뛴다"| P
    P --> R["래스터화"]
    R --> C["합성"]

생김새가 바뀌었으니 스타일 계산은 다시 합니다. 좌표와 크기는 그대로라서 레이아웃은 다시 할 것이 없습니다. 그래서 페인트로 바로 넘어갑니다. 페인트 뒤의 래스터화와 합성은 다시 실행됩니다.

이 글에서 「칠한다」는 페인트와 래스터화를 묶어 부르는 말입니다. 그리기 명령을 적는 일과 그 명령을 픽셀로 바꾸는 일을 함께 가리킵니다.

이 구분이 없다면 색 하나를 바꿀 때마다 모든 요소의 좌표를 새로 계산해야 합니다. 요소가 수천 개인 페이지라면 버튼에 마우스를 올릴 때마다 그 계산이 되풀이됩니다. 리페인트는 바뀌지 않은 배치를 다시 계산하는 헛수고를 덜어 줍니다.

리페인트를 일으키는 변경

같은 CSS 속성을 바꿔도 어느 속성이냐에 따라 다시 도는 범위가 다릅니다. 가르는 기준은 하나입니다. 그 속성이 요소의 좌표나 크기를 바꾸는지입니다.

바꾸지 않으면 리페인트로 끝납니다. 바꾸면 레이아웃부터 다시 합니다. 흔히 견주는 속성을 표로 놓으면 이렇습니다.

바꾼 속성 좌표·크기가 바뀌나 다시 시작하는 단계
color · 글자 색 ✗ 페인트
background-color · 배경색 ✗ 페인트
box-shadow · 그림자 ✗ 페인트
visibility · 보이기와 숨기기 ✗ 페인트
width · 너비 ✓ 레이아웃
display · 배치에 넣기와 빼기 ✓ 레이아웃

표의 visibility 와 display 는 둘 다 요소를 숨길 때 씁니다. 그런데 다시 도는 범위가 다릅니다. 아래 두 줄은 같은 상자를 숨기는 두 방법입니다.

JavaScript
el.style.visibility = "hidden"; // 리페인트
el.style.display = "none";      // 리플로우

첫 줄은 상자를 안 보이게만 합니다. 상자가 차지하던 공간은 그대로 남습니다. 이웃 요소의 좌표가 안 바뀌므로 그 영역만 다시 칠하면 됩니다.

둘째 줄은 상자를 배치에서 아예 뺍니다. 뒤따르던 요소들이 빈 공간으로 당겨 올라옵니다. 좌표가 바뀌었으니 레이아웃부터 다시 계산합니다. 이렇게 레이아웃부터 다시 하는 일을 리플로우라고 합니다.

리플로우와 견준 비용

리페인트는 리플로우와 짝으로 자주 불립니다. 리플로우는 요소의 크기나 위치가 바뀌어 레이아웃을 다시 계산하는 일입니다. 둘이 어떻게 겹치고 어느 쪽이 더 비싼지 정리합니다.

레이아웃 뒤에는 언제나 페인트가 옵니다. 그래서 리플로우가 일어나면 리페인트도 따라 일어납니다. 반대로 리페인트는 리플로우 없이 혼자 일어날 수 있습니다.

리플로우는 한 요소의 크기만 바뀌어도 번집니다. 이웃 요소와 부모 요소의 좌표까지 줄줄이 다시 계산해야 할 수 있습니다. 리페인트는 바뀐 영역을 칠하기만 하므로 대개 리플로우보다 쌉니다.

리페인트보다 할 일이 더 적은 경우도 있습니다. 브라우저는 자주 움직이는 요소를 떼어 별도의 판에 그려 두기도 합니다. 이렇게 따로 그린 판 한 장을 레이어라고 합니다.

레이어에 든 요소는 위치 이동이나 투명도만 바뀌면 다시 칠하지 않습니다. 이미 그려 둔 판을 다른 위치에, 다른 투명도로 겹치기만 합니다. 세 경우를 다시 하는 단계가 많은 순서로 놓으면 이렇습니다.

변경의 종류 다시 시작하는 단계 예
리플로우 레이아웃 너비를 바꾼다 · 목록에 항목을 넣는다
리페인트 페인트 글자 색을 바꾼다 · 그림자를 키운다
합성만 다시 합성 레이어에 든 메뉴를 옆으로 옮긴다 · 서서히 흐리게 한다

다시 칠할 영역의 표시

리페인트가 일어나도 페이지 전부를 다시 칠하지는 않습니다. 렌더링 엔진은 바뀐 요소가 차지한 영역에 「다시 칠할 곳」이라는 표시를 해 둡니다. 이전에 칠한 결과를 더는 못 쓴다고 표시하는 이 일을 페인트 무효화라고 합니다.

표시한 영역은 곧바로 칠하지 않습니다. 화면은 1초에 여러 번 새 그림으로 바뀝니다. 이때 새로 나가는 그림 한 장이 프레임입니다.

렌더링 엔진은 표시를 모아 두었다가 다음 프레임을 그릴 때 한꺼번에 칠합니다. 그래서 한 프레임 안에서 코드가 같은 요소의 색을 열 번 바꿔도 칠하기는 한 번이면 됩니다.

리페인트가 화면을 끊기게 하는 경우

리페인트는 리플로우보다 싸지만 공짜는 아닙니다. 넓게, 자주 일어나면 화면이 멈칫거립니다. 어떤 조건에서 그렇게 되는지와 흔한 처방을 차례로 짚습니다.

스크롤과 애니메이션이 부드러우려면 다음 프레임이 나가기 전에 칠하기까지 끝나야 합니다. 화면이 1초에 60번 바뀐다면 한 프레임에 쓸 수 있는 시간은 60분의 1초입니다. 이 안에 못 끝내면 그 프레임을 놓쳐 화면이 멈칫합니다.

페인트는 대개 자바스크립트 코드와 같은 스레드에서 번갈아 실행됩니다. 이 스레드를 메인 스레드라고 합니다. 칠하기가 길어지면 그만큼 코드 실행이 밀립니다. 클릭에 대한 반응도 늦어집니다.

칠하는 영역이 넓을수록 오래 걸립니다. 넓은 그림자나 흐림 효과는 래스터화 때 픽셀 하나를 만드는 데도 계산이 많이 듭니다. 이런 속성을 애니메이션으로 바꾸면 프레임마다 리페인트가 일어납니다.

흔한 처방은 움직임을 두 속성으로만 만드는 것입니다. transform 은 위치 이동·회전·크기를 바꿉니다. opacity 는 투명도를 바꿉니다. 그 요소가 레이어에 따로 있으면 페인트 없이 합성만 다시 합니다.

그림자가 커지는 효과를 예로 들어 봅니다. 그림자 크기를 직접 키우면 프레임마다 다시 칠합니다.

대신 같은 크기의 요소를 하나 더 만들어 큰 그림자를 달아 둡니다. 이 요소를 투명도 0, 곧 안 보이는 상태로 원래 요소 위에 겹쳐 둡니다. 효과가 필요할 때 이 요소의 투명도만 올리면 합성만 다시 합니다.

will-change 는 이 요소가 곧 바뀐다고 브라우저에 미리 알리는 CSS 속성입니다. 브라우저는 이 힌트를 보고 그 요소를 레이어로 미리 떼어 둘 수 있습니다.

레이어를 떼는 대가는 메모리입니다. 레이어는 저마다 픽셀을 한 장씩 따로 들고 있습니다. 레이어를 많이 떼면 메모리를 그만큼 더 씁니다. 겹칠 판도 늘어 합성에 시간이 더 듭니다.

어디서 리페인트가 되풀이되는지는 눈으로 찾을 수 있습니다. 크롬 개발자 도구의 Rendering 패널에서 Paint flashing 을 켜면 다시 칠해진 영역이 초록색으로 덧칠됩니다. 애니메이션을 돌리며 켜 두면 쉬지 않고 깜빡이는 영역이 드러납니다.

브라우저 밖의 리페인트

창을 띄우는 데스크톱 프로그램도 화면 일부를 다시 그립니다. 가려져 있던 창이 드러나거나 보여 줄 데이터가 바뀌면 그 영역을 새로 칠해야 합니다. 이 일도 리페인트라고 부릅니다.

이런 프로그램의 화면을 만드는 라이브러리를 GUI 툴킷이라고 합니다. GUI 는 Graphical User Interface, 그래픽 사용자 인터페이스의 줄임말입니다. 창과 단추 같은 화면 부품을 만들어 주는 것이 이 라이브러리의 일입니다.

자바의 GUI 툴킷인 Swing 에는 repaint() 라는 메서드가 있습니다. 이 메서드는 바로 그리지 않습니다. 「이 부품을 다시 칠해 달라」는 요청을 넣어 두기만 합니다.

요청은 화면을 맡은 스레드가 나중에 꺼내 처리합니다. 그 스레드가 부품의 그리기 메서드인 paintComponent 를 불러 실제로 칠합니다. 짧은 사이에 요청이 여러 번 들어오면 한 번으로 합쳐 칠하기도 합니다. 표시를 모았다가 한꺼번에 칠하는 브라우저와 같은 방식입니다.

관련 항목

리페인트가 지나가는 렌더링 단계

렌더링 · 렌더링 엔진 · DOM · CSSOM · 스타일 계산 · 레이아웃 · 페인트 · 래스터화 · 합성 · 페인트 무효화 · 중요 렌더링 경로

리페인트를 끌고 오는 레이아웃 재계산

리플로우 · 레이아웃 스래싱 · 강제 동기 레이아웃

리페인트를 일으키는 CSS 속성

CSS · visibility · display · box-shadow · 박스 모델

리페인트를 건너뛰게 해 주는 수단

합성 레이어 · will-change · CSS 트랜스폼 · opacity · 하드웨어 가속 · GPU · 컴포지터 스레드

리페인트가 다시 만드는 결과물

디스플레이 리스트 · 픽셀 · 비트맵 · 프레임

리페인트 시간을 쥐는 실행 구조

메인 스레드 · 스레드 · 이벤트 루프 · requestAnimationFrame · 프레임률

리페인트를 찾아내는 도구와 지표

개발자 도구 · 성능 프로파일링 · 다음 페인트까지의 상호작용 · 누적 레이아웃 이동 · 코어 웹 바이탈

리페인트를 같은 이름으로 쓰는 GUI 도구

GUI 툴킷 · Swing · 이벤트 디스패치 스레드 · 다시 그리기 · 캔버스

다른 이름: repaint · repainting · 리페인팅