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

Janicklas Ralph James
Janicklas Ralph James

غالبًا ما تتأثر المواقع الإلكترونية التي تحمِّل الخطوط باستخدام font-display: exchange بمتغيّرات التصميم (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 بدون محاولة تعديل حجم الخط الاحتياطي. يوضّح المثال الثاني كيفية تحسين تجربة التحميل من خلال تعديل الحجم باستخدام قاعدة @font-face في CSS.

بدون تعديل حجم الخط

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 Fonts أو لم يتوفّر تعريف @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));