사전 리플로
개념

리플로

gabury1고친 사람 github-actions[bot]

리플로는 웹 페이지의 요소들이 놓일 위치와 크기를 브라우저가 다시 계산하는 일입니다. 글이 길어지거나 창 너비가 바뀌면 요소들이 밀려서 배치를 새로 잡아야 합니다. 한 요소의 변화가 둘레 요소로 번지기 쉬워서 화면을 다시 그리는 일 가운데 비싼 편입니다. 전자 부품 납땜과 웹 접근성에도 같은 이름이 쓰입니다.

쉽고 빠른 이해

리플로는 화면에 놓인 요소들의 위치를 다시 잡는 일입니다. 게시글 아래에 댓글이 하나 붙으면 그 밑의 댓글과 바닥글이 전부 아래로 밀립니다. 브라우저는 밀린 요소마다 새 위치를 다시 계산합니다.

위치를 모르면 어디에 칠할지도 모릅니다. 처음 계산한 배치를 고치지 않고 쓰면 요소가 겹치거나 빈 칸이 생깁니다.

  1. 코드나 사용자 동작이 요소의 크기나 위치에 영향을 주는 값을 바꿉니다
  2. 브라우저가 영향받는 요소에 다시 계산할 곳이라는 표시를 해 둡니다
  3. 다음 화면을 그리기 전에 표시된 요소들의 위치와 크기를 다시 계산하고 다시 칠합니다

대가는 계산 시간입니다. 번지는 범위가 넓을수록 오래 걸립니다. 코드가 값을 바꾸자마자 크기를 읽는 일을 되풀이하면 계산이 여러 번 일어나 화면이 버벅입니다.

상세

책장 한 칸에 책을 빈틈없이 꽂아 두었다고 해 봅시다. 가운데에 두꺼운 책을 한 권 끼우면 그 오른쪽 책들이 전부 옆으로 밀립니다. 칸에서 넘친 끝의 책은 아랫칸으로 내려갑니다. 그러면 어느 책이 어디로 갔는지 다시 훑어봐야 합니다.

브라우저의 리플로도 이렇게 밀린 것들의 새 위치를 다시 셉니다. 요소 하나의 크기가 바뀌면 뒤따르는 요소들의 위치가 바뀝니다. 그 요소를 감싼 부모의 높이도 바뀔 수 있습니다. 브라우저는 이렇게 영향받은 요소마다 위치와 크기를 새로 정합니다.

브라우저가 화면을 그리는 순서

리플로가 무엇을 다시 하는지 보려면 브라우저가 처음 화면을 그리는 순서부터 알아야 합니다. 이 소절은 그 순서를 다섯 단계로 나눠 보입니다. 그다음 리플로가 그 가운데 어느 단계인지 짚습니다.

웹 페이지는 두 가지 글로 옵니다. 내용과 구조는 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)로 적혀 있습니다. 색과 크기 같은 생김새는 CSS(Cascading Style Sheets, 캐스케이딩 스타일 시트)로 적혀 있습니다.

브라우저는 HTML 을 읽어 요소들을 나무 모양으로 이어 둡니다. 이 나무가 DOM(Document Object Model, 문서 객체 모델)입니다. 자바스크립트 코드가 화면을 바꿀 때 손대는 것도 이 나무입니다.

DOM 에 CSS 를 입혀도 브라우저는 아직 무엇을 어떤 스타일로 그릴지까지만 압니다. 어디에 얼마만 한 크기로 그릴지는 모릅니다. 그것을 정하는 단계가 따로 있습니다.

HTML 과 CSS 를 그림으로 바꾸는 부분이 렌더링 엔진입니다. 렌더링 엔진은 일을 다섯 단계로 나눠 차례로 처리합니다.

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

리플로는 이 표의 둘째 단계를 다시 도는 일입니다. 페이지를 처음 띄울 때 하는 계산은 그냥 레이아웃이라고 부릅니다. 페이지가 뜬 뒤에 무언가 바뀌어 레이아웃을 또 하면 그것을 리플로라고 부릅니다.

두 이름은 같은 계산을 가리킵니다. 브라우저에 따라 재계산도 레이아웃이라고만 부르기도 합니다. 크롬 개발자 도구의 성능 기록에서도 이 작업은 Layout 이라는 이름으로 찍힙니다. 리플로우라고 적기도 합니다.

리플로를 일으키는 변경

무엇이 바뀌었느냐에 따라 다시 도는 단계가 다릅니다. 가르는 기준은 하나입니다. 그 변경이 요소의 위치나 크기에 영향을 주는지입니다.

위치나 크기에 영향을 주면 레이아웃부터 다시 합니다. 레이아웃 뒤에는 언제나 칠하기가 오므로 리플로가 일어나면 리페인트도 따라옵니다. 리페인트는 위치는 그대로 두고 생김새만 다시 칠하는 일입니다.

flowchart TD
    A["코드나 사용자가 무언가를 바꾼다"] --> B{"위치나 크기가 바뀌나"}
    B -->|바뀐다| L["레이아웃부터 다시 · 리플로"]
    B -->|안 바뀐다| C{"색 같은 생김새가 바뀌나"}
    C -->|바뀐다| P["페인트부터 다시 · 리페인트"]
    C -->|안 바뀐다| M["합성만 다시"]
    L --> P
    P --> M

그림의 맨 아래 칸인 「합성만 다시」는 배치와 이미 칠한 그림을 그대로 두고 겉보기만 바꿀 때입니다. 요소가 화면에서 보이는 위치만 옮기거나 투명도만 바꾸는 경우가 그렇습니다. 배치 계산이 정한 위치는 그대로라서 첫 갈림에서 「안 바뀐다」로 갑니다. 이 경우는 아래 「리플로를 줄이는 방법」에서 다시 봅니다.

리플로를 부르는 흔한 변경을 모으면 이렇습니다.

변경 예
요소를 넣거나 뺀다 목록에 항목을 더한다 · 알림 상자를 지운다
크기에 영향을 주는 CSS 값을 바꾼다 width · height · padding · margin · font-size
배치 방식을 바꾼다 display 를 none 으로 바꿔 요소를 숨긴다
글 내용이 바뀐다 입력창에 글자를 쳐서 줄이 늘어난다
늦게 도착한 것이 크기를 가진다 치수를 안 적은 이미지가 다 받아진다 · 웹 폰트가 늦게 도착한다
화면 폭이 바뀐다 창 크기를 바꾼다 · 폰을 가로로 돌린다

표의 마지막 두 줄은 코드가 아무것도 안 해도 일어납니다. 치수를 모르는 이미지 칸은 처음에 작게 잡혔다가 이미지가 도착하면 커집니다. 그 밑의 글이 갑자기 밀려 내려가는 현상이 레이아웃 이동입니다.

리플로가 번지는 범위

리플로가 비싼 까닭은 바뀐 요소 하나에서 끝나지 않아서입니다. 이 소절은 댓글 하나를 더하는 장면으로 계산이 어디까지 번지는지 보입니다.

웹 페이지의 요소는 대개 위에서 아래로 차례로 쌓입니다. 이렇게 차례대로 쌓이는 배치를 흐름이라 합니다. 앞의 요소가 커지면 뒤의 요소들은 그만큼 내려가야 합니다. 부모 요소는 자식을 다 담아야 하므로 자식이 커지면 부모도 커집니다.

flowchart TD
    B["body · 높이 다시 계산"]
    B --> H["머리글 · 그대로"]
    B --> M["본문 · 높이 다시 계산"]
    B --> F["바닥글 · 아래로 밀림"]
    M --> Post["게시글 · 그대로"]
    M --> L["댓글 목록 · 높이 다시 계산"]
    L --> C1["댓글 1 · 그대로"]
    L --> New["새 댓글 · 추가됨"]
    L --> C2["댓글 2 · 아래로 밀림"]

그림은 댓글 1 과 댓글 2 사이에 새 댓글이 끼어든 모습입니다. 새 댓글보다 앞에 있던 머리글·게시글·댓글 1 은 위치가 그대로입니다. 뒤에 오는 댓글 2 와 바닥글은 아래로 밀립니다.

위쪽으로도 번집니다. 댓글 목록이 길어지니 그것을 감싼 본문이 커집니다. 본문을 감싼 body 도 커집니다. 새 댓글 하나 때문에 조상 셋과 뒤따르는 요소 둘의 위치나 크기를 다시 정했습니다.

번지는 범위는 배치 방식에 따라 달라집니다. position: absolute 처럼 다른 요소 위에 띄워 놓은 요소는 흐름에서 빠져 있어서 대개 이웃을 밀지 않습니다. 반대로 페이지 맨 위의 요소가 커지면 그 아래 전부가 다시 계산됩니다.

한 번에 몰아서 하는 리플로

브라우저는 값이 바뀔 때마다 곧바로 리플로를 하지 않습니다. 바뀐 요소에 「다시 계산할 곳」이라는 표시만 해 두고 넘어갑니다.

화면은 1초에 여러 번 새 그림으로 바뀝니다. 이때 새로 나가는 그림 한 장이 프레임입니다. 브라우저는 표시를 모아 두었다가 다음 프레임을 그리기 직전에 한 번에 계산합니다.

JavaScript
box.style.width = "200px";  // 표시만
box.style.padding = "8px";  // 표시만
box.style.margin = "4px";   // 표시만

세 줄이 모두 크기에 영향을 주는 값을 바꿉니다. 그래도 리플로는 세 번이 아니라 한 번입니다. 세 변경이 다음 프레임 직전에 한꺼번에 계산되기 때문입니다.

코드가 리플로를 앞당기는 경우

몰아서 하는 방식이 깨지는 때가 있습니다. 코드가 값을 바꾼 직후에 요소의 크기나 위치를 읽을 때입니다.

크기를 읽으려면 그 값이 최신이어야 합니다. 방금 바꾼 값은 아직 계산되지 않았습니다. 그래서 브라우저는 다음 프레임까지 기다리지 않고 그 줄에서 바로 리플로를 합니다. 이렇게 코드가 앞당겨 부르는 리플로가 강제 동기 레이아웃입니다.

JavaScript
box.style.width = "200px";  // 표시만
const h = box.offsetHeight; // 즉시 리플로

offsetHeight 는 요소의 높이를 돌려주는 속성입니다. 너비가 바뀌면 줄바꿈이 달라져 높이도 바뀔 수 있습니다. 그래서 브라우저는 이 줄에서 리플로를 끝내고 나서야 높이를 돌려줍니다.

크기나 위치를 돌려주는 속성은 대부분 이렇게 동작합니다. offsetWidth · clientHeight · scrollTop 같은 속성과 getBoundingClientRect() 함수가 그렇습니다.

읽기와 쓰기가 번갈아 도는 반복문

강제 동기 레이아웃 한 번은 대개 문제가 안 됩니다. 문제는 반복문 안에서 읽기와 쓰기가 번갈아 나올 때입니다.

JavaScript
for (const el of items) {
  const w = box.offsetWidth; // 리플로
  el.style.width = w + "px"; // 표시만
}

둘째 바퀴부터는 앞 바퀴의 쓰기가 표시를 남겨 두었습니다. 그래서 읽을 때마다 리플로가 다시 일어납니다. 항목이 천 개면 리플로도 천 번 가까이 일어납니다. 이렇게 리플로가 쉴 새 없이 되풀이되는 상태가 레이아웃 스래싱입니다.

고치는 방법은 읽기를 반복문 밖으로 빼는 것입니다. 읽기를 먼저 몰아서 하고 쓰기를 뒤에 몰면 리플로는 다음 프레임 직전의 한 번으로 줄어듭니다.

JavaScript
const w = box.offsetWidth;   // 읽기는 한 번
for (const el of items) {
  el.style.width = w + "px"; // 표시만
}

리플로가 화면을 끊기게 하는 까닭

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

화면이 부드러우려면 다음 프레임이 나가기 전에 리플로와 칠하기가 끝나야 합니다. 화면이 1초에 60번 바뀐다면 한 프레임에 쓸 수 있는 시간은 60분의 1초입니다. 이 안에 못 끝내면 그 프레임을 놓쳐 화면이 멈칫합니다.

모든 리플로가 문제는 아닙니다. 버튼을 눌러 메뉴를 한 번 펼칠 때의 리플로는 대개 눈에 띄지 않습니다. 신경 쓸 때는 둘입니다. 애니메이션처럼 프레임마다 리플로가 일어날 때와 레이아웃 스래싱처럼 한 프레임 안에서 여러 번 일어날 때입니다.

리플로를 줄이는 방법

처방은 대부분 두 방향 가운데 하나입니다. 리플로의 횟수를 줄이거나 리플로를 아예 안 거치는 길로 돌아가는 것입니다.

처방 줄이는 것
크기를 읽는 코드와 바꾸는 코드를 따로 모은다 한 프레임 안의 리플로 횟수
움직임은 transform 과 opacity 로 만든다 리플로 자체
이미지와 광고 칸에 너비와 높이를 미리 적어 둔다 늦게 도착한 것이 부르는 리플로

둘째 줄의 transform 은 요소를 옮기거나 돌리거나 키우는 CSS 속성입니다. opacity 는 투명도를 바꾸는 속성입니다. 두 속성은 다른 요소의 배치에 영향을 주지 않습니다.

브라우저는 페이지를 한 장에 다 칠하지 않고 여러 장의 레이어에 나눠 칠하기도 합니다. 레이어는 따로 칠해 두었다가 합성 단계에서 겹치는 그림 한 장입니다. 요소가 자기 레이어에 따로 칠해져 있으면 그 레이어를 옮기거나 흐리게 해서 다시 겹치기만 하면 됩니다. 그래서 레이아웃과 페인트를 건너뛰고 합성만 다시 합니다.

같은 이동도 top 이나 left 로 만들면 프레임마다 리플로가 일어납니다. transform 으로 만들면 요소가 화면에서 보이는 위치만 바뀌고 배치 계산은 그대로입니다.

어디서 리플로가 일어나는지는 개발자 도구로 찾습니다. 크롬 개발자 도구의 성능 패널에서 기록을 떠 보면 Layout 이라는 이름의 막대가 보입니다. 이 막대가 길거나 한 프레임에 여러 개 찍혀 있으면 리플로를 의심할 곳입니다.

같은 이름의 다른 뜻

리플로라는 이름은 다른 분야에서도 쓰입니다. 뜻은 다르지만 「다시 흘려 채운다」는 그림은 같습니다.

분야 뜻
브라우저 이 글의 주제. 요소의 위치와 크기를 다시 계산한다
웹 접근성 화면 폭을 좁히거나 크게 확대해도 글이 폭에 맞춰 다시 줄바꿈되어 가로 스크롤 없이 읽히는 성질
전자책 글자 크기나 화면 크기에 맞춰 쪽 나눔이 다시 정해지는 배치 방식. 쪽이 고정된 배치와 맞선다
전자 부품 조립 기판에 납 반죽을 바르고 부품을 올린 뒤 열을 가해 납을 녹였다 굳혀 붙이는 납땜 공정

웹 접근성의 뜻은 브라우저의 뜻과 이어져 있습니다. 웹 접근성 지침인 WCAG(Web Content Accessibility Guidelines, 웹 콘텐츠 접근성 지침)에 이 이름의 기준이 있습니다. 폭이 좁아지면 브라우저는 리플로로 글을 다시 줄바꿈합니다. 너비를 고정해 둔 페이지에서는 이 줄바꿈이 일어나지 않습니다. 이 기준은 페이지가 그 재배치를 막지 않기를 요구합니다.

관련 항목

리플로가 지나가는 렌더링 단계

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

리플로와 함께 다시 도는 화면 갱신

리페인트 · 페인트 무효화 · 합성 레이어 · 프레임 · 프레임률

리플로가 되풀이되어 생기는 성능 문제

강제 동기 레이아웃 · 레이아웃 스래싱 · 레이아웃 이동 · 누적 레이아웃 이동 · FOUC

리플로를 일으키는 CSS 속성과 배치 방식

CSS · 박스 모델 · display · visibility · 노멀 플로 · 플렉스박스 · CSS 그리드 · 절대 위치 지정 · 웹 폰트 · 뷰포트

리플로를 건너뛰게 해 주는 수단

CSS 트랜스폼 · opacity · will-change · 하드웨어 가속 · 컴포지터 스레드 · requestAnimationFrame · DocumentFragment

리플로 시간을 쥐는 실행 구조

메인 스레드 · JavaScript · 이벤트 루프 · 스레드

리플로를 재는 도구와 지표

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

리플로와 이름이 겹치는 다른 분야의 용어

WCAG · 접근성 · 반응형 웹 디자인 · EPUB · 리플로 솔더링 · 표면 실장 기술

다른 이름: reflow · 리플로우