[F-Lab 모각코 챌린지 8일차]

milknsoda·2023년 6월 8일
0

F-Lab

목록 보기
5/5

TIL

  1. 브라우저 렌더링
  2. 폰트 최적화

브라우저 렌더링

그동안 계속 정리해왔던 브라우저 렌더링을 하나로 합쳐보자.

1. 통신

브라우저가 렌더링을 하기 위해서는 데이터가 필요하고, 우리는 브라우저에서 URL을 통해 그 데이터를 요청한다.

1) URL 입력

주소창에 URL을 입력하면,

1. 브라우저 캐시

2. OS 캐시

3. 라우터 캐시

4. DNS 서버

순서대로 찾아가며 대상 IP를 탐색한다.

2) HTTP 요청

URL을 통해 찾은 IP로 데이터를 요청하기 위해서 HTTP 요청 메시지를 생성한다.

HTTP는 보내는 데이터에 대해 정해진 규약.

3) TCP 통신

생성된 HTTP 요청은 TCP 통신을 통해서 대상 IP로 전달된다.

  1. 3-way handshake (SYN / SYN, ACK / ACK)
    • 작은 패킷 데이터를 주고 받아 연결의 신뢰성을 확보한다.
  2. 데이터 전송
    • 데이터는 작은 조각으로 나눠서 전송
    • 각 데이터는 인덱스를 가지고 있어서 받아서 재조합 가능
    • 조각난 데이터는 유실되거나 손상되었을 경우, 그 조각만 재전송할 수 있어 효율적이다.

4) HTTP 응답

HTTP 통신은 요청과 응답이 하나의 세트로 이루어져 있어서 찾아간 IP에서 데이터를 HTTP 응답 메시지로 만든다.

5) 다시 한번 TCP 통신

HTTP 응답은 다시 TCP 통신을 통해 요청 IP로 보내진다.

2. 렌더링

일련의 통신을 통해 받은 데이터를 화면에 그리기 위해서는 적절한 가공이 필요하며, parsing, style, layout, paint, composite의 단계를 거친다.

HTML의 기본적인 구조
<!DOCTYPE html>
<html>
  <head>
    <meta>
    <link>
    ...
  </head>
  <body>
    <div></div>
    ...
  </body>
</html>

1) parsing

렌더링의 기본적인 과정은 그림과 같다.

통신을 통해 받은 HTML은 우선 파싱을 통해 DOM으로 만들어야 한다.

(1) <meta>

  • 파서가 meta 태그를 만나는 경우, content를 확인하고 비어있지 않으면, refresh가 일어난다.
  • 한번 refresh가 일어나면, 최소 시간이 지나기 전까지는 동작하지 않는다.
  • 외부 리소스를 불러올 수 있다.
  • srcset, imagesrcset, media와 같은 특수한 속성을 지닌 경우를 제외하면, 모두 파싱 후에 로드한다.
  • rel 속성
    • 값이 비어있는 경우, link가 직접적으로 참조하는 리소스 뿐만 아니라 하위에 연결된 모든 리소스가 로드될 때까지 지연시킨다. > 렌더 블로킹
    • preload : 파싱 중에 가능한 빠른 순서로 로드해서 캐싱
      • as 속성 값으로 리소스 타입을 예측하여 로드
  • stylesheet의 경우에는 @import로 불러오는 외부 리소스도 모두 로드될 때까지 지연시킨다. > 렌더 블로킹

(3) <script>

  • javascript 코드 부분으로 태그를 만나는 즉시 파싱하여 실행한다. > 렌더 블로킹
    • async : 비동기적으로 코드를 로드 및 파싱 후 실행
      • 즉각적인 렌더 블로킹은 막을 수 있지만, 실행 시점에 렌더 블로킹 가능성은 여전히 남아있다.
      • 로드되는 대로 파싱 및 실행이 이루어지기 때문에 실행 순서를 보장할 수 없다.
    • defer : 의도적인 지연 발생, 문서 파싱이 끝난 후 실행
      • defer 속성을 가진 스크립트는 순차적으로 실행되기 때문에, 실행 순서를 보장할 수 있다.
  • 스크립트 실행 중에 파싱되지 않았거나 반영되지 않은 CSS를 참조하는 경우, 즉시 실행이 중단된다. > 스크립트 블로킹

(4) 그 외

파서에 의해서 트리 구조의 오브젝트를 생성

(+) 문서를 모두 파싱하면, load 이벤트가 발생한다.

2) style

파싱 과정에서 생성된 DOM 트리와 CSSOM 트리를 결합해서 Render 트리를 생성한다.

렌더 트리는 화면에 표시되는 요소만을 오브젝트화 사용하기 때문에, <head> 태그와 display: none 속성을 가진 요소들은 제외된다.

3) layout

각 요소들의 크기와 위치를 픽셀로 계산하여 전체적인 화면의 레이아웃 구성한다.

  1. 부모 요소: 본인의 너비 결정
  2. 자식 요소: x, y 설정 (+ 자식 높이 계산)
  3. 부모 요소: 자식 요소의 높이의 누적 합 + 패딩 + 여백으로 본인의 높이 결정 (계산된 높이는 본인이 아닌 상위 요소에서 사용)

더티 비트 개념은 이 단계에서 확인하고 사용된다.

더티 비트 : 브라우저는 변경되었거나 추가된 요소에 더티 상태를 부여하고, 렌더링할 때 더티 상태를 추적하여 그 요소만 다시 렌더링한다.

4) paint

이 단계에서는 실제 화면에 표시될 요소들을 layer별로 그린다.

Layer

  • 레이어는 기본적으로 root 레이어가 있다.

  • will-change, position (absolute, sticky, fixed), transform (3D 변환), canvas (3D 요소 그려진) 요소들은 개별 레이어로 분리된다.

stacking contexts

각 요소는 벡터로 구성되어 있다가 픽셀화되어 레이어에 얹어진다.

요소 별로 그 순서는 정의되어 있다.

  • 요소 (table 제외)

    1. 배경 색
    2. 배경 이미지
    3. 테두리
  • table

    1. table 배경색
    2. column group 배경색
    3. column 배경색
    4. row group 배경색
    5. row 배경색
    6. cell 배경색
    7. 테두리

5) composite

layer 별로 그려진 것을 합쳐서 하나의 화면으로 표시한다.

GPU 가속을 통해서 성능을 향상시킬 수 있다.


폰트 최적화

브라우저 렌더링 과정에서 폰트를 최적화할 수 있는 방법은 뭘까.

1. rel="preload" 활용

<link rel="preload" as="font" />

rel="preload" 값을 가진 link 태그는 가능한 빠른 시점에 해당 리소스를 로드하기 때문에, 폰트 로딩으로 인한 지연을 줄일 수 있다.

2. 로컬 폰트 사용

@font-face {
	font-family: Gothic;
	src: local('Gothic'), # 로컬 폰트 우선 사용
	     url(Gothic.woff2) format('woff2'),
	     url(Gothic.woff) format('woff'),
	     url(Gothic.ttf) format('ttf'),
}

local을 적용해서 현재 환경에 해당 폰트가 존재하는 경우에는 외부 리소스를 불러오지 않고 로컬 폰트를 적용한다.

3. 폰트 파일 크기 최소화

폰트는 형식에 따라 같은 폰트여도 파일의 크기가 다르다.

일반적으로 woff, woff2 형식이 웹용 폰트 형식으로 다른 형식의 폰트에 비해 크기가 작아, 로딩을 줄이는 방향으로 개선 가능하다.

형식사이즈 (예시)
woff2814KB
woff1,153KB
ttf2,615KB
otf1,601KB

4. 서브셋 폰트 사용

서브셋 폰트 : 사용 빈도가 낮아 필요성이 낮은 글자를 제거하여 크기를 줄인 폰트

한글은 조합에 따라 글자수가 많아지기 때문에 자연스럽게 폰트 파일의 크기가 커지게 된다.

이러한 단점을 보완하기 위해서 사용 빈도가 낮은 글자들을 제거하여 폰트 파일의 크기를 줄여 로딩 속도를 개선할 수 있다.

예를 들어 나눔바른고딕은 11,172자에서 불필요한 글자를 제거하여 2,350자로 줄임으로써 2.4MB에서 586KB로 크기를 줄였다.

5. unicode-range 적용

unicode-range 속성은 기본적으로 정해진 글자에만 폰트를 적용할 때 사용된다.

최적화의 관점에서 unicode-range 속성의 장점은 전체 텍스트에 정해진 글자가 포함되지 않는 경우에는 폰트를 다운로드 하지 않는다는 것에 있다.

@font-face {
	font-family: 'korea font';
	font-weight: 500;
	src: local('korea font'),
		url('path/to/korea/font') format('woff2'),
		url('path/to/korea/font') format('woff'),
		unicode-range: U+1100-U+11FF; 
}

6. 글자가 항상 보이도록 처리

  • FOIT (Flash Of Invisible Text)

    • 폰트가 로드될 때까지 텍스트 표시 x
    • 내용이 전혀 다르게 표시될 수 있음 → FOIT 을 지양해야 하는 이유
  • FOUT (Flash Of Unstyled Text)

    • 폰트가 로드되기 전에 레이아웃이 변경될 수 있음
      • 레이아웃 변경을 최소화하기 위해서 사이즈가 비슷한 대체 폰트를 적용
    • 폰트가 로드된 후에 적용 → 대체 폰트 ⇒ 원래 폰트 적용
  • font-display 적용을 통해서 조절

    • 폰트 차단 기간 : @font-face가 로드되지 않았을 때, 보이지 않는 대체 폰트로 렌더링 되는 시간 → 로드되면 폰트 교체

    • 폰트 교체 기간 : @font-face가 로드되지 않았을 때, 대체 폰트로 렌더링 되는 시간 → 로드되면 폰트 교체

    • 폰트 실패 기간 : @font-face가 로드되지 않은 경우, 대체 폰트를 정상 폰트로 인식하는 시간

@font-face {
  font-family: ExampleFont;
  src: url(/path/to/fonts/examplefont.eot) format('eot'),
       url(/path/to/fonts/examplefont.woff) format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: fallback;
}

font-display: auto;     # 유저 에이전트에 의해 결정
font-display: block;    # 짧은 차단 기간 -> 무한대의 교체 기간
font-display: swap;     # 매우 작은 차단 기간 -> 무한대의 교체 기간
font-display: fallback; # 매우 작은 차단 기간 -> 짧은 교체 기간
font-display: optional; # 매우 작은 차단 기간 -> 매우 작은 교체 기간

6. 두께별 폰트 적용

두께별로 개별 폰트를 지정하는 방법도 최적화의 방법 중 하나이다.

@font-face {
  font-family: ExampleFont;
  src: url(/path/to/fonts/examplefont-bold.woff) format('woff')
  font-weight: 700;
}

@font-face {
  font-family: ExampleFont;
  src: url(/path/to/fonts/examplefont-normal.woff) format('woff')
  font-weight: 500;
}

@font-face {
  font-family: ExampleFont;
  src: url(/path/to/fonts/examplefont-thin.woff) format('woff')
  font-weight: 300;
}

이 부분은 좀 더 확인이 필요해보인다.



Stacking Contexts
웹 폰트 사용과 최적화의 최근 동향

0개의 댓글