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

جانيكلاس رالف جيمس
جانيكلاس رالف جيمس

غالبًا ما تواجه المواقع الإلكترونية التي تحمِّل الخطوط باستخدام ميزة 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: exchange بشكل عام لمنع ظهور وميض نص غير مرئي (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

إذا لم يكن استخدام المكوِّن next/font خيارًا، يمكنك استخدام علامة optimizeFonts كطريقة أخرى لاستخدام هذه الميزة مع Google Fonts. يتضمن مقتطف Next.js ميزة OptimizeFonts مُفعَّلة تلقائيًا. تعمل هذه الميزة على تضمين خدمة 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));

شكر وتقدير

صورة رئيسية من إعداد ألكسندر أندروز على Unsplash