웹폰트 최적화 기법에 관한 몇가지 이야기

요근래 폰트 최적화에 관해 몇가지 고민할 일들이 생겼다. 구글과 여러 사이트들을 참조하며 정리한 내용들을 공유하고자 한다.

기존의 폰트 로딩 방식

body {
	font-family: "Nanum Gothic",Meiryo,"Noto Sans JP",sans-serif,Lucida Sans Unicode;
}

폰트는 font-family는 통해 적용한다. 적용 시점은 dom과 cssom을 만들고 페이지에서 지정된 텍스트를 렌더링 하는데 필요한 글꼴이 있으면 그 순간 적용된다.

하지만 컴퓨터에 글꼴이 설치되어 있지 않으면 서체를 보여주지 못한다.

그래서 나온게 @font-face이다. font-face는 해당 폰트가 시스템에 없으면 직접 경로를 통해 다운로드 하게 설정할 수 있다. 이를 웹폰트라한다.

@font-face {
  font-family: 'webFont';
  src: url('webfont.woff2') format('woff2'), /* 모던 브라우저. 압축률이 가장 높음(30%) */
       url('webfont.woff') format('woff'), /* 대부분 브라우저. 압축률이 좋음*/
       url('webfont.ttf')  format('truetype'), /* Android, iOS, 압축 x */,
       url('webfont.eot'); /* IE9 호완성 모드, 압축 x */
}
body {
    font-family: webFont, "Nanum Gothic", Meiryo,......
}

하지만 웹폰트에는 대표적인 두가지 문제점이 있다.

FOIT

Flash of Invisible Text의 약자

FOUT

Flash of Unstyled Text의 약자

둘 중 뭐가 나쁜지는 생각의 문제이다. 하지만 현재는 cpu와 네트워크 속도가 어느정도 빠르고 초기 로딩의 경험이 가장 중요하다고 생각한다. 페이지는 로딩할 때 빈 텍스트라면 그게 가장 큰 문제라고 생각한다.

각 브라우저는 어떻게 동작할까

크롬과 파이어폭스의 (+ 사파리) 경우는 FOIT이다. 일단 3초동안 웹폰트가 로드되기를 기다린다. 그 동안은 빈 화면이 나타난다. 만약 3초동안 오지 않으면 시스템 기본폰트를 보여준다.

크롬의 경우 아래와 같은 메시지가 뜬다. Inline-image-2018-02-02 17.37.12.335.png

IE와 EDGE는 FOUT이다. 일단 시스템폰트를 보여주고 웹폰트가 오면 대체한다.

구형 사파리의 경우는 시간제한 없이 FOIT로 웹폰트를 기다렸다. 60초가 넘으면 응답자체가 취소 되기 때문에 콘텐츠 자체를 볼 수 없는 원인이 되기도 하였다. 다행이도 최근에는 timeout을 적용하였다.

Inline-image-2018-02-01 15.07.46.330.png

압축과 캐싱.

@font-face의 여러 속성들 활용

호환성에 대해 알아야 할 부분

font의 지원범위

Inline-image-2018-02-02 12.19.23.163.png

font-display

@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 300;
    font-display: fallback;
    src: url(#{$font-folder}/Roboto-Light.woff2) format('woff2'),
    url(#{$font-folder}/Roboto-Light.woff) format('woff'),
    url(#{$font-folder}/Roboto-Light.ttf) format('truetype');
}

Inline-image-2018-02-02 10.30.39.042.png

local()의 활용

font-face를 쓴다면 font가 시스템에 있는지의 여부와 상관없이 네트워크로 부터 폰트를 다운을 받는다. 그래서 local을 먼저 앞에 선언해 시스템에 있는 것을 쓰도록 해야한다.

@font-face {
    font-family: 'Nanum Gothic';
    font-style: normal;
    font-weight: 400;
    src: local('Nanum Gothic'),
    url(#{$font-folder}/NanumGothic-Regular.woff2) format('woff2'),
    ...
}

두레이에 적용해보면 네트워크에서 받지도 않고 시스템으로 적용된 것을 볼 수있다. Inline-image-2018-02-02 12.36.36.643.png Inline-image-2018-02-02 10.57.09.479.png

Inline-image-2018-02-02 11.08.34.323.png

unicode-range

font face에는 해당 폰트가 지원할 범위를 설정 할 수 있다. 그래서 해당 범위의 문자가 있을 때에만 폰트를 다운받도록 변경이 가능하다. 현재 모든 모던 브라우저 지원이다.

@font-face {
  font-family: 'Ampersand';
  src: local('Times New Roman');
  unicode-range: U+26;
}

한글일 경우 아래정도이다. 한글 : U+0020-U+007E,U+1100-U+11F9,U+3000-U+303F,U+3131-U+318E,U+327F-U+327F,U+AC00-U+D7A3,U+FF01-U+FF60

font-variation-settings

font-variation-settings를 이용하면 가변으로 폰트를 설정할 수 있다.

src: url('source-sans-variable.woff2') format('woff2-variations')

참고: https://medium.com/clear-left-thinking/how-to-use-variable-fonts-in-the-real-world-e6d73065a604

preload

link태그에 preload라는 옵션이 있다. 이 옵션을 가진 리소스는 다른 어떤 리소스보다 빨리 받아온다. font뿐만 아니라 이미지, 비디오, 스크립트등에도 가능하다. 그래서 이것을 쓰면 로딩되기전에 미리 폰트를 받아 올 수 있다.

<link rel="preload" href="fonts/cicle_fina-webfont.woff2" as="font" type="font/woff2" crossorigin="anonymous">

Inline-image-2018-02-02 11.43.40.603.png

FOUT with class

FOIT를 피하기 위한 간단한 방법은 폰트를 스크립트에서 로딩하고 완료되면 class를 동적으로 넣어서 FOUT처럼 동작하게 하는 방법이 있다. webfont가 먼저 필요하지 않으니 브라우저는 기본폰트를 보여주고, 로딩이 완료되면 폰트가 적용되기 때문에 FOIT를 피할 수 있다. 돔을 그리기 전에 미리 폰트를 로딩을 한다면 FOUT 또한 피할 수 있다.

Font Face라는 api가 이미 구현이 되어있다. )

document.fonts.load('1em open_sansregular')
	.then(function() {
		var docEl = document.documentElement;
		docEl.className += ' open-sans-loaded';
	});

다만 현재는(2018.1) 크롬과 파이어 폭스에만 제대로 구현이 되어있다. 그래서 polyfill이나 webfont-loader같은 라이브러리를 써야한다.

webfont-loader

webfont-loader는 구글에서 만든 동적 폰트 loader라이브러리다. head태그 안에 넣으면된다.

폰트가 로딩이 되면 사진과 같이 class가 추가가 된다. Inline-image-2018-02-02 12.22.22.880.png

비동기로 로드 할 경우 로딩 시점이 다를 수 있으므로 FOUT가 발생할 수 있다.

<script>
   WebFontConfig = {
      typekit: { id: 'xxxxxx' }
   };

   (function(d) {
      var wf = d.createElement('script'), s = d.scripts[0];
      wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js';
      wf.async = true;
      s.parentNode.insertBefore(wf, s);
   })(document);
</script>

동기적으로 로딩하면 FOUT도 피할 수 있다. 하지만 그만큼 랜더링이 블락된다.

  global.WebFont.load({
            custom: {
                families: ['dcon', 'Nanum Gothic', 'Noto Sans JP'],
                urls: ['/assets/styles/lazy/iconfont.css', '/assets/styles/lazy/cal-iconfont.css', '/assets/styles/lazy/fonts.css']
            }
        });

fontfaceobserver

font face observer는 웹폰트로더랑 동작은 비슷하지만 스크롤이벤트가 발생할때마다 폰트로드를 확인하기 때문에 더 가볍다. 소스또한 경량이다.

var font = new FontFaceObserver('My Family');

font.load().then(function () {
  document.documentElement.className += " fonts-loaded";
});

.fonts-loaded {
  body {
    font-family: My Family, sans-serif;
  }
}

FOFT

Flash of Faux Text의 약자.

FOUT를 조금 더 개선한 최적화 기법이다. 폰트의 subset 폰트를 먼저 뽑아서 적용시킨다. 일만적으로 normal폰트를 뽑는다. 더 빠르게하려면 자주 쓰는 unicode 범위의 문자만 뽑아 적용시킬수도 있다. 그 후 나머지 볼드, 이태릭의 폰트들을 불러오는 방식이다.

subset폰트는 파일이 가볍고 빠르기 때문에 일반적인 FOUT보다 더 빠르게 화면을 볼 수 있다. 그 후 나머지 무겁지만 중요하지 않은 폰트를 다운받아서 적용하는 것이다.

어떻게 쓰는지 https://www.zachleat.com/web-fonts의 데모를 살펴보면

var fontASubset = new FontFaceObserver('LatoSubset');

Promise.all([fontASubset.load()]).then(function () {
    document.documentElement.className += " fonts-loaded-1";

    var fontA = new FontFaceObserver('Lato');
    var fontB = new FontFaceObserver('LatoBold', {
            weight: 700
        });
    var fontC = new FontFaceObserver('LatoItalic', {
            style: 'italic'
        });
    var fontD = new FontFaceObserver('LatoBoldItalic', {
            weight: 700,
            style: 'italic'
        });

    Promise.all([fontA.load(), fontB.load(), fontC.load(), fontD.load()]).then(function () {
        document.documentElement.className += " fonts-loaded-2";

        // Optimization for Repeat Views
        sessionStorage.fontsLoadedCriticalFoftPolyfill = true;
    });
});

FontFaceObserver옵져버를 사용하여 먼저 subset폰트를 로드하고 로드가 된 이후에 나머지를 로드한다. class를 2개로 분리하여 bold를 쓰는 것들은 두번째 로드가 된 이후에 적용시키면 된다.

.fonts-loaded-2 em {
    font-family: LatoItalic, sans-serif;
    font-style: italic;
}

font-face에 font-synthesis라는 속성을 사용하여 처리 할 수도 있다. 다만 브라우저 호환이 아직 좋지 않아서 지금은 위와 같이 쓰는게 좋다.

.syn {
  font-synthesis: style weight;
}
.no-syn {
  font-synthesis: none;
}

base64 font

sessionstorage 트릭

sessionstorage에 폰트를 받았는지 여부를 저장해놓고 네트워크 비용을 줄 일 수 있다. 단 메모리에 캐싱이 가능한 상태여야 한다. 위의 fout, foft와 base64 비동기 로딩 방식과 같이 쓸 수 있다. 시작 할 때 메모리에 있는지 검사함으로서 네트워크 요청을 줄일 수 있다.

if(sessionStorage.fonts) {
    document.body.className+=' fontsloaded
}
....중략

//google webfontlaoder
 active: function() {
    sessionStorage.fonts = true;
  }

or

.load(() => {
    sessionStorage.fonts = true; 
    document.body.className+=' fontsloaded;
});

localstorage에 저장하기

또다른 신기한 방법

css를 media=”none” 을 두고 로딩하면 랜더링을 블락하지 않는다고 한다. 이게 잘 된다면 그동안 block을 피하기위해 했던 방법들중에 가장 편하고 좋은게 아닐까 싶다. 참고

<link rel="stylesheet" type="text/css" href="fonts.css" media="none" onload="this.media='all';">
<link rel="stylesheet" type="text/css" href="style.css" media="none" onload="this.media='all';">

참고

https://dev.opera.com/articles/better-font-face/ https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization?hl=en#defining-font-family-with-font-face https://www.zachleat.com/web/comprehensive-webfonts/ https://davidwalsh.name/font-loading

관련 문서 더보기 web
blog comments powered by Disqus