웹 폰트 사용 가이드

늘보·2022년 2월 5일
15

폰트

목록 보기
1/1

이 글은 웹폰트 사용하기, 웹 폰트 사용과 최근 동향 두 글을 메인으로 읽고 요약, 정리 + 제 생각을 추가한 글입니다. 사실 이 글보다 위의 두 글을 꼼꼼하게 읽는게 훨씬 좋아요!

보통 멋진 글꼴을 발견한다면 어떻게 적용하는가? 일단 나는 글꼴 그 동안 글꼴(폰트) 적용에 많은 노력, 시간이 필요한 줄 모르고 그냥 html 코드에 link 태그 하나 추가하는 방식으로 적용해서 글꼴을 사용해왔다.

예를 들어, 난 Pretendard 라는 폰트를 좋아한다.(깔끔하고 예쁜 듯?) 이걸 적용하고 싶을 때, 주로 아래와 같은 방법을 이용해왔다.

// css
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');

// html
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />

폰트를 직접 저장하고 쓰는게 아닌 이상, 이렇게 웹에서 불러오는 방식을 많이 사용할 것이다. 매우 간단하다.

문제 상황

그런데 폰트가 갑자기 렉이 걸린 듯 일정 시간이 지나고 바뀐다거나, 화면이 로드되자마자 바뀌어 있는게 아니라 0.몇초의 매우 짧은 시간 동안 글자가 보이지 않다가 나타나거나 한 적이 있는가?

난 있다. 그러나 당시에는 이게 대수롭지 않은 일이라 생각해서 해결해야겠다는 생각도 없었다. 그런데 만약 다음과 같은 상황이 일어난다면 어떨까?

Not이라는 글자만 다른 폰트를 지정해서 의미를 강조하려 했던 걸로 보인다. 그런데 로딩 전과 로딩 후의 의미가 너무 명백할 정도로 다르지 않은가? (대통령 당선 / 당선 X의 차이다!)

이는 당시 미국에서 실제 있었던 사건으로, 이 사건으로 웹 폰트 로딩 문제가 이슈가 되었다고 한다. 이 이슈의 원인을 알기 위해서는 먼저 웹 폰트를 웹 페이지를 렌더링하는 과정을 살펴봐야 한다. (브라우저의 렌더링 과정을 어느정도 이해하고 있어야 한다)

문제점의 원인

주목해야 할 부분은 CSSOM(CSS Object Model)을 생성하는 과정이다. 이 과정에서 외부 웹 폰트 링크로 정의된 부분을 만나고 해당 폰트 파일을 다운로드하기 시작한다. 하지만 그리기(paint) 단계에서 웹 폰트 파일처럼 외부 링크로 연결된 파일의 다운로드가 완료되지 않았으면 브라우저는 해당 자원을 사용하는 콘텐츠의 렌더링을 차단한다. 그래서 Mitt Romney 기사의 사례와 같이 화면에 텍스트 "Not"이 보이지 않는 현상이 발생한다. - 인용: 웹 폰트 사용과 최근 동향

실제로 폰트는 용량이 매우 크다. (특히 한글 글꼴은 더) 따라서 폰트를 로드하는데 시간이 걸리는 건 당연하다. 특히, 한국 같이 네트워크 속도가 빠른 국가가 아니라면, 그 시간은 더욱 오래 걸리게 될 것이다.

  • 이렇게 웹 폰트는 다운로드하는데 시간이 걸린다.

그럼 폰트의 로드 시간을 줄이는 방법에는 어떤 것이 있을까? 그걸 알려면 먼저 로컬 폰트와 웹 폰트의 차이점에 대해 알아야 한다.

로컬 폰트와 웹 폰트

로컬 폰트

로컬 폰트는 사용자의 로컬 컴퓨터에 설치 되어 있을 경우에만 화면에 표시할 수 있다.

웹 폰트

사용자의 로컬 컴퓨터에 폰트 설치 여부와 상관 없이 온라인의 특정 서버에 위치한 폰트 파일을 다운로드하여 화면에 표시해주는 웹 전용 폰트이다.

예를 들면, 웹폰트를 사용하지 않았을 경우 나눔고딕 폰트 미설치 PC에서는 기본 폰트인 돋움 폰트가 노출되고 나눔고딕 폰트 설치 PC에서는 나눔고딕 폰트가 노출됩니다. - 인용: 웹 폰트 사용하기

일반적으로 사용자의 PC에 폰트가 설치되어 있다는 보장이 없으니, 많은 사이트들은 자연스럽게 웹 폰트를 사용하게 되는 것이다. 이제 웹 폰트를 적용하기 위해 @font-face 속성을 알아보자.

@font-face

@font-face {
  font-family: <a-remote-font-name>;
  src: <source> [,<source>]*;
  [font-weight: <weight>];
}


// Chrome 브라우저의 경우
@font-face {
  font-family: NanumSquareWeb;
  src: local(NanumSquareR), /* 첫번째 */
       local(NanumSquare), /* 두번째 */
       url(NanumSquareR.eot) format('embedded-opentype'),
       url(NanumSquareR.woff) format('woff'), /* 세번째 */
       url(NanumSquareR.ttf) format('truetype'); /* 네번째 */
  font-weight: normal;
  unicode-range: U+0-10FFFF;
}

h1 {
  font-family: NanumSquareWeb, sans-serif;
}

@font-face에는 위 코드와 같이

  • font-family
  • src(source)
  • font-weight
  • unicode-range

속성이 존재한다. 그럼 각각의 속성이 무엇을 의미하는지 알아보자.

font-family

a-remote-font-name: font 속성에서 폰트명으로 지정될 이름을 설정한다.

font-family에서 선언한 웹 폰트 패밀리명과 사용할 웹 폰트명이 반드시 같아야 하는 것은 아니다. 그러나 유지보수를 위해 동일하게 해주는 것이 좋다.

추가: IE 11 이하에서는 font-family에 선언한 웹폰트 패밀리명이 31자 이상 되면 적용 되지 않는다고 합니다.

src

src(source): 원격 폰트 파일의 위치를 나타내는 URL 값을 지정하거나, 사용자 컴퓨터에 설치된 폰트명을 local 형식으로 지정하는 속성이다. 폰트의 실제 소스라고 생각하자.

  • 콤마(,)를 사용해서 여러번 중첩해서 사용할 수 있으며, 마지막에 오는 속성에 세미콜론(;)만 붙여주면 된다.

  • 브라우저에 적용할 수 있는 폰트를 찾을 때까지 선언한 순서대로 이동한다.

@font-face {
  font-family: NanumSquareWeb;
  src: local(NanumSquareR), /* 첫번째 */
       local(NanumSquare), /* 두번째 */
       url(NanumSquareR.eot) /* 세번째 */
       url(NanumSquareR.woff)  /* 네번째 */
       url(NanumSquareR.ttf)  /* 다섯번째 */
  font-style: normal;
  font-weight: normal;
  unicode-range: U+0-10FFFF;
}

h1 {
  font-family: NanumSquareWeb, sans-serif;
}

현재 format 속성을 적용하지 않았을 경우입니다.

  • 위의 경우, 크롬 브라우저는 .eot 확장자를 지원해주지 않기 때문에 네번째인 url(NanumSquareR.woff) 파일이 적용된다. 그렇지만 세번째 파일인 eot파일까지 다운로드는 된다. (필요없는 폰트 파일이다!)

이러한 불필요한 다운로드를 막기 위해서 format 속성을 사용하게 된다.

@font-face {
  font-family: NanumSquareWeb;
  src: local(NanumSquareR), /* 첫번째 */
       local(NanumSquare), /* 두번째 */
       url(NanumSquareR.eot) format('embedded-opentype'),
       url(NanumSquareR.woff) format('woff'), /* 세번째 */
       url(NanumSquareR.ttf) format('truetype'); /* 네번째 */
}

위 코드와 같이 format 속성을 사용하면 지원 불가능한 .eot 파일은 건너뛰고 위 코드에 적힌 순서대로 파일이 적용된다.

font-weight

font-weight 속성은 아마 FE 개발자라면 익숙할 것이다. 숫자가 작을수록 얇은 굵기, 숫자가 높을수록 굵은 굵기이다.

그런데 다음과 같은 상황을 생각해보자. 다음과 같이 Light, Regular, Bold의 font-weight를 설정했다고 해보자.


// Light
@font-face {
  font-family: NanumSquareWeb;
  src: url(NanumSquareL.woff) format('woff');
  font-weight: 300;
}

// Regular
@font-face {
  font-family: NanumSquareWeb;
  src: url(NanumSquareR.woff) format('woff');
  font-weight: 400;
}

// Bold
@font-face {
  font-family: NanumSquareWeb;
  src: url(NanumSquareB.woff) format('woff');
  font-weight: 700;
}

그런데 만약 font-weight: 200, font-weight 600은 선언이 안되있는데 font-weight: 200과 font-weight:600일 경우 어떤 웹폰트가 적용될까?

h1 {
  font-family: NanumSquareWeb, sans-serif;
  font-weight: 200; /* ? */
}
h2 {
  font-family: NanumSquareWeb, sans-serif;
  font-weight: 600; /* ? */
}

이와 관련해서 규칙 4가지가 있다.

  • Rule 1: 만약 font-weight가 600 이상이면, 가능한 큰 font-weight 중 가장 가까운 font-weight가 적용된다.(만약 없다면 작은 font-weight 중 가까운 font-weight)

  • Rule 2: 만약 font-weight가 300 이하이면, 가능한 작은 font-weight 중 가장 가까운 font-weight가 적용된다.(만약 없다면 큰 font-weight 중 가까운 font-weight)

  • Rule 3: 만약 font-weight가 400이면, 500이 적용되고 500이 없으면 300 이하일 때 규칙이 적용된다.

  • Rule 4: 만약 font-weight가 500이면, 400이 적용되고 400이 없으면 300 이하일 때 규칙이 적용된다.

  • 따라서 위의 규칙에 따르면 font-weight: 200은 가능한 작은 font-weight 중 가까운 font-weight: 300이 적용되어 Light 폰트가 적용되고,

  • font-weight: 600은 가능한 큰 font-weight 중 가까운 font-weight: 700이 적용되어 Bold가 적용된다.

unicode-range

이 속성은 사용할 유니코드의 범위를 정한다. 유니코드 범위 내 사용하는 문자가 없으면 웹폰트를 다운로드 하지 않는다.

unicode-range: U+0-10FFFF;

'-' 를 이용하여 유니코드 범위를 설정하여 사용한 예시다. U+0에 해당하는 글자부터 U10FFFF까지 폰트를 적용한다고 이해하면 될 것이다.

그럼 이제 기본적인 속성들을 모두 살펴봤으니 본격적으로 웹폰트의 로드를 빠르게 할 수 있는 방법을 알아보자!

폰트 파일 용량 줄이기

위에서 언급했듯이, 폰트는 용량이 매우 크다. 웹 폰트는 네트워크를 통해 다운로드하는 자원이기 때문에 파일의 크기가 크면 웹 폰트가 적용된 글자가 화면에 표시될 때까지 시간이 지연되는 문제가 발생한다.

폰트 파일의 크기를 줄이면 그만큼 다운로드하는 속도 또한 빨라질 것이다.

WOFF 2.0 형식 폰트

폰트의 형식은 다양하다. 위에서 @font-face 문법의 src 속성에서 봤듯이, eot, ttf, woff, woff2, svg 등 다양한 형식을 가지고 있다.

다음은 나눔스퀘어라운드 폰트의 웹 폰트 크기를 비교한 것이다.

폰트 형식에서 WOFF(Web Open Font Format) 형식과 WOFF 2.0 형식(이하 WOFF2 형식)은 압축된 폰트 형식이다. 같은 계열에 속하는 WOFF 형식과 WOFF2 형식에서는 WOFF2 형식이 30~50% 더 압축된 형식이다. - 인용: https://d2.naver.com/helloworld/4969726

폰트 형식에 따른 브라우저 지원 범위는 다음과 같다.

IE를 제외하면 모두 WOFF2 형식을 지원한다! (IE는 이제 그만..) 그래도 아직은 IE를 대응해야 하는 경우가 잦으니

  • IE에서는 WOFF 형식을

  • 그 외의 브라우저에서는 WOFF2 형식을 사용하도록 하자.

서브셋 폰트

서브셋 폰트(subset font)는 폰트 파일에서 불필요한 글자를 제거하고 사용할 글자만 남겨둔 폰트다.

  • 영어는 26개 알파벳으로 이루어져 있다. 영문 폰트에는 대소문자를 포함해 총 72자의 글자가 필요하다.

  • 하지만 한글은 자음, 모음의 조합으로 구성되어 있다. 모든 경우를 조합하면 한글의 글자 수는 11,172자나 된다. 그래서 한글 폰트 파일은 영문 폰트 파일보다 용량이 크다.

다음은 11,172자의 한글을 포함하는 나눔바른고딕 폰트의 일부분이다.

노란색 블럭이 씌워져 있는 글자들을 보자. 정말 실생활에서 쓸 일이 없는 글자들이다. 이렇게 불필요한 글자를 제거하고 사용하는 글자만 남겨둔 폰트가 서브셋 폰트이다.

다음은 11,172자에서 2350자로 서브셋 폰트를 만든 예다.

용량이 매우 크게 줄어든 것을 확인할 수 있다.

위와 같은 서브셋 폰트는 위의 @font-face에서 확인한 unicode-range 속성으로 만들 수 있다. 그러나 이렇게 유니코드 표를 참고하며 unicode-range 속성으로 만들기는 너무 힘드니 다음과 같은 라이브러리를 사용하도록 하자.

리소스 순서 최적화

위에서 브라우저는 웹 페이지를 렌더링하는 과정에서 웹 폰트가 다운로드되지 않았으면 해당 텍스트의 렌더링을 차단한다고 하였다. 이런 렌더링 차단 방식은 브라우저 별로 이가 있는데, 대표적으로 FOIT, FOUT 방식이 있다.

아래 이미지에서 차이를 확인해보자.

FOIT(Flash of Invisible Text)

웹 폰트 파일이 로드되기 전 까지 웹 페이지를 로드하지 않고 웹폰트 로드가 완료되면 웹 페이지를 로드하기 때문에 로드가 완료되기 전 까지 텍스트가 안보이는 현상이다. IE 브라우저를 제외한 나머지 브라우저(Chrome, Safari 등)에서 발생한다.

  • 폰트가 로딩되지 않으면 웹페이지의 블락을 가져온다

  • 모던 브라우저의 경우 3초 동안 기다리고 그 후에는 시스템 기본 폰트를 보여준다.

FOUT(Flash of Unstyled Text)

웹 폰트 파일이 로드되기 전 까지 웹 페이지를 로드하지 않고 웹 폰트 로드가 완료되면 웹 페이지를 로드하기 때문에 로드가 완료되기 전 까지 텍스트가 안보이는 현상이다. IE, Edge 브라우저에서 발생한다

  • 흔히 말하는 브라우저의 깜빡임이다.

  • 폰트에 따라 자간, 높이에 따라 레이아웃이 변경될 수 있다.

FOIT, FOUT 현상 모두 사용자 경험에 악영향을 끼치는 요소들이다.

둘 중에 어떤 걸 선택해야 할까?

결론부터 말하자면, 굳이 선택하라면 FOUT 방식을 사용한다. 텍스트가 아예 보이지 않는 것보다는 그나마 텍스트라도 나오는게 더 나으니까..!

  • 디자이너 입장에서는 FOUT이 너무나 신경 쓰인다.

결국 두 현상 모두 선택이 아닌 최소화해야 하는 것들이다.

그럼 이제 FOUT 방식으로 폰트 로딩을 최적화하는 방법을 보자.

font-display

CSS의 font-display 속성을 사용하면 웹 폰트의 로딩 상태에 따른 동작을 설정할 수 있다.

font-display 속성은 다음과 같이

  • auto (default)
  • block
  • swap
  • fallback
  • optional

5가지 옵션이 있다. auto 속성은 브라우저의 기본 동작에 맡기는 값이므로 빼자.(위에서 다 설명했기 때문)

block

block 옵션은 FOIT와 동일하게 작동하는 옵션이다. 웹 폰트가 로딩되지 않았을 때는 텍스트를 렌더링하지 않는다(최대 3초). 웹 폰트 로딩이 완료되면 웹 폰트를 적용한다.

swap

swap 옵션은 FOUT와 동일하게 작동하는 옵션이다. 우선 폴백 폰트로 글자를 렌더링하고, 웹 폰트 로딩이 완료되면 웹 폰트를 적용한다. 웹 폰트 로딩 여부와 관계없이 항상 텍스트가 보인다.

fallback

fallback 옵션을 사용하면 우선 100ms 동안 텍스트가 보이지 않고, 그 후 폴백 폰트로 렌더링한다. 특이한 점은 약 2초의 전환(swap) 시간이 있다는 점이다. 이 시간 안에 로딩이 완료되면 웹 폰트로 전환한다. 하지만 이 시간이 지나면 웹 폰트 다운로드가 완료되어도 웹 폰트로 전환하지 않고 폴백 폰트를 유지한다.

전환 시간 이후에 다운로드된 웹 폰트는 웹 페이지에 적용되지는 않지만 캐시에는 저장된다. 그래서 추후에 사용자가 다시 방문했을 때 바로 웹 폰트가 적용된다는 장점이 있다.

optional

optional 옵션은 fallback 옵션과 비슷하지만 다르게 작동하는 옵션이다. 우선 100ms 동안 텍스트가 보이지 않고 그 후 폴백 폰트로 전환한다.

웹 폰트를 다운로드하지만 브라우저가 네트워크 상태를 파악해 웹 폰트 전환 여부를 결정한다는 점이 이 옵션의 특이한 점이다. 예를 들어 네트워크의 연결 상태가 안 좋으면 웹 폰트의 다운로드가 완료되어도 캐시에 저장만 하고 전환은 하지 않는다.

font-display 속성은 IE를 제외한 브라우저가 모두 지원하고 있다. 크롬, 사파리 같은 브라우저는 기본적으로 FOIT 방식으로 폰트 로딩을 차단하니 FOUT과 동일하게 작동하는 swap 옵션을 사용하자.

Font Face Observer 라이브러리

Font Face Observer 라이브러리는 웹 폰트의 로딩 상태를 추적할 수 있는 폰트 로더(font loader)로, 파일 크기가 작고 실행 속도가 빠르다는 장점이 있다.

CSS에는 웹 폰트를 적용하지 않은 상태와 적용한 상태를 선언해 두고, 적용하지 않은 상태의 CSS가 먼저 적용되도록 한다.

JavaScript에는 사용할 웹 폰트의 이름을 파라미터로 하는 FontFaceObserver 객체를 생성한다. 이 객체는 웹 폰트의 로딩 상태를 추적하는 객체다. 그리고 load() 메서드를 사용해 웹 폰트의 로딩이 완료되면 CSS 클래스가 추가되도록 한다.

이렇게 설정하면 처음에는 폴백 폰트를 설정한 CSS가 적용된다. 그리고 웹 폰트의 로딩이 완료되면 웹 폰트를 설정한 CSS가 적용된다. 마치 FOUT 방식처럼 작동하는 것이다.

preload

브라우저에게 리소스가 필요할 것이라고 알려주는 힌트입니다. 하지만 리소스를 로드하는것이 좋을지 안 좋을지는 브라우저가 결정한다.

해당 리소스를 다른 리소스보다 빨리 로딩한다. 중요도가 높은 자원(폰트, 이미지, 스크립트 파일 등)을 의도적으로 먼저 로딩할 때 사용하는 것이다!

<head>
  <link rel="preload" href="../webfont/NanumSquare/NanumSquare.woff" as="font" crossorigin>
</head>

이렇듯 웹 폰트 파일에 preload 옵션을 사용하면 다음 그림과 같이 CSS 파일보다 먼저 웹 폰트 파일의 다운로드를 시작한다. 동일한 환경에서는 렌더링도 더 빨리 끝난다. 로딩이 빠르기 때문에 렌더링 차단 시간 역시 줄어든다.

주의: 속성중에서 ascrossorigin이 있는데 preload를 사용할 경우에는 반드시 사용해 주어야 합니다. 만약 사용하지 않으면 같은 파일을 두번 요청하게 됩니다.

preload 옵션은 Chrome, Opera, Android에서만 사용 가능합니다.

참고 링크

3개의 댓글

comment-user-thumbnail
2023년 5월 10일

웹 폰트에 대해서 잘 몰랐는데, 덕분에 배우고갑니다! 감사합니다 !!

1개의 답글
comment-user-thumbnail
2023년 12월 13일

안녕하세요, 포스팅 관련 문의 건으로 연락드립니다. 연락처를 찾지 못해 댓글로 문의드려요. hyelim@sandoll.co.kr 으로 메일 보내주실 수 있을까요? 감사합니다.

답글 달기