تحسين تطبيق الويب التقدّمي تدريجيًا

يمكنك تصميم تطبيقات متوافقة مع المتصفّحات الحديثة وإجراء تحسينات تدريجية عليها كما في عام 2003.

في آذار (مارس) 2003، قام فينك أذهل ستيف شامبون عالم تصميم الويب بمفهوم التحسين التدريجي استراتيجية لتصميم الويب تؤكد على تحميل محتوى صفحة الويب الأساسية أولاً، ثم يضيف تدريجيًا المزيد من التفاصيل وطبقات عرض تقديمي وميزات أكثر صرامة من الناحية التقنية بالإضافة إلى المحتوى. بينما كان التحسين التدريجي في عام 2003 يدور حول استخدام التصاميم الحديثة ميزات CSS وJavaScript غير المزعج، وحتى رسومات موجّهة يمكن تغيير حجمها. يتعلق التحسين التدريجي في عام 2020 وما بعده باستخدام أحدث إصدار من المتصفّح.

تصميم ويب شامل للمستقبل مع التحسين التدريجي. شريحة عنوان من العرض التقديمي الأصلي لـ "فينك" و"شامبون"
الشريحة: تصميم ويب شامل للمستقبل مع التحسين التدريجي. (المصدر)

JavaScript حديث

بالحديث عن JavaScript، وضع توافق المتصفّح مع أحدث إصدار من JavaScript الأساسي ES 2015 الرائعة أمرًا رائعًا. يتضمّن المعيار الجديد الوعود والوحدات والفئات والقيم الحرفية للنماذج والدوال السهمية وlet وconst المعلَمات التلقائية والمولّدات وعملية التدمير والاستراحة والانتشار وMap/Set WeakMap/WeakSet، وغير ذلك الكثير. جميعها متاحة.

جدول دعم CanIUse لميزات ES6 التي تعرض الدعم في جميع المتصفحات الرئيسية.
جدول دعم المتصفح ECMAScript 2015 (ES6). (المصدر)

وهي ميزة غير متزامنة، وهي إحدى ميزات الإصدار ES 2017، وإحدى الوظائف المفضّلة لديّ شخصيًا، يمكن استخدامها في جميع المتصفحات الرئيسية. تتيح الكلمتان الرئيسيتان async وawait سلوكًا غير متزامن وقائم على الوعود أن تتم كتابتها بأسلوب أوضح، مع تجنب الحاجة إلى تهيئة سلاسل الوعود بشكل صريح.

جدول دعم CanIUse للدوال غير المتزامنة التي تعرض التوافق مع جميع المتصفحات الرئيسية
جدول دعم متصفِّح الدوال غير المتزامنة. (المصدر)

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

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
صورة الخلفية المميزة للعشب الأخضر في نظام التشغيل Windows XP.
عندما يتعلق الأمر بميزات JavaScript الأساسية، لون العشب أخضر. (لقطة شاشة لمنتج Microsoft، تُستخدم مع الإذن).

نموذج التطبيق: Fugu Greetings

في هذه المقالة، أعمل باستخدام تطبيق ويب تقدّمي (PWA) بسيط يسمى تحية "فوغو" (GitHub). يمثّل اسم هذا التطبيق تلميحًا لمشروع Project Fugu 🐡، فهو محاولة لتقديم كل ما هو جديد على الويب. إمكانيات تطبيقات Android/iOS/الكمبيوتر المكتبي. يمكنك قراءة المزيد عن المشروع في الصفحة المقصودة.

Fugu Greetings هو تطبيق رسم يتيح لك إنشاء بطاقات تهنئة افتراضية وإرسال إلى أحبائك. إنه يوضح مفاهيم PWA. من المهم موثوقة وتفعيل وضع "عدم الاتصال بالإنترنت" بالكامل، لذلك حتى إذا لم لديك شبكة، يمكنك الاستمرار في استخدامها. إنّه أيضًا قابل للتثبيت على الشاشة الرئيسية للجهاز ويتكامل بسلاسة مع نظام التشغيل كتطبيق مستقل.

تطبيق Fugu Greetings PWA مع رسم يشبه شعار منتدى PWA
نموذج تطبيق Fugu Greetings

التحسين التدريجي

وبعد الانتهاء من هذا العمل، حان الوقت للحديث عن التحسين التدريجي. تعريف مسرد مصطلحات مستندات الويب MDN المفهوم على النحو التالي:

التحسين التدريجي هو فلسفة تصميم توفر أساسًا من المحتوى والوظائف لأكبر عدد ممكن من المستخدمين، مع تقديم أفضل تجربة ممكنة للمستخدمين فقط من خلال أحدث إصدار في المتصفحات التي يمكنها تشغيل جميع الرموز المطلوبة.

اكتشاف الميزات يُستخدم عمومًا لتحديد ما إذا كان بإمكان المتصفحات التعامل مع وظائف أكثر حداثة، بينما يتم استخدام الرموز polyfill لإضافة الميزات الناقصة باستخدام JavaScript.

[…]

التحسين التدريجي هو أسلوب مفيد يتيح لمطوّري الويب التركيز على تطوير أفضل مواقع الويب الممكنة أثناء جعل هذه المواقع تعمل على عدة برامج وكيل مستخدم غير معروفة. التقليص السلس مرتبط ولكن ليس هو الشيء نفسه وغالبًا ما يُنظر إليه على أنه يسير في الاتجاه المعاكس إلى التحسين التدريجي. في الواقع، كلا النهجين صالحان ويمكن أن يتكاملا في كثير من الأحيان مع بعضهما البعض.

المساهمون في MDN

قد يكون بدء كل بطاقة تهنئة من الصفر أمرًا مرهقًا حقًا. إذًا لماذا لا تتوفّر ميزة تسمح للمستخدمين باستيراد صورة والبدء من هناك؟ باستخدام نهج تقليدي، كنت قد استخدمت <input type=file> العنصر لتحقيق ذلك. أولاً، عليك إنشاء العنصر وضبط type على 'file' وإضافة أنواع MIME إلى السمة accept. ثم "النقر" آليًا والاستماع إلى التغييرات. عند تحديد صورة، يتم استيرادها مباشرةً إلى اللوحة.

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

عند توفير ميزة الاستيراد، يجب أن تتوفّر الميزة تصدير على الأرجح. حتى يتمكن المستخدمون من حفظ بطاقات التهنئة محليًا. الطريقة التقليدية لحفظ الملفات هي إنشاء رابط ارتساء مع download مع عنوان URL لملف blob باعتباره href. يمكنك أيضًا النقر آليًا لبدء التنزيل، ولمنع تسرُّب الذاكرة، نأمل ألا ننسى إبطال عنوان URL لكائن الكائن الثنائي الكبير (blob).

const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

لكن انتظر دقيقة. عقليًا، لم "تنزّل" بطاقة تهنئة، لديك "تم الحفظ" بها. بدلاً من عرض "حفظ" مربع حوار يتيح لك اختيار مكان وضع الملف نزَّل المتصفِّح بطاقة التهنئة مباشرةً بدون تفاعل المستخدم ووضعها مباشرة في مجلد التنزيلات. هذا ليس رائعًا.

ماذا لو كانت هناك طريقة أفضل؟ ماذا لو كان بإمكانك فتح ملف محلي وتعديله ثم حفظ التعديلات، إما إلى ملف جديد أو إلى الملف الأصلي الذي فتحته في البداية؟ اتضح أن هناك. File System Access API: فتح الملفات وإنشائها بالإضافة إلى تعديلها وحفظها .

إذًا، كيف يمكنني اكتشاف واجهة برمجة تطبيقات؟ تعرض واجهة برمجة التطبيقات File System Access API طريقة جديدة للسمة window.chooseFileSystemEntries(). وبالتالي، أحتاج إلى تحميل وحدات استيراد وتصدير مختلفة بشكل مشروط حسب ما إذا كانت هذه الطريقة متاحة أم لا. لقد أوضحنا كيفية القيام بذلك أدناه.

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

ولكن قبل التعمق في تفاصيل واجهة برمجة تطبيقات File System Access API، اسمحوا لي أن أبرز بسرعة نمط التحسين التدريجي هنا. في المتصفحات التي لا تتيح حاليًا واجهة برمجة التطبيقات File System Access API، أحمّل النصوص البرمجية القديمة. يمكنك الاطلاع على علامتَي تبويب الشبكة في Firefox وSafari أدناه.

برنامج Safari Web Inspector (أداة فحص الويب في Safari) يعرض الملفات القديمة التي يتم تحميلها
علامة تبويب الشبكة في Safari Web Inspector.
أدوات مطوّري برامج Firefox تعرض طريقة تحميل الملفات القديمة
علامة تبويب "الشبكة" في أدوات مطوّري برامج Firefox

ولكن في Chrome، وهو متصفح متوافق مع واجهة برمجة التطبيقات، لن يتم تحميل سوى النصوص البرمجية الجديدة. وقد تم تحقيق ذلك بشكل أنيق بفضل الإصدار import() الديناميكي الذي تستخدمه جميع المتصفحات الحديثة الدعم. كما ذكرت سابقًا، العشب أخضر اللون في هذه الأيام.

&quot;أدوات مطوري البرامج في Chrome&quot; تعرض الملفات الحديثة التي يتم تحميلها
علامة تبويب "الشبكة" في "أدوات مطوري البرامج في Chrome"

واجهة برمجة التطبيقات File System Access API

وبعد أن عالجنا هذا الأمر، حان الوقت للاطّلاع على طريقة التنفيذ الفعلية استنادًا إلى واجهة برمجة التطبيقات File System Access API. لاستيراد صورة، أتصل بـ window.chooseFileSystemEntries(). وأمرره إلى السمة accepts حيث أقول "أريد ملفات صور". يمكن استخدام امتدادات الملفات وأنواع MIME. ينتج عن ذلك مؤشر ملف يمكنني من خلاله الحصول على الملف الفعلي من خلال استدعاء getFile().

const importImage = async () => {
  try {
    const handle = await window.chooseFileSystemEntries({
      accepts: [
        {
          description: 'Image files',
          mimeTypes: ['image/*'],
          extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
        },
      ],
    });
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

عملية تصدير الصورة هي نفسها تقريبًا، ولكن هذه المرة أحتاج إلى تمرير معلمة النوع 'save-file' إلى الطريقة chooseFileSystemEntries(). من هنا يظهر لي مربع حوار لحفظ الملفات. عندما يكون الملف مفتوحًا، لم يكن هذا الإجراء ضروريًا لأنّ 'open-file' هو الإعداد التلقائي. تم ضبط المعلَمة accepts بشكل مشابه سابقًا، ولكن هذه المرة تقتصر على صور PNG فقط. مرة أخرى، أعود إلى مؤشر الملف، ولكن بدلاً من الحصول على الملف، هذه المرة أنشئ بثًا قابلاً للكتابة عن طريق الاتصال بـ createWritable(). بعد ذلك، أكتب الكائن الثنائي الكبير، وهو صورة بطاقة التهنئة، إلى الملف. أخيرًا، أغلق ساحة المشاركات القابلة للكتابة.

دائمًا ما يفشل كل شيء: قد تكون مساحة القرص غير متاحة، قد يكون هناك خطأ في الكتابة أو القراءة، أو ربما يلغي المستخدم مربع حوار الملف. هذا هو السبب في أنني دائمًا ألتزم المكالمات بجملة try...catch.

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

باستخدام التحسين التدريجي مع واجهة برمجة التطبيقات File System Access API، يمكنني فتح ملف كما كان من قبل. يتم رسم الملف المستورَد مباشرةً على اللوحة. يمكنني إجراء تعديلاتي وحفظها في نهاية المطاف باستخدام مربع حوار حفظ حقيقي حيث يمكنني اختيار اسم الملف وموقع تخزينه. ويكون الملف جاهزًا الآن لحفظه للأبد.

تطبيق Fugu Greetings يظهر فيه مربّع حوار مفتوح للملف
مربع حوار فتح الملف.
تم استيراد تطبيق Fugu Greetings الآن.
الصورة التي تم استيرادها.
تطبيق Fugu Greetings مع الصورة المعدّلة
حفظ الصورة المعدَّلة في ملف جديد

واجهتا برمجة التطبيقات Web Share وWeb Share Target

بصرف النظر عن الاحتفاظ بها للأبد، ربما أرغب في مشاركة بطاقة التهنئة. وهذا ما تفعله Web Share API Web Share Target API يتيح لي ذلك. حصلت الأجهزة المحمولة وأنظمة تشغيل سطح المكتب مؤخرًا على ميزة المشاركة المدمجة والآليات الأخرى. على سبيل المثال، في ما يلي ورقة المشاركة في متصفّح Safari على أجهزة الكمبيوتر المكتبي على نظام التشغيل macOS والتي تم تشغيلها من مقالة في. مدونتي. عند النقر على زر مشاركة المقالة، يمكنك مشاركة رابط يؤدي إلى المقالة مع صديق، وذلك على سبيل المثال، عبر تطبيق "الرسائل" على نظام التشغيل macOS.

تم تشغيل ورقة المشاركة في متصفّح Safari على أجهزة كمبيوتر سطح المكتب على نظام التشغيل macOS من خلال زر المشاركة في المقالة.
واجهة برمجة التطبيقات Web Share API في متصفّح Safari على جهاز كمبيوتر سطح المكتب على نظام التشغيل macOS

التعليمات البرمجية اللازمة لتنفيذ ذلك واضحة جدًا. اتصل بـ navigator.share() يمكنك تمريره اختياريًا إلى title وtext وurl في كائن. ولكن ماذا لو أردت إرفاق صورة؟ لا يتوافق المستوى 1 من Web Share API مع هذا الإجراء حتى الآن. الخبر السار هو أنّ المستوى 2 من ميزة "مشاركة الويب" قد أضاف إمكانيات مشاركة الملفات.

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

دعني أوضح لك كيفية تنفيذ ذلك باستخدام تطبيق بطاقة Fugu Greeting. أولاً، أحتاج إلى تحضير كائن data باستخدام مصفوفة files تتألف من كائن ثنائي كبير واحد، ثم title وtext. وبعد ذلك، ومن أفضل الممارسات، أستخدِم طريقة navigator.canShare() الجديدة التي ما يقترحه اسمه: فهي تخبرني ما إذا كان بإمكان المتصفّح مشاركة كائن data الذي أحاول مشاركته من الناحية الفنية. إذا طلبت مني navigator.canShare() مشاركة البيانات، أنا مستعدّ الاتصال بـ navigator.share() كما في السابق. بما أنّه قد يتعذّر تنفيذ بعض الخطوات، سأستخدم مجموعة try...catch من جديد.

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

كما في السابق، أستخدم التحسين التدريجي. إذا كان كل من 'share' و'canShare' موجودين على الكائن navigator، عندها فقط سأنتقل إلى الأمام تحميل share.mjs عبر import() الديناميكي. لا أحمّل الملف على المتصفّحات مثل Safari على الأجهزة الجوّالة التي تستوفي شرطًا واحدًا فقط من الشرطَين. الوظيفة.

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

في Fugu Greetings، إذا نقرت على الزر مشاركة على متصفّح داعم مثل Chrome على Android، يتم فتح ورقة المشاركة المضمنة. يمكنني، على سبيل المثال، اختيار Gmail، فتنبثق أداة إنشاء البريد الإلكتروني تم إرفاق صورة.

ورقة مشاركة على مستوى نظام التشغيل تعرض تطبيقات متنوعة يمكن مشاركة الصورة معها.
اختيار تطبيق لمشاركة الملف معه.
أداة إنشاء الرسائل الإلكترونية في Gmail مع الصورة المرفقة.
يتم إرفاق الملف برسالة إلكترونية جديدة في منشئ المحتوى في Gmail.

واجهة برمجة تطبيقات منتقي جهات الاتصال

بعد ذلك، أريد التحدّث عن جهات الاتصال، بمعنى دفتر عناوين الجهاز. أو تطبيق "مدير جهات الاتصال" عند كتابة بطاقة تهنئة، قد لا يكون من السهل دائمًا كتابتها بشكل صحيح اسم شخص ما. فعلى سبيل المثال، لدي صديق كريم يفضل كتابة اسمه بالأحرف السيريلية. أنا باستخدام لوحة مفاتيح QWERTZ ألمانية وليس لديهم أي فكرة عن كيفية كتابة أسمائهم. هذه مشكلة يمكن لواجهة برمجة تطبيقات اختيار جهات الاتصال حلها. بما أنّ صديقي مخزن في تطبيق جهات الاتصال على هاتفي، عبر واجهة برمجة تطبيقات منتقي جهات الاتصال، يمكنني النقر على جهات الاتصال من الويب.

أولاً، أحتاج إلى تحديد قائمة الخصائص التي أريد الوصول إليها. في هذه الحالة، أريد فقط الأسماء، ولكن في حالات الاستخدام الأخرى، قد أهتم بأرقام الهواتف وعناوين البريد الإلكتروني وصور الأفاتار أو الأيقونات أو العناوين المادية. بعد ذلك، يمكنني إعداد كائن options وضبط multiple على true، لأتمكّن من اختيار المزيد أكثر من إدخال واحد. أخيرًا، يمكنني استدعاء الدالة navigator.contacts.select()، والتي تعرض الخصائص المطلوبة لجهات الاتصال التي يحددها المستخدم.

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

وربما تكون قد تعلمت الآن النمط التالي: لا أحمّل الملف إلا عندما تكون واجهة برمجة التطبيقات متوافقة.

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

في Fugu Greeting، عندما أنقر على زر جهات الاتصال وأختار أفضل صديقَين لي Церге البودكاست кинакоровиш ضرباتрин و劳伦斯·爱德华·"拉里"·佩奇، يمكنك معرفة كيف فإن منتقي جهات الاتصال يقتصر على إظهار أسمائهم فقط، وليس عناوين بريدهم الإلكتروني، أو معلومات أخرى مثل أرقام هواتفهم. ثم يتم رسم أسمائهم على بطاقة التهنئة.

منتقي جهات الاتصال يعرض اسم جهتي اتصال في دفتر العناوين.
تحديد اسمين باستخدام منتقي جهات الاتصال من دفتر العناوين.
اسما جهتي الاتصال اللتين تم اختيارهما سابقًا والمرسومة على بطاقة التهنئة.
ثم يتم رسم الاسمين على بطاقة التهنئة.

واجهة برمجة التطبيقات Asynchronous Clipboard API

الخطوة التالية هي النسخ واللصق. يعد النسخ واللصق من العمليات المفضلة لدينا كمطوري البرامج. بصفتي مؤلف بطاقات تهنئة، قد أحتاج في بعض الأحيان إلى الشيء نفسه. أريد إما لصق صورة في بطاقة تهنئة أعمل عليها، أو نسخ بطاقة التهنئة حتى أتمكن من متابعة تعديلها من في مكان آخر. Async Clipboard API تدعم النصوص والصور معًا. لأشرح لكم كيف أضفتُ دعم النسخ واللصق إلى Fugu تطبيق رسائل الترحيب

لنسخ شيء ما على حافظة النظام، أحتاج إلى الكتابة إليه. تستخدم الطريقة navigator.clipboard.write() صفيفًا من عناصر الحافظة . يُعد كل عنصر في الحافظة في الأساس كائنًا ذا قيمة ثنائية، ونوع الكائن الثنائي الكبير (blob) كمفتاح.

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

لللصق، أحتاج إلى التكرار فوق عناصر الحافظة التي أحصل عليها من خلال استدعاء navigator.clipboard.read() السبب في ذلك هو أن عناصر متعددة في الحافظة قد تكون موجودة في الحافظة في تمثيلات مختلفة. يتضمّن كل عنصر في الحافظة حقل "types" يوضّح لي أنواع MIME المتاحة الموارد. أستدعي طريقة getType() لعنصر الحافظة، وأمرر نوع MIME الذي حصلت عليه من قبل.

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

ولا داعي لقول ذلك الآن. لا أنفّذ هذا الإجراء إلا على المتصفّحات المتوافقة.

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

إذًا، كيف يعمل هذا عمليًا؟ لدي صورة مفتوحة في تطبيق معاينة macOS ونسخه إلى الحافظة. عندما أنقر على لصق، يطلب مني تطبيق Fugu Greetings ما إذا كنت أريد السماح للتطبيق بعرض النصوص والصور على الحافظة أم لا.

تطبيق Fugu Greetings يعرض الطلب بإذن الوصول إلى الحافظة
إشعار بإذن الوصول إلى الحافظة

أخيرًا، بعد قبول الإذن، يتم لصق الصورة في التطبيق. الطريقة الأخرى تعمل أيضًا. دعني أنسخ بطاقة تهنئة إلى الحافظة. عندما أفتح "معاينة" وانقر على ملف ثم جديد من الحافظة، يتم لصق بطاقة التهنئة في صورة جديدة بلا عنوان.

تطبيق &quot;الميزات التجريبية&quot; على نظام التشغيل macOS يتضمّن صورة بلا عنوان تم لصقها للتو.
تم لصق صورة في تطبيق المعاينة على نظام التشغيل macOS.

واجهة برمجة التطبيقات Badging API

هناك واجهة برمجة تطبيقات أخرى مفيدة وهي Badging API. ولأنّه تطبيق ويب تقدّمي قابل للتثبيت، فإنّ تطبيق Fugu Greetings لديه رمز تطبيق التي يمكن للمستخدمين وضعها على شريط التطبيقات أو الشاشة الرئيسية. هناك طريقة ممتعة وسهلة لعرض واجهة برمجة التطبيقات وهي (ab)استخدامها في Fugu Greetings كعدّاد ضربات القلم. أضفت أداة معالجة حدث تعمل على زيادة عدّاد ضغطات القلم كلما حدث حدث pointerdown ثم يضبط شارة الرمز المحدث. عند محو اللوحة، تتم إعادة ضبط العدّاد وتتم إزالة الشارة.

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

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

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

في هذا المثال، رسمت الأرقام من واحد إلى سبعة، باستخدام خط واحد لكل رقم. أصبح عدّاد الشارات على الرمز الآن سبعة.

الأرقام من واحد إلى سبعة مرسومة على بطاقة التهنئة، ولكل منها خط بقلم واحد فقط.
رسم الأرقام من 1 إلى 7، باستخدام سبع ضغطات قلم.
رمز الشارة في تطبيق Fugu Greetings يظهر فيه الرقم 7
عدّاد ضربات القلم في شكل شارة رمز التطبيق.

واجهة برمجة التطبيقات Periodic Background Sync API

هل تريد أن تبدأ كل يوم بشيء جديد؟ من الميزات الرائعة في تطبيق Fugu Greetings أنّه يمكن أن يكون مصدر إلهام لك كل صباح بصورة خلفية جديدة لبدء بطاقة التهنئة. يستخدم التطبيق واجهة برمجة التطبيقات Periodic Background Sync API. لتحقيق ذلك.

تتمثّل الخطوة الأولى في تسجيل حدث مزامنة دوري في تسجيل مشغّل الخدمات. يرصد علامة مزامنة تُسمى 'image-of-the-day'. ويكون الحد الأدنى للفاصل الزمني هو يوم واحد، ليتمكّن المستخدم من الحصول على صورة خلفية جديدة كل 24 ساعة.

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

الخطوة الثانية هي الاستماع إلى حدث periodicsync في مشغّل الخدمات. إذا كانت علامة الحدث هي 'image-of-the-day'، أي العلامة التي تم تسجيلها من قبل، يتم استرداد صورة اليوم عن طريق الدالة getImageOfTheDay()، وتم نشر النتيجة لجميع العملاء، حتى يتمكنوا من تحديث اللوحات ذاكرات التخزين المؤقت.

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

مرة أخرى، هذا هو التحسين التدريجي، لذلك لا يتم تحميل الرمز إلا عندما يتيح المتصفح استخدام واجهة برمجة التطبيقات. وينطبق ذلك على كلّ من رمز العميل ورمز مشغّل الخدمات. في حال استخدام المتصفِّحات غير المتوافقة، لا يتم تحميل أي منهما. ملاحظة كيفية إجراء ذلك في مشغّل الخدمات، بدلاً من عنصر import() ديناميكي (هذا الخيار غير متاح في سياق مشغّل الخدمات) حتى الآن)، أستخدم النموذج الكلاسيكي importScripts()

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

في تحية Fugu Greeting، يؤدي الضغط على الزر خلفية إلى إظهار صورة بطاقة التهنئة لليوم التي يتم تحديثها كل يوم عبر واجهة برمجة التطبيقات Periodic Background Sync API.

تطبيق Fugu Greetings مع صورة بطاقة تهنئة جديدة لليوم
يؤدي الضغط على الزر الخلفية إلى عرض صورة اليوم.

واجهة برمجة تطبيقات تشغيل الإشعارات

في بعض الأحيان، حتى مع وجود الكثير من الإلهام، تحتاج إلى تذكير تلقائي لإنهاء الترحيب الذي بدأته. بنجاح. هذه ميزة يتم تفعيلها من خلال واجهة برمجة التطبيقات Notification Triggers API. بصفتي مستخدمًا، يمكنني إدخال وقت أريد فيه تنبيهي لإكمال بطاقة التهنئة. عند حلول ذلك الوقت، أتلقى إشعارًا بأنّ بطاقة التهنئة في انتظارك.

بعد المطالبة بالوقت المستهدف، يجدول التطبيق الإشعار من خلال showTrigger. يمكن أن يكون هذا الحدث TimestampTrigger بتاريخ الاستهداف المحدّد سابقًا. سيتم تشغيل إشعار التذكير على الجهاز، ولن تحتاج إلى الشبكة أو الخادم.

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

وكما هو الحال مع كل شيء آخر عرضتُه حتى الآن، هذا تحسين تدريجي، لذلك يتم تحميل التعليمة البرمجية بشكل مشروط فقط.

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

عندما أضع علامة في مربّع الاختيار تذكير في Fugu Greetings، تظهر رسالة عندما أريد أن يتم تذكيري بإنهاء بطاقة التهنئة.

تطبيق Fugu Greetings مع طلب يسأل المستخدم عن الوقت الذي يريد فيه تذكيره لإنهاء بطاقة التهنئة
تحديد موعد لتذكيرك بإكمال بطاقة تهنئة.

عندما يظهر إشعار مجدوَل في Fugu Greetings، يظهر تمامًا مثل أي إشعار آخر، ولكن كما كتبت من قبل، لم يتطلب الاتصال بالشبكة.

مركز إشعارات macOS يعرض إشعارًا تم تشغيله من Fugu Greetings
يظهر الإشعار الذي تم تشغيله في "مركز إشعارات macOS".

واجهة برمجة تطبيقات Wake Lock

أريد أيضًا تضمين Wake Lock API. في بعض الأحيان تحتاج فقط إلى التحديق لفترة طويلة على الشاشة حتى الحصول على الإلهام قبلك. أسوأ ما يمكن أن يحدث بعد ذلك هو إيقاف الشاشة. وقد تمنع واجهة برمجة التطبيقات Wake Lock API حدوث ذلك.

الخطوة الأولى هي الحصول على قفل تنشيط باستخدام navigator.wakelock.request method(). أدخلها على السلسلة 'screen' للحصول على قفل تنشيط الشاشة. بعد ذلك، أضيف أداة معالجة حدث ليتم تنبيهها عند فتح قفل التنشيط. يمكن أن يحدث هذا، على سبيل المثال، عندما يتغير مستوى رؤية علامة التبويب. إذا حدث هذا، يمكنني الحصول على قفل التنشيط مرة أخرى عندما تصبح علامة التبويب مرئية مرة أخرى.

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

نعم، هذا تحسين تدريجي، لذلك لا أحتاج إلى تحميله إلا عندما يتصفح يدعم واجهة برمجة التطبيقات.

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

في Fugu Greetings، يتوفّر مربّع الاختيار الأرق الذي يحافظ على تنشيط الشاشة.

يؤدي مربّع اختيار الأرق في حال تحديده إلى إبقاء الشاشة في الوضع النشط.
يؤدي مربّع اختيار الأرق إلى إبقاء التطبيق نشطًا.

واجهة برمجة تطبيقات Idle Detection

في بعض الأحيان، حتى إذا كنت تحدّق في الشاشة لساعات، عديم الفائدة ولا يمكنك استنباط أقل فكرة حول ما تفعله ببطاقة التهنئة. تسمح Idle Detection API للتطبيق برصد وقت عدم نشاط المستخدم. إذا كان المستخدم غير نشِط لفترة طويلة جدًا، تتم إعادة ضبط التطبيق على الحالة الأولية. ويمحو اللوحة. تخضع واجهة برمجة التطبيقات هذه حاليًا خلف إذن الإشعارات نظرًا لأن الكثير من حالات الاستخدام التجريبي للاكتشاف في وضع عدم النشاط تكون متعلقة بالإشعارات، على سبيل المثال، لإرسال إشعار إلى الجهاز الذي يستخدمه المستخدم حاليًا بشكل نشط.

بعد التأكد من منح إذن الإشعارات، أنشئ حينئذٍ مثيلاً كاشف الخمول. أسجّل أداة معالجة حدث تنتبه إلى التغييرات غير النشطة، والتي تتضمن بيانات المستخدم حالة الشاشة. يمكن أن يكون المستخدم نشطًا أو خاملاً، ويمكن فتح قفل الشاشة أو قفلها. إذا كان المستخدم في وضع عدم النشاط، يتم محو اللوحة. أُعطي أداة رصد عدم النشاط لفترة قصيرة 60 ثانية.

const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
  const userState = idleDetector.userState;
  const screenState = idleDetector.screenState;
  console.log(`Idle change: ${userState}, ${screenState}.`);
  if (userState === 'idle') {
    clearCanvas();
  }
});

await idleDetector.start({
  threshold: 60000,
  signal,
});

وكالعادة، لا أحمّل هذا الرمز إلا عندما يتيح المتصفّح ذلك.

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

في تطبيق Fugu Greetings، يتم محو اللوحة عند ضبط مربّع الاختيار الملف الشخصي المؤقت. ويتم تحديده ويظل المستخدم في وضع الخمول لفترة طويلة جدًا.

تطبيق Fugu Greetings مع لوحة تم محوها بعد عدم نشاط المستخدم لفترة طويلة
عندما يتم وضع علامة في مربّع الاختيار الملف الشخصي المؤقت ويكون المستخدم غير نشِط لفترة طويلة، يتم محو اللوحة.

الخاتمة

أوه، يا لها من رحلة. العديد من واجهات برمجة التطبيقات في نموذج واحد فقط وتذكر أنني لا أجعل المستخدم يدفع تكلفة التنزيل مطلقًا عن ميزة لا يدعمها المتصفح. باستخدام التحسين التدريجي، أتأكّد من تحميل الرمز ذي الصلة فقط. ونظرًا لأن HTTP/2، تكون الطلبات رخيصة، فمن المفترض أن يعمل هذا النمط بشكل جيد مع العديد من التطبيقات بالرغم من أنك قد ترغب في التفكير في أداة لحزم التطبيقات كبيرة الحجم.

لوحة شبكة &quot;أدوات مطوري البرامج في Chrome&quot; لا تعرض سوى طلبات الملفات التي تتضمّن رمزًا يتوافق مع المتصفّح الحالي.
علامة تبويب "شبكة أدوات مطوري البرامج في Chrome" تعرض فقط طلبات الملفات التي تتضمّن رمزًا يتوافق مع المتصفّح الحالي.

قد يبدو التطبيق مختلفًا قليلاً على كل متصفح نظرًا لعدم دعم جميع الأنظمة الأساسية لجميع الميزات، ولكن الوظيفة الأساسية متاحة دائمًا هناك، ويتم تحسينها بشكل تدريجي وفقًا لإمكانيات المتصفّح المحدَّد. تجدر الإشارة إلى أنّ هذه الإمكانيات قد تتغيّر حتى في متصفّح واحد اعتمادًا على ما إذا كان التطبيق قيد التشغيل كتطبيق مثبَّت أو في علامة تبويب متصفّح.

يعمل تطبيق Fugu Greetings على متصفِّح Chrome على نظام التشغيل Android، ويعرض العديد من الميزات المتاحة.
يعمل Fugu Greetings على Chrome على جهاز Android.
يتم تشغيل Fugu Greetings على متصفّح Safari على جهاز الكمبيوتر، ويعرض ميزات أقلّ.
يعمل تطبيق Fugu Greetings على متصفّح Safari على الكمبيوتر المكتبي.
يعمل تطبيق Fugu Greetings على أجهزة الكمبيوتر المكتبي، ويعرض العديد من الميزات المتوفّرة.
يعمل تطبيق Fugu Greetings على Chrome على أجهزة الكمبيوتر المكتبي.

إذا كان يهمّك تطبيق Fugu Greetings، ابحث عنها وتشعبها على GitHub.

Fugu Greetings repo على GitHub.
تطبيق Fugu Greetings على GitHub.

يسعى فريق Chromium جاهدًا إلى جعل العشب أكثر مراعاة للبيئة في ما يتعلق بواجهات برمجة تطبيقات Fugu API المتقدمة. ومن خلال تطبيق التحسين التدريجي على تطوير تطبيقي، أتأكد من أن الجميع يحصل على تجربة أساسية جيدة وقوية، ولكن الأشخاص الذين يستخدمون متصفحات تتوافق مع المزيد من واجهات برمجة تطبيقات النظام الأساسي للويب يحصلون على تجربة أفضل. أنا أتطلع إلى معرفة ما يمكنك فعله بالتحسين التدريجي في تطبيقاتك.

شكر وتقدير

أنا ممتنّ كريستيان ليبل هيمانث HM الذين ساهما في إطلاق ميزة Fugu Greetings تمت مراجعة هذه المقالة بواسطة جو ميدلي و كايس باسك. ساعدني جيك أرتشيبالد في معرفة الموقف باستخدام import() الديناميكي في سياق مشغّل الخدمات.