글꼴 대체를 위한 프레임워크 도구

Janicklas Ralph James
Janicklas Ralph James

font-display: 스왑으로 글꼴을 로드하는 사이트에서는 웹 글꼴이 로드되고 대체 글꼴로 전환될 때 레이아웃 변경 (CLS)이 발생하는 경우가 많습니다.

대체 글꼴의 크기를 기본 글꼴 크기와 일치하도록 조정하여 CLS를 방지할 수 있습니다. @font-face 규칙의 size-adjust, ascent-override, descent-override, line-gap-override와 같은 속성은 대체 글꼴의 측정항목을 재정의하여 개발자가 글꼴이 표시되는 방식을 더 효과적으로 제어할 수 있도록 합니다. 글꼴 대체 및 재정의 속성에 관한 자세한 내용은 이 게시물을 참고하세요. 이 데모에서 이 기법의 실제 구현을 확인할 수도 있습니다.

이 도움말에서는 대체 글꼴 CSS를 생성하고 CLS를 줄이기 위해 글꼴 크기 조정이 Next.js 및 Nuxt.js 프레임워크에서 구현되는 방식을 살펴봅니다. 또한 Fontaine 및 Capsize와 같은 교차 컷 도구를 사용하여 대체 글꼴을 생성하는 방법도 보여줍니다.

배경

font-display: swap은 일반적으로 FOIT (보이지 않는 텍스트의 플래시)를 방지하고 화면에 콘텐츠를 더 빠르게 표시하는 데 사용됩니다. swap 값은 글꼴을 사용하는 텍스트가 시스템 글꼴을 사용하여 즉시 표시되어야 하고 맞춤 글꼴이 준비되었을 때만 시스템 글꼴을 대체해야 한다고 브라우저에 알립니다.

swap의 가장 큰 문제는 부자연스러운 효과로, 두 글꼴의 문자 크기 차이로 인해 화면 콘텐츠가 이동합니다. 이로 인해 텍스트가 많은 웹사이트의 경우 CLS 점수가 낮아집니다.

다음 이미지는 문제의 예를 보여줍니다. 첫 번째 이미지는 대체 글꼴 크기를 조정하려고 시도하지 않고 font-display: swap를 사용합니다. 두 번째는 CSS @font-face 규칙을 사용하여 크기를 조정하면 로드 환경을 개선하는 방법을 보여줍니다.

글꼴 크기 조정 안함

body {
  font-family: Inter, serif;
}
텍스트의 글꼴과 크기가 갑자기 변경되어 부자연스러운 효과를 일으킵니다.

글꼴 크기 조정 후

body {
  font-family: Inter, fallback-inter, serif;
  }

@font-face {
  font-family: "fallback-inter";
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0.00%;
  size-adjust: 107.40%;
  src: local("Arial");
}
다른 글꼴로 부드럽게 전환되는 텍스트입니다.

대체 글꼴의 크기를 조정하는 것은 글꼴 로드 레이아웃 변경을 방지하는 효과적인 전략이 될 수 있지만 글꼴 대체에 관한 이 게시물에 설명된 것처럼 로직을 처음부터 구현하는 것은 까다로울 수 있습니다. 다행히 앱을 개발하는 동안 이 작업을 더 쉽게 할 수 있도록 이미 몇 가지 도구 옵션이 마련되어 있습니다.

Next.js로 글꼴 대체를 최적화하는 방법

Next.js는 대체 글꼴 최적화를 사용 설정하는 방법을 기본적으로 제공합니다. 이 기능은 @next/font 구성요소를 사용하여 글꼴을 로드할 때 기본적으로 사용 설정됩니다.

@next/font 구성요소는 Next.js 버전 13에서 도입되었습니다. 이 구성요소는 Google Fonts 또는 맞춤 글꼴을 페이지로 가져오는 API를 제공하며 글꼴 파일의 자동 자체 호스팅 기능을 내장하고 있습니다.

이 글꼴 측정항목을 사용하면 대체 글꼴 측정항목이 자동으로 계산되어 CSS 파일에 삽입됩니다.

예를 들어 Roboto 글꼴을 사용하는 경우 일반적으로 CSS에 다음과 같이 정의합니다.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

body {
  font-family: Roboto;
}

다음/글꼴로 이전하려면 다음 단계를 따르세요.

  1. 'Roboto' 텍스트를 가져와서 Roboto 글꼴 선언을 자바스크립트로 이동합니다. 함수를 호출합니다. 함수의 반환 값은 구성요소 템플릿에서 활용할 수 있는 클래스 이름이 됩니다. 이 기능을 사용 설정하려면 구성 객체에 display: swap를 추가해야 합니다.

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. 구성요소에서 생성된 클래스 이름을 사용합니다. javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }

를 통해 개인정보처리방침을 정의할 수 있습니다.

adjustFontFallback 구성 옵션은 다음을 수행합니다.

@next/font/google: 누적 레이아웃 이동을 줄이기 위해 자동 대체 글꼴을 사용해야 하는지 설정하는 불리언 값입니다. 기본값은 true입니다. Next.js는 글꼴 유형 (각각 Serif와 Sans Serif)에 따라 대체 글꼴을 Arial 또는 Times New Roman로 자동 설정합니다.

@next/font/local: 레이아웃 변경 횟수를 줄이기 위해 자동 대체 글꼴을 사용해야 하는지를 설정하는 문자열 또는 불리언 false 값입니다. 가능한 값은 Arial, Times New Roman 또는 false입니다. 기본값은 Arial입니다. Serif 글꼴을 사용하려면 이 값을 Times New Roman로 설정하는 것이 좋습니다.

Google 글꼴의 또 다른 옵션은

next/font 구성요소를 사용할 수 없는 경우 optimizeFonts 플래그를 통해 Google Fonts에서 이 기능을 사용할 수도 있습니다. Next.js에는 optimizeFonts 기능이 이미 기본적으로 사용 설정되어 있습니다. 이 기능은 HTML 응답에서 Google Font CSS를 인라인으로 표시합니다. 또한 다음 스니펫과 같이 next.config.js에 experimental.adjustFontFallbacksWithSizeAdjust 플래그를 설정하여 글꼴 대체 조정 기능을 사용 설정할 수 있습니다.

// In next.config.js
module.exports = {
 experimental: {
   adjustFontFallbacksWithSizeAdjust: true,
 },
}

참고: 새로 도입된 app 디렉터리로 이 기능을 지원할 계획은 없습니다. 장기적으로는 next/font를 사용하는 것이 이상적입니다.

Nuxt로 글꼴 대체를 조정하는 방법

@nuxtjs/fontaine은 Nuxt.js 프레임워크용 모듈로, 대체 글꼴 측정항목 값을 자동으로 계산하고 대체 @font-face CSS를 생성합니다.

모듈 구성에 @nuxtjs/fontaine를 추가하여 모듈을 사용 설정합니다.

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

Google Fonts를 사용하거나 글꼴에 @font-face 선언이 없으면 추가 옵션으로 선언할 수 있습니다.

대부분의 경우 모듈은 CSS에서 @font-face 규칙을 읽고 글꼴 모음, 대체 글꼴 모음, 디스플레이 유형과 같은 세부정보를 자동으로 추론할 수 있습니다.

글꼴이 모듈에서 검색할 수 없는 위치에 정의된 경우 다음 코드 스니펫과 같이 측정항목 정보를 전달할 수 있습니다.

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
  fontMetrics: {
  fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})

모듈이 자동으로 CSS를 스캔하여 @font-face 선언을 읽고 대체 @font-face 규칙을 생성합니다.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}
/* This will be generated. */
@font-face {
  font-family: 'Roboto override';
  src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
    local('Arial'), local('Noto Sans');
  ascent-override: 92.7734375%;
  descent-override: 24.4140625%;
  line-gap-override: 0%;
}

이제 다음 예와 같이 Roboto override를 CSS에서 대체 글꼴로 사용할 수 있습니다.

:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto override';
}

CSS 직접 생성

독립형 라이브러리를 사용하면 대체 글꼴 크기 조정을 위한 CSS를 생성할 수도 있습니다.

Fontaine 라이브러리 사용

Nuxt나 Next.js를 사용하지 않는다면 Fontaine을 사용할 수 있습니다. Fontaine은 @nuxtjs/fontaine을 지원하는 기본 라이브러리입니다. 프로젝트에서 이 라이브러리를 사용하여 Vite 또는 Webpack 플러그인을 사용하는 대체 글꼴 CSS를 자동으로 삽입할 수 있습니다.

CSS 파일에 Roboto 글꼴이 정의되어 있다고 가정해 보겠습니다.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

Fontaine은 빌드 체인에 쉽게 연결할 수 있도록 ViteWebpack 변환기를 제공하여 다음 자바스크립트에 표시된 대로 플러그인을 사용 설정합니다.

import { FontaineTransform } from 'fontaine'

const options = {
  fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
  // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  resolvePath: (id) => 'file:///path/to/public/dir' + id,
  // overrideName: (originalName) => `${name} override`
  // sourcemap: false
}

Vite를 사용하는 경우 다음과 같이 플러그인을 추가합니다. javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Webpack을 사용하는 경우 다음과 같이 사용 설정합니다.

// Webpack
export default {
  plugins: [FontaineTransform.webpack(options)]
}

모듈은 파일을 자동으로 스캔하여 @font-face 규칙을 수정합니다. css @font-face { font-family: 'Roboto'; font-display: swap; src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff'); font-weight: 700; } /* This will be generated. */ @font-face { font-family: 'Roboto override'; src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'), local('Arial'), local('Noto Sans'); ascent-override: 92.7734375%; descent-override: 24.4140625%; line-gap-override: 0%; }

이제 CSS에서 Roboto override를 대체 글꼴로 사용할 수 있습니다. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Capsize 라이브러리 사용

Next.js, Nuxt, Webpack 또는 Vite를 사용하지 않는 경우 Capsize 라이브러리를 사용하여 대체 CSS를 생성하는 방법도 있습니다.

새로운 createFontStack API

이 API는 createFontStack라는 @capsize/core 패키지의 일부로, 글꼴 스택을 지정하는 것과 동일한 순서로 글꼴 측정항목의 배열을 허용합니다 (font-family 속성).

여기에서 Capsize 사용에 관한 문서를 참조하세요.

다음 예를 살펴보겠습니다. 원하는 웹 글꼴은 Lobster이며, Helvetica Neue를 사용한 후 Arial로 돌아갑니다. CSS에서는 font-family: Lobster, 'Helvetica Neue', Arial입니다.

  1. 핵심 패키지에서 createFontStack을 가져옵니다.

    import { createFontStack } from '@capsizecss/core';
    
  2. 원하는 각 글꼴의 글꼴 측정항목을 가져옵니다 (위의 글꼴 측정항목 참고). javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. 글꼴 스택을 만들고 글꼴 모음 CSS 속성을 통해와 동일한 순서를 사용하여 측정항목을 배열로 전달합니다. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

이는 다음을 반환합니다.

{
  fontFamily: Lobster, 'Lobster Fallback: Helvetica Neue', 'Lobster Fallback: Arial',
  fontFaces: [
    {
      '@font-face' {
      'font-family': '"Lobster Fallback: Helvetica Neue"';
      src: local('Helvetica Neue');
      'ascent-override': '115.1741%';
      'descent-override': '28.7935%';
      'size-adjust': '86.8251%';
      }
     '@font-face' {
       'font-family': '"Lobster Fallback: Arial"';
       src: local('Arial');
       'ascent-override': 113.5679%;
       'descent-override': 28.392%;
       'size-adjust': 88.053%;
     }
   }
 ]
}

fontFamily 및 fontFaces 코드를 CSS에 추가해야 합니다. 다음 코드는 CSS 스타일 시트 또는 <style> 블록 내에서 구현하는 방법을 보여줍니다.

<style type="text/css">
  .heading {
    font-family: 
  }

  
</style>

그러면 다음과 같은 CSS가 생성됩니다.

.heading {
  font-family: Lobster, 'Lobster Fallback: Helvetica Neue',
    'Lobster Fallback: Arial';
}

@font-face {
  font-family: 'Lobster Fallback: Helvetica Neue';
  src: local('Helvetica Neue');
  ascent-override: 115.1741%;
  descent-override: 28.7935%;
  size-adjust: 86.8251%;
}
@font-face {
  font-family: 'Lobster Fallback: Arial';
  src: local('Arial');
  ascent-override: 113.5679%;
  descent-override: 28.392%;
  size-adjust: 88.053%;
}

@capsize/metrics 패키지를 사용하여 재정의 값을 계산하고 CSS에 직접 적용할 수도 있습니다.

const fontMetrics = require(`@capsizecss/metrics/inter`);
const fallbackFontMetrics = require(`@capsizecss/metrics/arial`);
const mainFontAvgWidth = fontMetrics.xAvgWidth / fontMetrics.unitsPerEm;
const fallbackFontAvgWidth = fallbackFontMetrics.xAvgWidth / fallbackFontMetrics.unitsPerEm;
let sizeAdjust = mainFontAvgWidth / fallbackFontAvgWidth;
let ascent = fontMetrics.ascent / (unitsPerEm * fontMetrics.sizeAdjust));
let descent = fontMetrics.descent / (unitsPerEm * fontMetrics.sizeAdjust));
let lineGap = fontMetrics.lineGap / (unitsPerEm * fontMetrics.sizeAdjust));

감사의 말씀

히어로 이미지: Alexander Andrews, Unsplash