사전 렌더링 엔진
개념

렌더링 엔진

gabury1고친 사람 github-actions[bot]

렌더링 엔진은 웹 페이지를 받아 화면에 그려 냅니다. 사용자가 브라우저에서 보는 페이지는 전부 이 엔진이 그린 것입니다. 게임과 3D 그래픽에서도 장면을 그리는 부품을 같은 이름으로 부릅니다. 이 항목은 브라우저 안의 렌더링 엔진을 다룹니다.

쉽고 빠른 이해

렌더링 엔진은 서버가 보낸 글자를 사용자가 보는 그림으로 바꾸는 프로그램입니다. 뉴스 기사를 열면 제목은 크게, 본문은 두 단으로 나뉘어 보입니다. 그 배치를 정하고 그리는 것이 엔진입니다.

서버는 그림을 보내지 않습니다. 페이지의 내용과 꾸밈 규칙을 글자로 보낼 뿐입니다. 받은 쪽에서 누군가 그 글자를 읽고 그림으로 바꿔야 합니다.

  1. 내용을 읽어 요소들이 서로 품는 나무 모양으로 정리합니다
  2. 요소마다 어떤 꾸밈이 붙는지 정하고, 화면 어디에 얼마나 크게 놓일지 계산합니다
  3. 계산한 대로 색을 칠하고, 여러 장으로 그린 것을 겹쳐 화면에 내보냅니다

대가는 무언가 바뀔 때마다 이 과정을 다시 돌아야 한다는 것입니다. 크기가 바뀌면 배치 계산을 다시 해야 해서 화면이 버벅일 수 있습니다. 모르는 사이트가 보낸 글자를 읽는 프로그램이라 브라우저는 엔진을 따로 가둬 두고 돌립니다.

상세

잡지 편집 디자이너를 떠올려 봅시다. 기자에게서는 원고를 받습니다. 편집장에게서는 「제목은 크게, 사진은 오른쪽 위에」 같은 지시를 받습니다. 디자이너는 둘을 합쳐 어느 글이 지면 어디에 얼마나 크게 들어갈지 정합니다. 그런 다음 정한 대로 지면을 채웁니다.

렌더링 엔진이 이 디자이너입니다. 원고는 페이지의 내용입니다. 지시는 꾸밈 규칙입니다. 지면은 사용자의 화면입니다.

글자를 받아 화면을 그리는 부품

웹 서버가 브라우저에 보내는 것은 그림이 아니라 글자입니다. 페이지의 내용과 구조는 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)로 적습니다. <h1> 은 제목, <p> 는 문단처럼 꺾쇠 괄호 태그로 글에 이름표를 붙이는 형식입니다.

생김새는 따로 CSS(Cascading Style Sheets, 캐스케이딩 스타일 시트)로 적습니다. 「제목은 굵게」, 「이 상자의 너비는 화면의 절반」 같은 규칙을 모은 글입니다. 내용과 생김새를 나눠 두면 같은 내용을 휴대폰과 모니터에서 다르게 보여 줄 수 있습니다.

두 글자 뭉치를 읽어 픽셀로 바꾸는 프로그램이 렌더링 엔진입니다. 엔진이 없으면 브라우저는 받은 HTML 을 소스 그대로 늘어놓을 수밖에 없습니다. 사용자는 꺾쇠 괄호가 가득한 글을 보게 됩니다.

같은 HTML 이라도 창의 너비, 글꼴, 화면의 해상도에 따라 그림이 달라집니다. 그래서 그림을 서버에서 미리 만들어 보낼 수 없습니다. 페이지를 여는 컴퓨터 안에서 그때그때 그려야 합니다. 이렇게 적어 둔 것을 받아 결과물로 바꾸는 일을 렌더링이라고 부릅니다.

렌더링 엔진과 자바스크립트 엔진의 몫

브라우저 안에는 엔진이 하나 더 있습니다. 페이지가 보낸 자바스크립트 코드를 실행하는 자바스크립트 엔진입니다. 버튼을 누르면 목록에 항목이 늘어나는 것처럼, 페이지를 움직이게 하는 코드가 이쪽에서 돕니다.

두 엔진은 한 가지를 같이 씁니다. 렌더링 엔진이 HTML 을 읽어 만든 문서의 나무 구조입니다. 이 구조를 DOM(Document Object Model, 문서 객체 모델)이라고 부릅니다. 자바스크립트 코드는 DOM 을 고쳐서 화면을 바꿉니다.

하는 일 맡는 엔진
HTML 을 읽어 DOM 을 만든다 렌더링 엔진
CSS 규칙을 요소에 붙이고 배치를 계산한다 렌더링 엔진
픽셀을 칠해 화면에 내보낸다 렌더링 엔진
자바스크립트 코드를 실행한다 자바스크립트 엔진
코드가 요청한 대로 DOM 을 고친다 렌더링 엔진이 함수로 열어 두고, 자바스크립트 엔진이 부른다

표의 마지막 줄이 두 엔진이 만나는 곳입니다. 코드가 DOM 을 고치면 렌더링 엔진은 바뀐 부분을 다시 그립니다.

문서가 픽셀이 되기까지 거치는 단계

엔진이 받는 것은 HTML 과 CSS 두 글자 뭉치입니다. 이 글자는 여러 단계를 지나 화면의 픽셀이 됩니다. 단계마다 앞 단계가 만든 것을 받아 한 가지만 더 정합니다.

먼저 HTML 을 읽습니다. 글자를 읽어 구조로 바꾸는 부분을 파서라고 부릅니다. 파서는 태그가 서로 품는 관계를 따라 DOM 을 만듭니다. <body> 아래에 <h1> 과 <p> 가 매달리는 나무입니다.

CSS 도 따로 읽어 구조로 만듭니다. 이 구조를 CSSOM(CSS Object Model, CSS 객체 모델)이라고 부릅니다. 어느 규칙이 어느 요소를 겨누는지를 찾아 쓰기 좋게 정리해 둔 것입니다.

다음은 스타일 계산입니다. DOM 의 요소 하나하나에 어떤 CSS 규칙이 붙는지 가립니다. 그 결과로 요소마다 최종 생김새를 정합니다. 여러 규칙이 한 요소를 겨누면 더 구체적으로 겨눈 규칙이 대개 이깁니다. 이 단계가 끝나면 요소마다 글꼴과 색과 너비가 정해져 있습니다.

그다음이 레이아웃입니다. 요소마다 화면의 어느 좌표에 얼마만 한 크기로 놓일지 계산합니다. 부모 상자의 너비가 정해져야 자식 상자의 너비가 정해지는 식이라 나무를 위에서 아래로 훑습니다. 창 너비가 바뀌면 이 계산을 다시 합니다.

다음은 페인트입니다. 좌표가 정해진 요소들을 무엇을 어떤 순서로 칠할지 그리기 명령의 목록으로 적습니다. 「이 좌표에 파란 사각형」, 「그 위에 이 글자」 같은 명령입니다. 이 단계에서는 아직 픽셀이 없습니다.

페인트할 때 엔진은 페이지를 한 장에 몰아 적지 않습니다. 따로 움직일 만한 부분은 떼어 다른 판에 적습니다. 옆에서 미끄러져 들어오는 메뉴가 흔히 그렇게 떼어집니다. 이렇게 떼어 둔 판 한 장이 레이어입니다. 떼어 두면 메뉴가 움직일 때 나머지를 다시 칠하지 않고 그 판만 옮기면 됩니다.

페인트 다음이 래스터화입니다. 레이어마다 적어 둔 그리기 명령 목록을 실제 픽셀로 바꿉니다. 이 단계가 끝나면 레이어마다 한 장씩 그림이 생깁니다.

마지막이 합성입니다. 픽셀이 된 레이어들을 순서대로 겹쳐 한 장의 화면을 만듭니다. 이 겹치는 계산은 그림 처리에 특화된 GPU(Graphics Processing Unit, 그래픽 처리 장치)가 맡는 경우가 많습니다.

지금까지의 단계를 한 장에 놓으면 아래와 같습니다. 두 갈래로 읽어 들인 것이 스타일 계산에서 만납니다. 거기서부터는 한 줄기로 내려갑니다.

flowchart TD
    H["HTML → 파서 → DOM"] --> S["스타일 계산"]
    C["CSS → CSSOM"] --> S
    S --> L["레이아웃 · 좌표와 크기"]
    L --> P["페인트 · 그리기 명령"]
    P --> R["래스터화 · 레이어마다 픽셀로"]
    R --> K["합성 · 레이어를 겹친다"]
    K --> V["화면"]

바뀐 만큼만 다시 그리는 방식

페이지는 한 번 그리고 끝나지 않습니다. 자바스크립트 코드가 DOM 을 고칩니다. 사용자가 창 크기를 바꿉니다. 마우스가 올라간 버튼은 색이 바뀝니다. 그때마다 처음부터 다시 그리면 너무 느립니다.

그래서 엔진은 무엇이 바뀌었는지를 보고 필요한 단계부터만 다시 돕니다. 어느 단계부터 도는지는 바뀐 것의 종류가 정합니다.

무엇이 바뀌었나 다시 도는 단계 예
크기나 위치 레이아웃 · 페인트 · 합성 상자의 너비를 바꾼다 · 목록에 항목을 넣는다
색이나 배경 페인트 · 합성 글자 색을 바꾼다
레이어의 위치나 투명도 대개 합성만 메뉴가 옆에서 미끄러져 들어온다 · 팝업이 서서히 흐려진다

아래로 갈수록 건너뛰는 단계가 많아 싸게 끝납니다. 크기가 바뀌면 이웃 요소의 좌표까지 줄줄이 밀리므로 레이아웃을 다시 해야 합니다. 이 재계산을 리플로우라고도 부릅니다.

색만 바뀌면 좌표는 바뀌지 않아 레이아웃을 건너뜁니다. 이렇게 다시 칠하기만 하는 것을 리페인트라고 부릅니다.

엔진은 바뀐 것을 모아 두었다가 다음에 화면을 그릴 때 한꺼번에 처리합니다. 그런데 코드가 레이아웃 결과를 곧바로 물으면 미룰 수 없습니다. 아래 두 줄이 그런 경우입니다.

JavaScript
el.style.width = "300px";  // 쓰기
const h = el.offsetHeight; // 읽기

첫 줄은 너비를 바꿔 레이아웃을 낡게 만듭니다. 둘째 줄은 요소의 높이를 묻습니다. 새 너비에서 글이 몇 줄로 접히는지 알아야 높이가 나오므로, 엔진은 곧바로 레이아웃을 다시 계산해 답합니다.

이 두 줄을 요소 백 개에 번갈아 돌리면 레이아웃도 백 번 돕니다. 이것을 레이아웃 스래싱이라고 부릅니다. 쓰기를 먼저 모아서 하고 읽기를 나중에 몰아 하면 레이아웃은 한 번으로 줄어듭니다.

한 프레임 안에 끝내야 하는 일

화면은 1초에 수십 번 새 그림으로 바뀝니다. 한 번 바뀌는 그림 한 장을 프레임이라고 부릅니다. 스크롤이나 애니메이션이 부드럽게 보이려면 엔진이 다음 프레임이 나갈 때까지 새 그림을 준비해야 합니다.

스레드는 운영체제가 따로따로 돌려 주는 실행 흐름 하나입니다. 스타일 계산과 레이아웃과 페인트는 대개 자바스크립트 실행과 같은 스레드에서 번갈아 돕니다. 이 스레드가 메인 스레드입니다.

그래서 오래 걸리는 자바스크립트 함수 하나가 렌더링을 세웁니다. 함수가 도는 동안 엔진은 새 그림을 그리지 못합니다. 사용자에게는 화면이 멈추고 스크롤이 끊기는 것으로 보입니다.

합성은 메인 스레드 밖에서 도는 경우가 많습니다. 앞의 표에서 「합성만」 다시 도는 변화가 부드러운 까닭입니다. 메인 스레드가 바빠도 이미 그려 둔 레이어를 옮겨 겹치는 일은 계속됩니다.

남이 보낸 문서를 다루는 부품

렌더링 엔진은 처음 가 본 웹사이트가 보낸 HTML 과 CSS, 이미지와 글꼴 파일을 읽습니다. 누가 만들었는지 모르는 입력입니다. 이 입력이 엔진의 버그를 건드려 사용자의 파일이나 다른 사이트의 데이터에 손대지 못하게 막아야 합니다.

엔진은 여러 형식을 읽는 크고 복잡한 프로그램이라 버그가 나옵니다. 그래서 브라우저는 엔진을 권한을 크게 깎은 별도 프로세스 안에서 돌립니다. 권한을 깎아 가둬 두고 돌리는 이 방식이 샌드박스입니다.

엔진이 갇혀 도는 이 프로세스가 렌더러 프로세스입니다. 렌더러 프로세스는 파일을 열거나 네트워크에 직접 붙지 못합니다. 그런 일은 창과 탭을 관리하는 브라우저 본체에 부탁합니다.

한 걸음 더 나가면 브라우저 본체가 사이트마다 렌더러 프로세스를 따로 띄웁니다. 이 방식이 사이트 격리입니다. 아래 그림에서 화살표는 본체가 프로세스를 띄우는 방향입니다.

flowchart TD
    B["브라우저 본체 · 파일과 네트워크를 맡는다"]
    subgraph R1["렌더러 프로세스 · 사이트 A"]
        E1["렌더링 엔진"]
        J1["자바스크립트 엔진"]
    end
    subgraph R2["렌더러 프로세스 · 사이트 B"]
        E2["렌더링 엔진"]
        J2["자바스크립트 엔진"]
    end
    B -->|띄운다| R1
    B -->|띄운다| R2

그림에서 두 엔진은 한 프로세스 안에 짝으로 들어 있습니다. 사이트 A 의 코드가 엔진을 뚫어도 그 프로세스의 메모리에는 사이트 A 의 데이터밖에 없습니다.

그런데 사이트는 출처보다 넓은 묶음입니다. 출처는 주소의 프로토콜과 호스트와 포트를 묶은 것입니다. 사이트는 프로토콜과 주소 끝의 도메인만 봅니다. https://shop.example.com 과 https://blog.example.com 은 출처가 다르지만 사이트는 example.com 하나입니다.

그래서 출처가 다른 두 문서가 한 렌더러 프로세스에 함께 들 수 있습니다. 한 페이지가 다른 페이지를 품을 때가 그렇습니다. 이렇게 품은 페이지가 iframe(inline frame, 인라인 프레임)입니다. shop 페이지가 blog 페이지를 iframe 으로 품으면 둘은 한 프로세스 안에 있을 수 있습니다.

이때는 엔진이 직접 막습니다. 엔진은 문서마다 어느 출처에서 왔는지 적어 둡니다. 코드가 다른 문서의 DOM 에 손을 뻗으면 두 출처를 대조합니다. 출처가 다르면 막습니다. 이 규칙이 동일 출처 정책입니다.

두 장치는 겹으로 선 벽입니다. 동일 출처 정책은 엔진 안에서 출처마다 문서를 가릅니다. 사이트 격리는 엔진이 뚫려도 버티도록 사이트마다 프로세스를 한 번 더 가릅니다.

엔진이 여럿인 까닭

HTML 을 어떻게 읽고 CSS 를 어떻게 그릴지는 웹 표준이라는 공개 문서로 정해져 있습니다. 브라우저를 만드는 회사마다 렌더링 엔진을 따로 만들어 왔습니다. 모든 엔진이 이 표준을 따르려 합니다.

표준을 따라도 엔진마다 결과가 조금씩 어긋납니다. 새 기능이 들어가는 시기가 엔진마다 다릅니다. 표준이 비워 둔 대목을 채우는 방식도 다릅니다. 여러 브라우저에서 같은 모습이 나오도록 맞추는 일을 크로스 브라우징이라고 부릅니다.

엔진 하나를 새로 만드는 일은 아주 큽니다. 그래서 새 브라우저는 대개 엔진을 직접 만들지 않고 이미 있는 엔진을 가져다 씁니다. 겉모습이 다른 브라우저 여럿이 속으로는 같은 엔진을 쓰는 일이 흔합니다.

창 없이 쓰는 렌더링 엔진

렌더링 엔진은 화면이 없는 서버에서도 쓰입니다. 창을 띄우지 않고 브라우저를 돌리는 방식을 헤드리스 브라우저라고 부릅니다. 엔진은 평소처럼 페이지를 그립니다. 그 결과를 화면 대신 파일로 넘깁니다.

백엔드에서는 이 방식으로 웹 페이지를 PDF(Portable Document Format) 파일이나 이미지로 떠냅니다. 자바스크립트가 그린 뒤에야 내용이 채워지는 페이지를 긁어 올 때도 씁니다. 화면을 눌러 보는 자동 테스트도 같은 엔진 위에서 돕니다.

관련 항목

렌더링 엔진이 문서를 그리며 거치는 단계

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

렌더링 엔진이 읽어 들이는 언어와 형식

HTML · CSS · SVG · 웹 폰트 · 이미지 포맷 · JavaScript

렌더링 엔진을 다시 돌게 해 화면을 느리게 하는 문제

리플로우 · 리페인트 · 레이아웃 스래싱 · 강제 동기 레이아웃 · 렌더링 차단 리소스 · 누적 레이아웃 이동

렌더링 엔진이 그림을 내보내는 실행 구조

메인 스레드 · 스레드 · 이벤트 루프 · 프레임 · requestAnimationFrame · GPU · 하드웨어 가속

브라우저 안에서 렌더링 엔진 곁에 서는 부품

브라우저 · 자바스크립트 엔진 · 렌더링 · 브라우저 프로세스 · 네트워크 스택

렌더링 엔진을 가둬 두는 보안 장치

샌드박스 · 렌더러 프로세스 · 프로세스 · 사이트 격리 · 동일 출처 정책 · 출처 · iframe

렌더링 엔진을 구현한 제품

Blink · WebKit · Gecko · Servo · Trident · EdgeHTML

렌더링 엔진의 동작을 맞추는 표준과 참고 문서

웹 표준 · WHATWG · W3C · MDN · 크로스 브라우징 · User-Agent

렌더링 엔진을 창 없이 띄워 쓰는 도구

헤드리스 브라우저 · Puppeteer · Playwright · Selenium · 웹 크롤러 · E2E 테스트

렌더링 엔진과 이름이 겹치는 개념

서버 사이드 렌더링 · 클라이언트 사이드 렌더링 · 게임 엔진 · 3D 렌더링 · 렌더 파이프라인

다른 이름: rendering engine · 브라우저 엔진 · browser engine · 레이아웃 엔진 · layout engine