2021. 8. 29. 01:58ㆍ웹 프론트엔드 깊게 이해하기/성능 최적화
이 글은 아래 링크의 내용을 바탕으로 작성되었습니다.
https://www.youtube.com/watch?v=4YCBBoSg2fk
웹 어플리케이션의 폰트는 어느 이용자의 컴퓨터에나 설치되어 있을만한 기본 폰트를 사용해도 좋지만 디자인 등의 다른 요소를 고려해서 외부의 폰트를 다운로드 받아 적용하는 것이 일반적입니다. 하지만 단순히 인터넷에서 가져다 쓰는 웹 폰트는 최적화가 안되어 있는 경우가 많아 실제로 적용이 되기 까지 오래걸리는 경우가 많습니다. 특히 알파벳을 일렬로 나열할 뿐인 영어와는 다르게 한국어와 같이 글자들을 조합해서 사용하는 폰트들은 엄청난 용량을 자랑하는 만큼, 반드시 최적화를 해줘야 합니다.
게다가 설정에 따라, 웹폰트가 적용되기로 지정된 문자들은 웹폰트가 다운로드 되기까지 아예 글자가 안보일 수도 있습니다. 이런 식의 문제 때문에 사용자 경험이 악화되는 것을 막으려면 웹폰트 최적화는 필수적인 과정입니다.
웹폰트 다운로드 시점을 앞당기기
예시 어플리케이션에서 초기 로딩 과정을 살펴보면 웹폰트를 바로 다운로드 받는 것이 아니라 CSS 파일을 다운로드한 후 해당 CSS 파일에 명시된 웹폰트를 다운로드 받는 것을 확인할 수 있습니다.
해당 CSS 는 html 의 head 태그에 박힌 웹 폰트를 적용하기 위한 구글 폰트 CDN 링크로부터 가져온 것입니다. 이와 같은 link 태그 형식을 다른 어플리케이션에서도 많이 보셨을 것이라 생각합니다.
저 코드는 구글 폰트에서 쉽게 웹폰트를 적용하고 싶은 개발자들에게 제공하는 코드입니다. 하지만 내부를 살펴보면 정말 웹 폰트를 로드하는 css 코드 말고는 아무 내용도 없습니다. 브라우저는 똑똑하게도 실제 페이지 내에서 사용될 폰트만을 다운로드해주긴 하지만, 고작 이런 내용 밖에 들어있지 않는 CSS 파일을 다운로드 받는 것은 낭비입니다.
그러니 이 CSS 파일을 바로 다운로드하기 보다는 CSS 파일 내부의 내용을 style 태그로 집어넣는게 추가적인 네트워크 요청 수를 줄일 수 있고 폰트 파일의 다운로드 시점을 앞당길 수 있는 방법입니다.
필요한 font-face 만을 선택하기
내부를 보시면 정말 많은 font-face 코드로 가득찬 것을 보실 수 있습니다. vietamese, latin, latin-ext 는 또 뭘까요? 이 중에서 저희가 실제로 필요한 font-face 는 무엇일까요?
'한글'은 사용하는 나라가 저희 대한민국 밖에는 없지만 '영어'의 경우엔 정말 많은 나라들이 사용합니다. 그리고 각 나라의 영어 알파벳에는 다른 나라들에서 사용하지 않는 알파벳들이 들어 있는 경우도 많죠. 베트남어의 알파벳이 가장 좋은 예시일 것입니다. 그렇기에 하나의 영어 폰트는 여러 나라에서 쓰일 것에 대비하여 여러 '버전'이 존재합니다. 각각에 대한 설명은 다음과 같습니다.
- latin : 우리가 '영어'하면 생각하는 보편적인 알파벳들이 들어있습니다. 미국과 유럽권의 주요 서구권 국가들이 사용하는 알파벳들이 들어있는 버전입니다
- latin-ext : 주요 영어권 국가들의 알파벳들뿐만 아닌 유럽 대부분의 국가에서 사용하는 알파벳들이 담긴 버전입니다.
- vietnames : 베트남어에 사용되는 알파벳이 담긴 버전입니다.
이와 비슷한 맥락으로 spanish 등등 많은 버전들이 존재합니다. 그럼 이제 어떤 버전의 웹폰트를 써야 하는지 알 수 있겠죠. 저희는 latin 버전의 폰트가 필요합니다.
하지만 여기서 끝이 아닙니다. 같은 latin 버전이지만 단순히 font-style 이 각각 normal, italic 으로 서로 다르다면 완전히 다른 폰트 파일을 가져오는 것을 확인할 수 있으실 겁니다. 이는 각 스타일마다 다운로드해야 하는 서체가 완전히 다르기 때문입니다.
저희 앱은 검색어를 입력할 때는 font-style 이 normal 인 폰트를, 그 외에 다른 텍스트는 모두 font-style 이 italic 인 폰트를 씁니다. 그렇기 때문에 저희는 latin 버전의 italic, normal 스타일 폰트가 모두 필요합니다.
여기서 끝이 아닙니다. 이상하게도 위의 파일에서는 latin 버전에 font-style 이 italic 으로 동일한데다가 가져오는 font 파일의 이름도 동일한데 font-weight 가 틀리다는 이유로 다른 font-face 로 구분되어 있는 것을 확인할 수 있습니다.
이건 font-face 를 설정할 때 같은 font-family 로 지정되어 있더라도 font-weight 에 따라 사실은 완전히 다른 폰트를 적용할 수 있게끔 되어 있기 때문입니다. 따라서 font-face 안의 font-weight 에는 어플리케이션에서 해당 폰트에 대해서 사용되는 모든 font-weight 들이 명시되어야 합니다.
저희 예시 어플리케이션의 경우, normal 스타일의 폰트는 검색어 입력 한곳에서만 쓰이기 때문에 font-weight 도 400으로 한가지 밖에 없지만, italic 스타일 폰트는 font-weight 가 400 과 700 두가지로 사용되고 있습니다. 따라서 italic 폰트에 한해서는 font-weight 에 400과 700을 모두 명시해주어야 합니다. 그렇지 않고 400만을 명시해버린다면 브라우저는 웹폰트 자체에서 설정한 font-weight 700 을 사용하지 않고 임의로 font-weight 를 적당히 늘려서 적용해버립니다.
기나긴 여정 끝에, 드디어 어떤 font-face 를 적용해야 할지에 대한 결론이 나왔습니다. 저희의 예시 어플리케이션에는 다음의 두가지 font-face 가 필요합니다.
@font-face {
font-family: 'Josefin Sans';
font-style: italic;
font-weight: 400 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/josefinsans/v17/Qw3EZQNVED7rKGKxtqIqX5EUCEx6XHg.woff2)
format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Josefin Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/josefinsans/v17/Qw3aZQNVED7rKGKxtqIqX5EUDXx4.woff2)
format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
폰트 서브셋 만들기
이 작업은 사실 영어 폰트가 아니라 한글 폰트에 훨씬 유용한 작업입니다. 한글은 여러 자음 모음을 혼합하여 글자를 만드는 형식이기 때문에 폰트 하나가 무려 11,172 가지 글자들을 가지고 있을 수 있습니다. 하지만 '괆' 과 같이 실생활에 전혀 쓰일 것 같지 않은 글자는 솔직히 이용자에게 보여줄 일이 없기 때문에 폰트 파일 안에 포함되어 있을 필요가 없습니다. 이런 식으로 필요없는 글자들을 폰트 파일 안에서 제거해서 만드는 작업을 서브셋팅이라고 부릅니다.
다소 김 빠지는 이야기긴 하지만 저희는 이미 서브셋팅을 완료했습니다. 폰트들 중 latin 버전 폰트들만을 선택적으로 다운로드하고 있기 때문입니다. 그래도 서브셋팅을 수행해주는 서비스를 소개해주고 넘어가도록 하겠습니다.
https://everythingfonts.com/subsetter
https://opentype.jp/subsetfontmk.htm
폰트 서브셋을 만들 때의 주의사항
'괆'과 같은 글자는 대체로 사용되지 않는 글자인 것은 맞지만 이용자가 한글 글자를 입력하는 상황에서는 예외가 있을 수 있습니다. 예를 들어 이용자가 '괄목상대'라는 사자성어를 input 태그에 입력하는 와중에 글자가 '괄 → 괆 → 괄모 → 괄목'과 같이 변화하게 되면 브라우저는 어쨌건 '괆'이라는 문자를 화면에 띄워야합니다. 이 경우 웹 폰트 설정에 따라서 '괄 → 안보임 → 괄모 → 괄목' 과 같이 처리되거나 '괆'이라는 글자에는 웹폰트가 적용되지 않아서 사용자 경험을 저하시킬 수 있습니다. 이와 같은 경우엔 초기 로딩 이후에라도 모든 서브셋을 다운로드 하는 방법 등의 해결책을 생각해보아야 할 것입니다.
최대한 많은 웹폰트 파일 형식을 지원하기
위의 font-face 에는 아직 아쉬운 점이 하나 있습니다. 바로 다운로드 받는 폰트 파일의 형식이 woff2 하나라는 것입니다. woff2 는 매우 압축률이 뛰어난 폰트 파일이지만 구형 브라우저에서는 이 형식을 지원해주지 않습니다. 따라서 가능하면 많은 형식의 폰트 파일 형식을 font-face 에 명시하는 것이 좋습니다. 하지만 문제는 그 많은 파일 형식을 어디서 다운로드 받는가 하는 점이겠죠(디자이너를 닦달한다).
다행히도 하나의 폰트 파일 형식으로 나머지 파일 형식을 생성할 수 있는 많은 폰트 컨버터 서비스가 웹상에 존재합니다. 이 중 하나를 추천드리자면 저는 아래의 cloudconvert 를 추천드립니다. 사용법은 간단합니다. 변환을 원하는 파일의 폰트 파일 형식을 선택한 뒤, 그것을 어떤 형식으로 바꾸고 싶은지에 대한 항목들을 만들고 다운로드 해주시면 됩니다.
폰트 형식을 모두 다운로드 받았으니 이를 적용해보겠습니다. 이참에 woff2 형식의 폰트도 구글 서버가 아닌 호스팅 서버에서 다운로드 받을 수 있도록 경로를 재설정 하도록 합시다.
여기서 주의하실 점은, 단순히 url 만을 설정하는게 아니라 format 또한 url 옆에 명시해줘야 한다는 점입니다. 브라우저는 자신이 적용할 수 있는 폰트 파일 형식인지를 이 format 형식을 보고 판단할 수 있기 때문입니다. format 형식이 명시되어 있지 않다면 브라우저는 해당 폰트 파일을 지원할 수 없음에도 일단 해당 url 을 다운로드 해보고 적용할 수 없다고 판단하면 다음 fallback 웹폰트를 다운로드하는 식으로 동작해버립니다.
font-display 설정하기
아직 제가 언급하지 않은 font-face 의 두 가지 중요한 속성이 있습니다. font-display 와 unicode-range 가 그것인데요. 둘에 대해서도 간단히 언급하고 넘어가겠습니다.
font-display 는 브라우저가 웹 폰트를 '어느 시점'에 적용할지를 결정하는 속성입니다. 이를 명시해주지 않으면 브라우저는 자신의 기본 설정에 따라 웹 폰트의 적용시점을 결정해버립니다. 크롬, 사파리, 파이어폭스 브라우저는 웹 폰트가 다운로드 완료될 때까지 글자를 보여주지 않다가 웹 폰트가 모두 다운로드 된 후에 글자를 포여주는 FOIT 방식을 선택하고 있으며 엣지와 IE 브라우저는 웹 폰트를 다운 받는 동안 기존에 설정된 fallback 폰트를 이용자에게 대신 보여주고 다운로드가 완료되면 웹 폰트를 적용하는 FOUT 방식을 기본으로 선택하고 있습니다.
여기서 font-display 값을 block 으로 맞추면 FOIT 방식을, swap 으로 맞추면 FOUT 방식을 폰트 적용 방식으로 설정하게 됩니다. 더 자세한 내용은 아래의 링크를 참고하시기 바랍니다.
https://d2.naver.com/helloworld/4969726
결론적으로 말해서 가장 많이 선택하는 font-display 값인 swap 을 선택하는게 가장 일반적입니다. 이용자가 3초라는 시간 동안 보지 못하는 글자가 있다면 사용성이 크게 저하될 수 있기 때문입니다.
그러나 FOUT 방식에 문제가 없는 것은 아닙니다. 찰나의 시간이라 할지라도 글자의 스타일이 순간 변하는 것은 대부분의 이용자들이 충분히 인지할 수 있습니다. 그렇기에 텍스트 정보 뿐만 아니라 텍스트 자체의 디자인이 중요한 서비스에서는 오히려 FOUT 보다 FOIT 를 선호할 수도 있습니다.
그렇다면 여기서 FOIT 방식에서 글자를 보여주지 않는 시간이 3초가 아니라 0.1 초라면 어떨까요? 0.1초라면 사실상 이용자가 인지하기 어려울 정도의 찰나의 시간이며 (정말 무거운 웹폰트가 아니라면) 웹폰트가 다운로드 완료되는 데에도 충분한 시간입니다. 이 같이 FOUT 와 FOIT 방식의 중간에 서 있는 이 방식을 적용하기 위해서는 font-display 값을 fallback 으로 설정해주면 됩니다. 저는 이 방식을 택하도록 하겠습니다.
unicode-range 설정하기
unicode-range 는 최적화를 위한 또 하나의 기능이기는 하지만, 사실 대부분의 서비스에서는 굳이 적용할 필요가 없어보이는 기능이기도 합니다. 필요한 서비스는 '다국어'를 지원할 필요가 있는 서비스에 한할 것이라고 저는 생각합니다. unicode-range 는 간단히 말해서 내가 다운받고자 하는 웹폰트에서 가지고 있어야 할 글자들을 명시해놓는 것입니다. 만일 unicode-range 로 명시된 유니코드들에 해당하는 글자가 '현재 페이지에 한 글자도' 없다면 브라우저는 해당 웹폰트를 다운로드하지 않습니다.
unicode-range 의 대표적인 가장 대표적으로 언급되는 예가 바로 외국어 사전 웹사이트입니다. 위의 예시에서 이용자가 검색을 수행하기 전에 아랍어 폰트를 다운로드 받는다면 그것은 낭비일 것입니다. 당장 적용할 글자도 없고 이용자가 정말로 검색을 수행할지도 모르는데 일단 웹폰트를 다운 받을 필요가 없기 때문입니다. 이용자가 검색을 하면 실제 아랍어가 화면에 출력될 것이고, 브라우저는 이를 감지하여 지정된 unicode-range 에 해당하는 글자가 화면상에 있는 것을 확인하고 해당 웹폰트를 다운로드할 것입니다.
지금 저희는 기본으로 unicode-range 가 설정되어 있는데, 저희 서비스는 다국어를 지원하지 않으므로 삭제해도 그만, 삭제하지 않아도 그만이라고 생각합니다. 추후에 다국어 서비스를 지원한다는 전제를 그냥 깔고 지우지 않고 넘어가도록 하겠습니다.
Fallback 폰트 설정하기
저희는 당연히 대비해야 하지만 아직까지 대비하지 않은 상황이 있습니다. 그것은 바로 '웹 폰트가 다운로드 되지 못했을 때' 입니다. 서버 문제이든 어떠한 다른 문제이든 간에 만일 웹폰트 자체를 다운로드 받지 못하게 되면 어떻게 될까요? 당연히 fallback 폰트가 웹 폰트 대신 적용될 것입니다. 이 경우 다음과 같은 문제가 발생할 수 있습니다.
fallback 폰트와 실제 적용될 폰트간의 차이가 심하면 표시된 부분처럼 레이아웃이 깨진 화면이 이용자에게 보여질 가능성이 있습니다. 무엇이 fallback 폰트로 설정될지 예상하지 못한다면 정확히 어떤 식으로 레이아웃이 깨질지도 모를 수밖에 없겠죠. 따라서 저희는 이용자의 기기에 내장되어 있되, 최대한 저희가 적용하고자 하는 웹폰트와 유사한 자간, 줄간격, 굵기를 가진 폰트를 fallback 폰트로 설정해두는 것이 좋습니다.
각 OS 별로 어떤 폰트를 기본으로 내장하고 있는지는 다음의 사이트에서 확인해 보실 수 있습니다.
https://granneman.com/webdev/coding/css/fonts-and-formatting/default-fonts
- 윈도우를 위해서 Segoe UI
- IOS/MAC 을 위해서 San Francisco
- 안드로이드를 위해서 Roboto
폰트들을 fallback 폰트로 적용할 수 있겠습니다. 해당 폰트들이 적용할 웹폰트와 얼마나 차이가 나는지를 확인해봅시다. 이를 확인할 수 있는 서비스로 아래의 링크를 추천드립니다.
https://sangziii.github.io/fontStyleMatcher/
생각보다 차이가 많이나는군요. 하지만 글자 굵기의 차이는 크게 없어보입니다. 그냥 자간과 줄간격을 조정해서 차이를 메꿔보겠습니다.
이 서비스는 line height 를 0.05 단위로 밖에는 증감시킬 수 없군요. line-height 를 1.55 가 아니라 1.57 정도로 맞추면 많이 유사해질 것이라 생각합니다.
'웹 프론트엔드 깊게 이해하기 > 성능 최적화' 카테고리의 다른 글
프론트엔드 성능 최적화 Quick Start - 7. 클라우드 프론트와 S3를 이용한 CDN & 캐싱 적용 (0) | 2021.08.29 |
---|---|
프론트엔드 성능 최적화 - 4. 이미지 파일 최적화 (1) | 2021.08.29 |
프론트엔드 성능 최적화 - 3. 코드 스플리팅 & 트리 쉐이킹 (2) | 2021.08.29 |
프론트엔드 성능 최적화 - 2. 소스코드 최적화 (2) | 2021.08.29 |
프론트엔드 성능 최적화 - 1. 문제 인식 (0) | 2021.08.29 |