Technote

성능 최적화 심화

연재 한글 웹 타이포그래피 8부 중 7부

폴백 폰트 메트릭 보정으로 흔들림 없애기

웹폰트가 도착할 때 화면이 흔들리는 이유는 폰트가 늦어서가 아니라 폴백 폰트와 글자 폭·높이가 다르기 때문입니다. 그 둘을 맞춰 두면 교체가 일어나도 레이아웃이 그대로입니다.

웹폰트를 font-display: swap으로 붙이면 반드시 한 번은 화면이 바뀝니다. 대개는 그것으로 끝나지 않고 레이아웃이 통째로 움직입니다 — 제목이 세 줄에서 두 줄로 접히고, 그 아래 모든 요소가 위로 당겨집니다.

이 현상을 “폰트가 느려서” 라고 진단하면 해결이 안 됩니다. 폰트를 아무리 빨리 받아도 교체 순간의 차이는 그대로이기 때문입니다. 원인은 속도가 아니라 두 폰트의 치수가 다르다는 사실입니다.

무엇이 다른가

두 가지가 다릅니다. 글자 폭세로 메트릭입니다.

글자 폭이 다르면 한 줄에 들어가는 글자 수가 달라집니다. 폴백 폰트가 조금 더 넓으면 같은 문단이 한 줄 더 길어지고, 웹폰트가 도착하는 순간 그 줄이 사라지면서 아래가 전부 올라옵니다.

세로 메트릭(ascent · descent · line gap)이 다르면 같은 line-height에서도 글자가 놓이는 위치가 달라집니다. 줄 수가 같아도 글자가 미세하게 위아래로 움직입니다.

같은 font-display: swap 이라도 폴백을 맞춰 두면 결과가 다르다

폴백 가족을 새로 만듭니다

해법은 시스템 폰트를 그대로 쓰지 않고, 그 폰트를 감싼 새 @font-face 가족을 만들어 치수를 덮어쓰는 것입니다. CSS에 네 개의 보정 속성이 있습니다.

@font-face {
  font-family: 'Pretendard Fallback';   /* 우리가 만든 이름 */
  src: local('Malgun Gothic'), local('맑은 고딕');
  size-adjust:        88.19%;   /* 글자 폭·크기를 웹폰트에 맞춘다 */
  ascent-override:    95%;      /* 기준선 위 높이 */
  descent-override:   25%;      /* 기준선 아래 높이 */
  line-gap-override:  0%;
}

body {
  font-family: 'Pretendard Variable', 'Pretendard Fallback', sans-serif;
}

size-adjust가 가장 큰 일을 합니다. 글리프 전체를 비율로 확대·축소하므로 글자 폭과 체감 크기가 함께 맞춰집니다. 폭이 맞으면 줄바꿈 위치가 같아지고, 줄 수가 같아지면 그 아래 요소가 움직이지 않습니다.

나머지 셋은 줄 상자 안에서 글자가 놓이는 높이를 맞춥니다. 폰트마다 자기 ascent · descent 값을 갖고 있어서, 이것을 통일해 두면 line-height가 같을 때 기준선 위치도 같아집니다.

숫자는 재서 얻습니다

위 숫자를 그대로 복사하면 안 됩니다. 보정값은 웹폰트와 폴백 폰트의 조합마다 다릅니다. 같은 폴백이라도 웹폰트가 바뀌면 값이 달라지고, 운영체제마다 폴백 폰트 자체가 다릅니다.

구하는 방법은 어렵지 않습니다. 브라우저에서 같은 문장을 두 폰트로 그려 폭을 재고, 그 비율을 size-adjust로 씁니다. 캔버스의 텍스트 측정 기능이면 충분합니다.

const measure = (family) => {
  const c = document.createElement('canvas').getContext('2d');
  c.font = `100px ${family}`;
  return c.measureText('한글 본문 폭을 재는 문장입니다 ABC 123').width;
};

// 폴백을 웹폰트 폭에 맞추는 비율
const adjust = measure('Pretendard Variable') / measure('Malgun Gothic');
console.log((adjust * 100).toFixed(2) + '%');

측정 문장에는 실제 본문에 나오는 문자를 넣습니다. 한글만 넣으면 라틴 알파벳과 숫자가 섞인 실제 문단에서 어긋날 수 있습니다.

남는 오차와 검증

이 기법이 오차를 0으로 만들지는 않습니다. size-adjust는 전체를 같은 비율로 조정하므로, 글자 하나하나의 상대적 폭 차이까지 맞추지는 못합니다. word-break: keep-all을 쓰는 한글 문단에서는 어절 하나가 넘어가고 말고의 경계에서 여전히 줄 수가 달라질 수 있습니다.

그래도 보정 전과는 차원이 다릅니다. 검증은 눈이 아니라 도구로 합니다 — 브라우저 개발자 도구의 성능 기록에서 레이아웃 이동 항목이 남아 있는지 보고, 남아 있다면 어느 요소인지 확인합니다.

네 단계 — ②의 숫자만 폰트 쌍마다 다시 잰다

레이아웃 이동을 포함한 성능 지표 전반은 성능 최적화 아카이브에 있고, 이런 계측과 개선을 전후 비교로 남기는 방식은 최적화 지원 사업의 리포트에 그대로 들어갑니다.

다음 회차

글자가 안정적으로 도착하게 됐으니, 마지막은 그 글자를 어떻게 배치할 것인가입니다. 다음 회차는 측정폭과 수직 리듬 — 장문 글이 끝까지 읽히게 만드는 조판입니다.

이 주제의 다른 글

노하우 목록으로

성능 최적화 실무

태그 하나가 더하는 밀리초는 누가 냅니까

추적 스크립트는 내려받고 끝나지 않습니다. 파싱과 실행이 메인 스레드를 점유하는 동안 페이지는 손가락에 반응하지 않습니다. 그 비용은 직접 잴 수 있습니다.

마케터 3분 읽기

₩270,000 · 신청하기