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

Janicklas Ralph James
Janicklas Ralph James

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

ويمكنك منع متغيّرات التصميم التراكمية (CLS) من خلال ضبط أبعاد الخط الاحتياطي لتتطابق مع الخط الأساسي. يمكن أن تساعد السمات مثل size-adjust وascent-override وdescent-override وline-gap-override في القاعدة @font-face في إلغاء مقاييس الخط الاحتياطي، ما يتيح للمطوّرين المزيد من التحكّم في كيفية عرض الخطوط. يمكنك الاطّلاع على المزيد من المعلومات حول الإجراءات الاحتياطية للخطوط وسمات الإلغاء في هذه المشاركة. يمكنك أيضًا الاطّلاع على طريقة تنفيذ صالحة لهذه التقنية في هذا العرض التوضيحي.

تستكشف هذه المقالة كيفية تنفيذ تعديلات حجم الخط في إطارَي عمل Next.js وNuxt.js لإنشاء CSS للخط الاحتياطي وتقليل متغيّرات التصميم التراكمية (CLS). كما يوضح أيضًا كيف يمكنك إنشاء خطوط احتياطية باستخدام أدوات القطع المتقاطع مثل Fontaine وCapsize.

الخلفية

font-display: change: يُستخدَم هذا التنسيق بشكل عام لمنع 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;
}

للانتقال إلى التالي/الخط:

  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. إذا كنت تريد استخدام خط serif، ننصحك بضبط هذه القيمة على Times New Roman.

من بين خيارات Google Fonts

إذا لم يكن استخدام المكوِّن next/font متاحًا، يمكنك استخدام العلامة optimizeFonts كطريقة أخرى لاستخدام هذه الميزة مع Google Fonts. سبق أن تم تفعيل ميزة opt.Fonts تلقائيًا بشكل تلقائي في 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 تحسب تلقائيًا قيم مقاييس الخطوط الاحتياطية وتنشئ لغة CSS @font-face الاحتياطية.

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

شكر وتقدير

صورة رئيسية من تصميم ألكسندر أندرو على Unسباش.