폰트 디스플레이 전략

Font display strategy

커스텀 폰트가 아직 다운로드되지 않았을 때 텍스트를 어떻게 보여줄지 정하는 CSS 속성. 안 보이게 숨길지(FOIT), 폴백 폰트로 먼저 보여줄지(FOUT)를 고릅니다.

다른 이름: FOITFOUTfont-display
···
html
<div class="stage">
  <div class="sample" id="sample">Loading Text</div>
  <div class="phase" id="phase">block · 0ms</div>
  <div class="note">시뮬레이션 — 실제 네트워크 요청 없음</div>
</div>
css
.stage{display:grid;justify-items:center;gap:10px}
.sample{font:800 8vmin/1.2 -apple-system,system-ui,sans-serif;color:var(--fg);transition:opacity .15s, font-style .15s}
.sample.hidden{opacity:0}
.sample.custom{font-style:italic;color:var(--accent)}
.phase{font:11px ui-monospace,Menlo,monospace;color:var(--accent-2)}
.note{font:10px ui-monospace,Menlo,monospace;color:var(--muted)}
js
const sample = document.getElementById('sample');
const phase = document.getElementById('phase');
const strategies = [
  { name: 'block', steps: [[0,'hidden'],[500,'hidden'],[900,'fallback-then-custom']] },
  { name: 'swap', steps: [[0,'fallback'],[700,'custom']] },
  { name: 'fallback', steps: [[0,'hidden'],[150,'fallback'],[900,'stays-fallback']] },
  { name: 'optional', steps: [[0,'hidden'],[100,'fallback-forever']] },
];
let s = 0;
function runStrategy(){
  const strat = strategies[s % strategies.length];
  strat.steps.forEach(function(step){
    setTimeout(function(){
      const [ms, state] = step;
      sample.classList.remove('hidden', 'custom');
      if (state === 'hidden') sample.classList.add('hidden');
      if (state === 'custom' || state === 'fallback-then-custom') sample.classList.add('custom');
      phase.textContent = 'font-display: ' + strat.name + ' · ' + ms + 'ms · ' + state;
    }, step[0]);
  });
  s++;
  setTimeout(runStrategy, 2600);
}
runStrategy();

웹폰트는 파일이라 다운로드에 시간이 걸립니다. 그 사이 브라우저가 뭘 보여줄지는 크게 두 갈래입니다 — FOIT(Flash of Invisible Text)는 폰트가 로드될 때까지 텍스트를 아예 안 보이게 숨기고, FOUT(Flash of Unstyled Text)는 폴백 폰트로 즉시 보여주다가 로드가 끝나면 커스텀 폰트로 바꿔치기(swap)합니다.

CSS `font-display`가 이 전략을 고릅니다. `block`은 짧게(보통 3초) 숨겼다가 그래도 안 오면 폴백으로 보여주고 나중에 커스텀 폰트로 바뀝니다(FOIT에 가까움). `swap`은 처음부터 폴백을 보여주고 로드되는 즉시 바꿉니다(FOUT). `fallback`은 아주 짧게만 숨기고, 폴백으로 넘어간 뒤에는 스왑 가능 시간이 지나면 영영 폴백에 머뭅니다. `optional`은 네트워크가 느리면 아예 커스텀 폰트를 포기하고 폴백만 씁니다 — 콘텐츠가 이미 자리 잡은 뒤에 폰트가 바뀌어 레이아웃이 흔들리는(레이아웃 시프트) 걸 막고 싶을 때 씁니다.

이 사이트는 외부 폰트를 로드하지 않으므로, 데모는 실제 폰트 로딩이 아니라 각 전략의 타임라인을 시뮬레이션합니다 — 실제 네트워크 요청은 없다는 점을 화면에 명시합니다. 로딩이 느린 환경(3G, 저사양 기기)일수록 `swap`이 체감 속도를 높이지만 스타일이 갑자기 바뀌는 "튐"이 보일 수 있고, `optional`은 튐은 없지만 커스텀 폰트를 아예 못 볼 수도 있다는 트레이드오프가 있습니다.

언제 쓰나

본문처럼 즉시 읽혀야 하는 텍스트엔 `swap`을, 레이아웃 시프트를 절대 피해야 하는 곳(광고 슬롯 옆 텍스트 등)엔 `optional`을 고려하세요. `auto`(브라우저 기본값)에 맡기고 넘어가는 경우도 많습니다.