أدوات إطار العمل للعناصر الاحتياطية للخطوط

Janicklas Ralph James
Janicklas Ralph James

غالبًا ما تواجه المواقع الإلكترونية التي تحمّل الخطوط باستخدام font-display: swap مشكلة تغيُّر التصميم (CLS) عند تحميل خط الويب واستبداله بالخط الاحتياطي.

يمكنك منع حدوث 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 في الإصدار 13 من Next.js. يقدّم المكوّن واجهة برمجة تطبيقات لاستيراد خطوط 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: سلسلة أو قيمة منطقية خاطئة تضبط ما إذا كان يجب استخدام خط احتياطي تلقائي لتقليل متغيّرات التصميم التراكمية. القيم المحتمَلة هي Arial أو Times New Roman أو false. القيمة التلقائية هي Arial. إذا كنت تريد استخدام خط مكتوب بحرف لاتيني عريض، ننصحك بضبط هذه القيمة على Times New Roman.

خيار آخر لخطوط Google

إذا لم يكن استخدام المكوّن next/font متاحًا، يمكنك استخدام علامة optimizeFonts للاستفادة من هذه الميزة مع "Google Fonts". تم تفعيل ميزة optimizeFonts في Next.js تلقائيًا. تُدرج هذه الميزة ملف CSS الخاص بخدمة "خطوط Google" في استجابة HTML. بالإضافة إلى ذلك، يمكنك تفعيل ميزة تعديل الخطوط الاحتياطية من خلال ضبط العلامة experimental.adjustFontFallbacksWithSizeAdjust في next.config.js، كما هو موضّح في المقتطف التالي:

// 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" أو لم يكن لديك بيان @font-face لخط، يمكنك الإفصاح عن هذه الخطوط كخيارات إضافية.

في معظم الحالات، يمكن للوحدة قراءة قواعد @font-face من CSS واستنتاج التفاصيل تلقائيًا، مثل عائلة الخطوط وعائلة الخطوط الاحتياطية ونوع العرض.

إذا تمّ تحديد الخط في مكان لا يمكن للوحدة العثور عليه، يمكنك تمرير معلومات المقاييس كما هو موضّح في مقتطف الرمز البرمجي التالي.

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.

لنفترض أنّ لديك خط 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;
}

توفّر Fontaine محوِّلَي Vite وWebpack للتوصيل بسلسلة الإنشاء بسهولة، ويمكنك تفعيل المكوّن الإضافي كما هو موضّح في 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 الاحتياطي.

واجهة برمجة التطبيقات الجديدة createFontStack

تشكّل واجهة برمجة التطبيقات جزءًا من حزمة @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. أنشئ حزمة الخطوط، مع تمرير المقاييس كصفيف، باستخدام الترتيب نفسه الذي تستخدمه من خلال سمة font-family في 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));