כלי מסגרת לחלופות של גופנים

Janicklas Ralph James
Janicklas Ralph James

אתרים שמטעינים גופנים באמצעות font-display: swap סובלים לעיתים קרובות מתזוזות בפריסת הדף (CLS) כשגופן ה-webfont נטען ומוחלף בגופן החלופי.

כדי למנוע CLS, אפשר לשנות את המאפיינים של גופן החלופה כך שיתאימו לאלה של הגופן הראשי. מאפיינים כמו size-adjust,‏ ascent-override,‏ descent-override ו-line-gap-override בכלל @font-face יכולים לעזור לשנות את המדדים של גופן חלופי, וכך לתת למפתחים יותר שליטה על אופן הצגת הגופנים. מידע נוסף על גופנים חלופיים ועל נכסי ההחרגה זמין בפוסט הזה. אפשר גם לראות הטמעה פעילה של הטכניקה הזו בהדגמה הזו.

במאמר הזה נסביר איך מתבצעת ההטמעה של התאמות של גודל הגופן במסגרות Next.js ו-Nuxt.js כדי ליצור את קובץ ה-CSS של הגופן החלופי ולצמצם את הערך של CLS. בנוסף, תלמדו איך ליצור גופנים חלופיים באמצעות כלים חוצי-תחומים כמו 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. הרכיב מספק ממשק API לייבוא גופנים של Google או גופנים מותאמים אישית לדפים, וכולל אירוח עצמאי מובנה ואוטומטי של קובצי הגופנים.

כשמשתמשים במדדי הגופן החלופיים, הם מחושבים באופן אוטומטי ומוטמעים בקובץ ה-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;
}

כדי לעבור ל-next/font:

  1. כדי להעביר את ההצהרה על הגופן Roboto ל-JavaScript, מייבאים את הפונקציה Roboto מ-'next/font'. ערך ההחזרה של הפונקציה יהיה שם הכיתה שתוכלו להשתמש בו בתבנית הרכיב. חשוב לזכור להוסיף את 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 מגדירה באופן אוטומטי את הגופן החלופי כ-Arial או כ-Times New Roman, בהתאם לסוג הגופן (serif לעומת sans-serif, בהתאמה).

עבור @next/font/local: מחרוזת או ערך בוליאני False שקובעים אם צריך להשתמש בגופן חלופי אוטומטי כדי לצמצם את Cumulative Layout Shift (Cumulative Layout Shift). הערכים האפשריים הם Arial,‏ Times New Roman או false. ערך ברירת המחדל הוא Arial. אם ברצונך להשתמש בגופן serif, מומלץ להגדיר את הערך הזה ל-Times New Roman.

אפשרות נוספת לגופנים של Google

אם אי אפשר להשתמש ברכיב next/font, אפשר להשתמש בדגל optimizeFonts כדי להשתמש בתכונה הזו עם Google Fonts. התכונה optimizeFonts מופעלת כברירת מחדל ב-Next.js. התכונה הזו מחילה את ה-CSS של Google Font בתגובת ה-HTML. בנוסף, אפשר להפעיל את התכונה של התאמת גופנים חלופיים על ידי הגדרת הדגל experimental.adjustFontFallbacksWithSizeAdjust בקובץ next.config.js, כפי שמוצג בקטע הקוד הבא:

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

הערה: לא מתוכננת תמיכה בתכונה הזו עם הגרסה החדשה של app dir. בטווח הארוך, עדיף להשתמש ב-next/font.

איך משנים את הגופנים החלופיים ב-Nuxt

‎@nuxtjs/fontaine הוא מודול למסגרת Nuxt.js שמחשב באופן אוטומטי את ערכי המדדים של גופן החלופי ויוצר את קובץ ה-CSS @font-face החלופי.

כדי להפעיל את המודול, מוסיפים את @nuxtjs/fontaine להגדרת המודולים:

import { defineNuxtConfig } from 'nuxt'

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

אם אתם משתמשים ב-Google Fonts או שאין לכם הצהרה @font-face לגבי גופן, אתם יכולים להצהיר עליהם כאפשרויות נוספות.

ברוב המקרים, המודול יכול לקרוא את הכללים של @font-face מ-CSS ולהסיק באופן אוטומטי פרטים כמו font-family, משפחת הגופן החלופית וסוג התצוגה.

אם הגופן מוגדר במקום שלא ניתן לגילוי על ידי המודול, אפשר להעביר את פרטי המדדים כפי שמתואר בקטע הקוד הבא.

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. אפשר להשתמש בספרייה הזו בפרויקט כדי להחדיר באופן אוטומטי CSS של גופן חלופי באמצעות יישומי פלאגין של Vite או Webpack.

נניח שבקובץ ה-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 יש טרנספורמטורים של Vite ו-Webpack שניתן לחבר בקלות לשרשרת ה-build. כדי להפעיל את הפלאגין, צריך להשתמש בקוד ה-JavaScript הבא.

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%; }

עכשיו אפשר להשתמש ב-Roboto override כגופן חלופי ב-CSS. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

שימוש בספרייה Capsize

אם אתם לא משתמשים ב-Next.js, ב-Nuxt, ב-Webpack או ב-Vite, אפשרות אחרת היא להשתמש בספריית Capsize כדי ליצור את קובץ ה-CSS החלופי.

ממשק ה-API החדש createFontStack

ה-API הוא חלק מהחבילה @capsize/core שנקראת createFontStack, שמקבלת מערך של מדדי גופנים באותו סדר שבו אתם מציינים את מקבץ הגופנים (המאפיין 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 font-family. 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));