굴림... 돋음... 궁서... 등등 브라우저에는 기본 폰트가 있는데 그것이 아닌 폰트를 쓰게 되면 웹폰트가 됐든, 따로 로컬에 폰트 파일을 넣었든 간에 렌더링 이후에 폰트가 다운로드되다 보니, 기본 폰트에서 해당 폰트로 바뀌어버리는 현상이다.
이를 FOUT (Flash Of Unstyled Text) 라고 하는데, 단순히 PC 환경이라면 큰 문제가 없을지도... 모르지만 모바일 환경에서는 조금 이야기가 달라진다.
최근에 하이브리드 모바일 앱 프로젝트에 웹뷰 개발자로 참여했었는데, 생각보다 폰트가 바뀐다거나, 요소들이 변경될 때 깜빡이는 현상들이 버그라는 QA 항목이 많았다.
뭐 이미지나 컴포넌트들은 특정 상태값으로 핸들링하는 데에 성공했는데 이 놈의 폰트는 무슨 짓을 하던 시종일관 나에게 윙크를 해댔다.
심지어 웹에서 다운받는 방식의 폰트도 아니고, 레포지토리에 폰트 파일을 저장해서 font-face 방식으로 불러다가 쓰는 방식이라 이걸 어떻게 해결해야하나 영 난감했는데, 다행히 font를 객체로 빼다 쓸 수 있는(?) 패키지가 있었다.
https://github.com/bramstein/fontfaceobserver
npm i fontfaceobserver
yarn add fontfaceobserver
이 친구는 참 유용하다. font-face 로 지정해둔 폰트를 꺼내다가 이 친구가 로딩됐는지 안됐는지 체크할 수도 있고, 기본 굵기를 지정해준다던가 하는 동작을 수행시킬 수 있게 도와준다.
const fontObject = new FontFaceObserver('Your FontFamily')
fontObject.load(()=>{
// do Something you want to operate after font loading
})
기본적으로 위와 같이 사용한다.
가령, font-face를 이렇게 선언했다고 가정해본다면.
@font-face{
src:'뭐시기'
font-family:'aaaa'
}
const aaaa = new FontFaceObserver('aaaa')
aaaa.load(()=>{
console.log("폰트 로딩!");
})
이렇게 해주면 되시겠다.
뿐만 아니라, load()는 Promise 객체에 해당한다.
그렇게, 최종적으로 내가 적용한 코드는 다음과 같다.
const font1 = new FontFaceObserver('font1')
const font2 = new FontFaceObserver('font2')
...
Promise.all([font1.load(),font2.load(),...])
.then(()=>{
app.isFontLoaded = true;
})
// App.ts
잘 모르겠으니 그냥 App.ts에 넣고.
폰트 로딩 여부를 알려주는 스토어 상태변수를 true로 돌려준다.
그러니까, app.isFontLoaded가 true일 때 컴포넌트들이 렌더링되게끔 해줬다.
물론, 약간 초기 렌더링 속도가 느려지긴 하겠지만 나도 물론이고 그걸 체감할리가 없을테다.
번외로 font-display
에 대한 이야기도 있었는데.
font-display:swap;
// 기본 폰트에서 -> 폰트 로딩시 변경
font-display:block;
// 로딩 전에는 글자 안보임 -> 로딩 후에 글자 렌더링
나 같은 경우엔, 컴포넌트가 렌더링되자마자 알맞는 폰트의 글자가 나와야했기 때문에 적절한 방법이 아니었어서 쓱 지나갔다.
이런 것들 하나하나 경험할 때마다 느끼는 건... 어서 Next.js 를 실무 레벨이 되도록 공부해야겠다는 생각.
FontFaceObserver라는 별도의 패키지가 있다면, 분명히 뭔가 폰트를 빼내올 수 있는 객체가 있을텐데..... 뭔가 있을텐데... 라는 생각으로 검색해보니
아니 진짜루 있다. 검색을 조금만 더 잘해보자 나 자신.
사용법은 아래와 같다.
const font1 = new FontFace("Your_FontFamily", "url('your/font/path')")
Promise.all([font1.load()]).then(()=>
doSomething()
);
스타일을 따로 설정해주는 것도 FontFaceObserver와 같이 동일하고, 똑같이 load() 메서드가 Promise를 반환하기 때문에 여러개의 폰트를 사용하고 있는 경우 Promise.all로 처리할 수 있다.
FontFaceObserver의 기능을 무조건 사용해야만한다!!! 라면 패키지를 설치해야겠지만, 알다시피 번들을 줄이기 위해 단순한 기능만 필요하다면 FontFace를 사용하는게 더 나을 것 같다.
다만 url입력하는게 좀 귀찮긴 했다.
https://darrengwon.tistory.com/746
https://www.npmjs.com/package/fontfaceobserver
https://devs.vercel.app/webfont-foit-fout