빠른 첫화면을 위한 한글 폰트 최적화 여행 - part 2: 꼼수로 완성하다

filekiwi·2022년 8월 4일
15
post-thumbnail

현행기술분석

앞의 글에 이어서 씁니다.
한글 폰트 최적화를 위한 꼼수를 찾기 위해서 관련 기술을 가능한 모조리 조사해봅니다.

  • font subset
  • unicode-range
  • Incremental Transfer subset

가장 도움이 되었던 한글폰트에 관한 영어문서 보기

font subset

폰트를 적당히 잘라서 쓰는 것입니다. 어쩌면 당연한 시도라고 할수 있습니다.
44bits블로그 포스트에서 관련된 기술과 방법들을 찾을 수 있었습니다.
이글에서도 잠깐 언급했듯이 몇글자만 들어가는 작은 subset도 만들수 있습니다.

랜딩 페이지의 극단적인 최적화가 필요한 경우 페이지에서 사용하는 글자만을 담은 폰트를 만들 수도 있습니다.
이러한 접근 방법은 한 페이지짜리 랜딩 페이지에는 유용할 수 있지만, 페이지가 많아지면 폰트 생성 과정이 번거롭고 무엇보다 전체 폰트를 다운로드 받고 캐시해서 사용하는 것보다 훨씬 불리할 수 있습니다. 적절한 판단이 필요합니다.

오!! 저는 여기서 말하는 극단적인 방식이 제가 원하는 것입니다. 이것으로 발생하는 다른 문제들은 다른 꼼수들로 틀어막는 한이 있더라도, 첫번째 로딩에서 문제가 없기만 하면 됩니다.

unicode-range

이 기술의 취지와 적용과정은 여기서
보실 수 있습니다.

자주쓰이는 글자의 빈도 비율분포 출처:구글블로그

문자는 아주 많지만 자주 쓰이는 글자는 그렇게 많지 않다는 것을 보여줍니다. 이러한 통계에 기반해서 unicode-range 구획을 나누었다는 것입니다. 이것이 첫번째 로딩을 빠르게 하고 싶은 저에게 어떤 도움을 줄 수 있을까요?
크게 없습니다. 이러한 통계는 여러 사이트에서, 여러번 로딩할때 최적화 할수 있는 자료이지 첫번째 로딩의 순간에는 도움이 되지 않습니다. 그 순간에 필요한 글자를 이 표위에 초록색으로 표현해 보면 대략 아래와 같을 것입니다.

첫번째 로딩때 부터 여러 파일들을 읽어야 한다는 의미입니다.

이 통계는 웹폰트를 운영하는 구글에게 중요합니다. 트래픽을 줄일 수 있으니까요. 그리고, 웹폰트가 인기를 끌 수록, 사용자에게 미리 로딩된 폰트가 많아지고, 그래서, 한글 웹폰트가 빨리 로딩될수 도 있습니다. 냉정하게 보자면, 내가 만든 사이트가 구글 폰트 프리로더 역할을 해주는 것입니다.

통계에 기반한 unicode-range의 유익은 큰 스케일로 많은 사이트가 참여할때 발생합니다. 그러므로, 자기가 만든 또는 자기만 쓸 폰트를 구글처럼 unicode-range로 쪼개서 쓰는 것은 적절한 응용이 아니라고 볼 수 있습니다.

Incremental Transfer subset

점진적으로 폰트를 읽어들이는 기술입니다. 테스트해보기
unicode-range를 미리 만들어 놓는 것이 아니라, 브라우저에서 요청하는것에 맞게 폰트파일을 만들어서 보내주는 방식입니다.
한글과 같은 비라틴문자 계열의 언어에 꼭 기술이라고 볼수 있겠습니다.
아마 구글이 한국회사였다면, 이 기술이 가장먼저 고민되고 구현되었을것이라고 생각합니다. 아쉽게도 아직 쓸수 있는 단계는 아니지만, 결국 한국어 사이트는 이런 방식의 기술로 폰트를 로딩하게 될것입니다.

계획

제가 사용할 수 있는 선택지는 사실상 font subset 이 전부입니다. 계획을 세워봅니다.

  • 첫페이지에서 사용하는 글자만으로 이루어진 subset을 만든다.
  • 이 폰트를 font-display:optional 로 로딩한다.
  • 더 빠르게 로딩하기 위해서 link rel=preload 태그를 사용한다.
  • 100ms 안에 subset폰트를 로딩하면 성공!

구현하기

작업조건

  • 첫페이지의 문자갯수는 284개
  • 사용할 폰트는 스포카 산스 네오 서브셋 폰트크기 180kb
  • 크롬/파이어폭스
  • wifi 수준의 속도

테스트1 - 실패

  • 생성된 폰트사이즈 80kb
  • 폰트 로딩시간 170 ms

폰트 사이즈를 100kb나 줄였는데, 100ms 이하로는 떨어지질 않습니다.

수정1

메인페이지 내용을 줄여서 해봅니다. 문자열 갯수를 최대한 줄여봅니다.

테스트2 - 실패

  • 생성된 폰트사이즈 50kb
  • 폰트 로딩시간 110 ms

페이지 내용을 최대한 줄였지만 가끔 성공하고 대부분 실패합니다.
물론 더 줄이면 가능할 것 같은데.. 폰트로딩하려고 페이지 내용을 포기하는건 아닌것 같습니다.

아.. 거의 다왔는데.. 이제 할만 큼 한게 아닌가 포기하려다가
더 극단적인 선택을 해보기로 합니다.

수정2 - 페이지 분할

첫로딩시 보이는 페이지 상단 영역, 딱 그 부분만 해결해보자.

먼저 페이지를 첫화면과 아닌 것으로 구분하였습니다. 첫화면 부분은 최대한 단순하게 만들고, 높이를 vh로 주어서 첫화면의 요소에는 변화가 거의 없도록 했습니다.
이런 화면분할은 토스에서도 볼수 있습니다.(이것 자체가 웹폰트 최적화는 아닙니다.)

화면축소를 아무리 해도 스크롤 전에는 다음것을 볼수 없습니다.

제가 보여줘야 할 첫화면은 아래와 같습니다.

문자수만을 세보니 85자가 되었습니다. (약간 여유를 두었습니다.)

테스트3 - 첫화면 최적화 성공!

  • 상단영역 font subset-사이즈 10kb
  • 폰트 로딩시간 30 ms
 <link rel="preload" href="./fonts/Spoqa/SpoqaHanSansNeo-Regular.woff2" crossorigin="anonymous" as="font" type="font/woff2"/>

오!. 그디어 충분히 로딩시간이 줄었습니다. 왠만해서는 100m를 넘지 않습니다.

페이지 로딩 시작후 225ms 후에 화면에 내용이 바로 표시되기 시작합니다. 이 시점에 웹폰트가 적용되어 있습니다. 폰트가 나중에 뜬다든지 깜박거리는 것 없습니다. 전체 0.2초 가량의 시간이 소요되긴 하지만, 사람에게는 거의 느껴지지 않습니다.

수정3 - 하단 웹폰트 적용

첫화면은 성공했지만, 첫화면 하단 부분은 웹폰트가 적용되지 않았습니다.
나머지 부분에도 웹폰트를 적용하는 여러 방법이 있습니다.
실제 브라우저가 필요한 폰트데이터는 페이전 전체 font subset 에서 상단영역 font subset을 제외한 부분입니다. 이러한 subset을 또 만들어서 불어오면 빠르겠지만, 관리가 너무 복잡해집니다.
이 시점에서 가장 중요한점은 일단 첫페이지 로딩을 급하게 완성해 놓으면, 여유가 생긴다는 점입니다. 처음 방문하는 페이지가 화면에 떳을때, 최소한 보는 시간이 있는거죠.
그래서 하단 영역을 위한 서브셋을 또 만들지 않고, cdn에 공개되어 있는 웹폰트를 사용하기로 했습니다.
이렇게 하면 불필요한 데이터를 읽어오는 단점은 있지만, 운영하는 서버트래픽비용을 아낄수 있고, 이후 페이지 표시를 위해서 결국 전체폰트를 한번에 읽는 것이 더 적합하기 때문입니다. 그래서, 44bits 블로그에서 걱정했던 부분을 해결할 수 있습니다. 또한, cdn 웹폰트 로딩시간이 대부분의 경우 2초이내로 완료 되기때문에, 하단으로 스크롤 하기전에 cdn웹폰트는 로딩이 완료되어 있을 것입니다.

적절한 시점에 서브셋 폰트에서 cdn폰트로 변경하는 작업이 필요합니다. 이를 구현하는데 여러 방법 가능할 것입니다.
제가 처음 고민했던 방식은 font-family:'cdn폰트', '서브셋폰트'; 이런 식으로 우선순위를 두는 것이였습니다. 가장 그럴듯해 보였지만, 사파리 브라우저에서 깜박이는 문제가 발생하여 포기하였습니다.
다음으로 FontFaceObserver을 사용해서 cdn폰트가 로딩되는 시점에서 폰트변경 작업을 하는 것입니다. 동작을 하기는 하는데 라이브러리를 추가하는게 부담스러워서 (사이트가 그만큼 느려집니다.)
더 가벼운 방법을 찾다가,
scroll event를 이용하기로 했습니다. 사용자가 페이지를 조금이라도 스크롤하면 cdn폰트로 교체하는 것입니다. cdn폰트가 필요한 하단영역은 스크롤없이는 보여질 수 없으니까요.

이하는 구현부분에 대한 코드입니다.

//js 구현부분

let ff;
 ff=()=>{

  if(window.scrollY <10) return ;
       window.removeEventListener('scroll',ff);
       //body 에 loaded 클래스를 추가한다. 
      document.body.classList.add('loaded');
   
  }
if(window.scrollY >10) ff(); else 
window.addEventListener('scroll', ff);
 //css파일 부분
 //상단 영역용 폰트 
 //생성한 서브셋 family 이름은 뒤에 "-" 를 붙여서 구분하고 있다. 
 @font-face {
    font-family:'Spoqa Han Sans Neo-';
    font-weight: 400;
    font-display: optional;
    src: url('./fonts/Spoqa/SpoqaHanSansNeo-Regular.ttf') format('woff2');
  
  }
  
  //일반 웹폰트 cdn
  @font-face{
      font-family:'Spoqa Han Sans Neo';
      font-weight:400;
      font-display: swap;
      src: url('https://cdn.jsdelivr.net/gh/spoqa/spoqa-han-sans@latest/Subset/SpoqaHanSansNeo/SpoqaHanSansNeo-Regular.woff2') format('woff2')
    }

 
 
// loaded 클래스가 없는 경우, 서브셋폰트가 적용됨 
body:not(.loaded) {
  font-family:Spoqa Han Sans Neo-;
 }

// loaded 클래스가 있는 경우, CDN폰트가 적용됨 
body.loaded {
  font-family:Spoqa Han Sans Neo;
 
}
 

@font-face의 font-display 의 값이 중요합니다.

서브셋폰트는 optional로 지정했습니다. 영어사이트에서 동작하듯이 잘 돌아가고 있습니다. 사용자가 스크롤 하면, 눈에는 보이지 않지만 글자들의 폰트는 Spoqa Han Sans Neo- 에서 Spoqa Han Sans Neo 로 바뀝니다.

참고로 HTML 코드 일부입니다.

<head>
  <link rel="preload" href="./fonts/Spoqa/SpoqaHanSansNeo-Bold.woff2" crossorigin="anonymous" as="font" type="font/woff2"/>
    <link rel="preload" href="./fonts/Spoqa/SpoqaHanSansNeo-Regular.woff2" crossorigin="anonymous" as="font" type="font/woff2"/>

</head>

테스트4 - 성공!


위의 두개 폰트가 서브셋폰트이고, 나머지 두개가 cdn폰트입니다.

로딩직후, 페이지 하단에서는 폰트가 깨지고, 레이아웃이 뒤틀리는등 난리도 아니지만,
사용자에게 보이는 상단은 웹폰트에 아무런 문제가 없습니다.
사용자의 시선이 위에 머무는 동안, 아래쪽도 정리가 끝난 상태가 되고,
스크롤하여 내려보면 역시 웹폰트에 문제가 없습니다.

수정5 - 자동화

서브셋 폰트는 페이지 내용이 바뀔때 마다 변경되야 하는데요. 이걸 매번 수작업 하기는 번거롭죠. 저는 webpack에 다 집어 넣어서 build시 폰트가 생성되도록 만들었습니다.
webpack 설정이 워낙 지저분해서 일반적으로 설명드리기가 어렵겠네요. 간단히 과정만 요햑하자면,아래와 같은 일을 하는 woff2 파일의 로더를 만들었습니다 .

  • index.html 파일을 읽어서 html text 부분의 상단 N개 단어 추출
  • 문자열의 중복제거
  • fontmin 으로 폰트 생성

결과

서비스에 완성된 꼼수가 적용되어 있기 때문에,
file.kiwi에서 직접 해보실수 있습니다.

항상 그러는 것은 아니지만 꽤 자주 퍼포먼스 100%(모바일,데스크탑 모두)을 찍습니다. (이것이 웹폰트 최적화 완성을 뜻하는 것은 아니구요. 최적화작업에 신경썻다는 의미입니다.)

한계와 개선점

여러 삽질과 노가다와 테스트를 거쳐 만족스러운 한글 폰트 최적화를 할수 있게 되었습니다. 물론 제가 사용한 꼼수는 한계가 있고 적용할 수 있는 조건이 있습니다. 다음과 같은 것을 고려해야 합니다.

  • 첫화면에 표시되어야 할 글자수가 많은 경우 적용어렵습니다.
  • subset 폰트를 만들어도 되는지 저작권 확인이 필요합니다.
  • IE 같은 브라우져에서느 동작하지 않습니다.

일반적인 소개페이지나 랜딩페이지에서는 무리없이 적용이 가능합니다.

결론

한글 폰트 최적화를 위해서 페이지를 첫화면과 나머지화면으로 구분하고, 첫화면만을 위한 최소한의 폰트subset을 사용하였습니다. 꼼수에 꼼수를 더해서 영어사이트 수준과 동일한 웹폰트 최적화에 성공하였습니다.
한글 웹폰트 기술은 매우 초보적인 수준이며 발전의 여지가 많고 결국 이 꼼수는 필요없게 될 것입니다. 기술이 완성이 될 때까지는 이렇게라도 버티고 있어야 겠죠. 한글 웹폰트기술은 실제 쓰는 사람들이 노력하지 않으면, 다른 나라 사람들은 아무도 대신 고민해주지 않는다는 것을 느끼게 됩니다. 기술적용이 1-2년 늦어질 수 밖에 없습니다. 개인적으로는 네이버가 크롬브라우저 고쳐쓸때, 한글부분을 연구해서 개선해주면 좋겠다는 생각을 합니다.
이상 긴 글을 읽어주셔서 감사합니다.
다른 최적화 꼼수나 추가할 아이디어가 있다면 알려주세요.

profile
파일키위개발블로그

6개의 댓글

comment-user-thumbnail
2022년 8월 7일

좋은 글 잘 읽었습니다. 한글 폰트 로딩 중에 줄바꿈이 생기는 문제가 있어서 어떻게 해야 하나 고민중인데 여러 생각을 하게 하는 글이네요

1개의 답글
comment-user-thumbnail
2022년 8월 10일

정면승부가 아닌 꼼수라 하셨지만 글에서 치열한 고민으로 한 결과로 느껴집니다. 문제해결 과정을 물 흐르듯 잘 설명해주셔서 흥미롭게 읽었습니다👍🏻

1개의 답글
comment-user-thumbnail
2022년 8월 15일

어렴풋이 이상하다고 생각만 하고 있던 지점인데, 이렇게 신기한 방법으로 문제를 해결하고 또 그걸 정갈하게 공유까지 해주시니 너무 감사하게, 그리고 재밌게 잘 읽었습니다.

질문이 있습니다: firefox 대응 부분의 "하단 div부분도 꼼수"는 정확히 어떤 기능을 하는 것인가요? 두 줄의 div가 의미하는 바가 궁금합니다.

그리고 한가지 건의사항이 있습니다: PC로 들어가서 확인해봤는데, 하단 li 태그가 이쁘게 보이지 않습니다. li 태그 하나에 딸린 텍스트가 2줄 이상을 차지하게 되면, 위아랫줄 글자가 겹쳐 보입니다. (ex. "대기시간 없어요- 업로드 완료까지 기다리지 않고 바로 전달해요.") 확인해보니 line-height: 1rem 이고 font-size: 3vmin 이라 뭔가 빈틈이 있는 듯 합니다. 계산식이 살짝 수정되어야 할 것 같습니다. 모바일에선 문제 없습니다. 저는 Dell 3219Q 모니터를 쓰고 있습니다.

1개의 답글