פיתוח לדפדפנים מודרניים ושיפור הדרגתי כמו שעשינו ב-2003
במרץ 2003, ניק פינק וסטיב צ'מפיון הדהימו את עולם עיצוב האינטרנט עם הרעיון של שיפור הדרגתי – אסטרטגיה לעיצוב אתרים שמתמקדת תחילה בטעינה של תוכן הליבה של דף האינטרנט, ולאחר מכן מוסיפים בהדרגה שכבות עשירות יותר של הצגה ותכונות, עם דגש על איכות טכנית גבוהה. בשנת 2003, שיפור הדרגתי התבסס על שימוש בתכונות מודרניות של CSS, ב-JavaScript לא פולשני ואפילו רק ב-SVG. שיפור הדרגתי בשנת 2020 ואילך הוא שימוש ביכולות מודרניות של דפדפנים.
JavaScript מודרני
בנוגע ל-JavaScript, התמיכה בדפדפנים בתכונות ה-JavaScript העדכניות ביותר של הליבה של ES 2015 מצוינת.
התקן החדש כולל הבטחות (promises), מודולים, כיתות, ליבות תבנית, פונקציות חץ, let
ו-const
, פרמטרים שמוגדרים כברירת מחדל, גנרטורים, הקצאה לניתוח מבנה (destructuring), rest ו-spread, Map
/Set
, WeakMap
/WeakSet
ועוד הרבה.
הכול נתמך.
פונקציות אסינכררוניות (async) הן תכונה של ES 2017 ואחת מהתכונות האהובות עלי, ואפשר להשתמש בהן בכל הדפדפנים העיקריים.
מילות המפתח async
ו-await
מאפשרות לכתוב התנהגות אסינכררונית שמבוססת על הבטחה בסגנון נקי יותר, בלי צורך להגדיר מחרוזות הבטחה באופן מפורש.
אפילו שפות שנוספו לאחרונה ל-ES 2020, כמו שרשרת אופציונלית ושילוב של nullish, קיבלו תמיכה מהר מאוד. בהמשך מופיעה דוגמה לקוד. כשמדובר בתכונות הליבה של JavaScript, המצב מצוין.
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
האפליקציה לדוגמה: Fugu Greetings
במאמר הזה אשתמש ב-PWA פשוט שנקרא Fugu Greetings (GitHub). שם האפליקציה הזה הוא מחווה ל-Project Fugu 🐡, מאמץ לתת לאינטרנט את כל התכונות של אפליקציות ל-Android, ל-iOS ולמחשב. מידע נוסף על הפרויקט זמין בדף הנחיתה שלו.
Fugu Greetings היא אפליקציית ציור שמאפשרת ליצור כרטיסי ברכה וירטואליים ולשלוח אותם לאנשים שאתם אוהבים. הוא מדגים את המושגים המרכזיים של אפליקציות PWA. הוא מהימן ומאפשר שימוש מלא במצב אופליין, כך שתוכלו להשתמש בו גם אם אין לכם חיבור לרשת. אפשר גם להתקין את האפליקציה במסך הבית של המכשיר, והיא משתלבת בצורה חלקה עם מערכת ההפעלה כאפליקציה עצמאית.
שיפור הדרגתי
עכשיו אפשר להמשיך לדבר על שיפור הדרגתי. במילון של MDN Web Docs מוגדר המונח כך:
שיפור הדרגתי הוא פילוסופיית עיצוב שמספקת בסיס של תוכן ופונקציונליות חיוניים לכמה שיותר משתמשים, תוך מתן חוויית השימוש הטובה ביותר רק למשתמשים בדפדפנים המודרניים ביותר שיכולים להריץ את כל הקוד הנדרש.
בדרך כלל משתמשים בזיהוי תכונות כדי לקבוע אם הדפדפנים יכולים לטפל בפונקציונליות מודרנית יותר, ואילו בפוליפילים משתמשים לרוב כדי להוסיף תכונות חסרות באמצעות 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 מאפשר לכם לפתוח וליצור קבצים וספריות, וגם לשנות ולשמור אותם .
אז איך מזהים תכונות ב-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.
עם זאת, ב-Chrome, דפדפן שתומך ב-API, רק הסקריפטים החדשים נטענים.
אפשר לעשות זאת בזכות import()
דינמי, שכל הדפדפנים המודרניים תומכים בו.
כפי שאמרתי קודם, הדשא ירוק בימים האלה.
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()
.
בשלב הבא, כותבים לקובץ את ה-blob, שהוא התמונה של כרטיס האיחול.
בסוף, סוגרים את הסטרימינג לכתיבה.
תמיד יכול להיות משהו שיסתיים בכישל: יכול להיות שאין מספיק מקום בדיסק, יכולה להיות שגיאה בכתיבה או בקריאה, או אולי המשתמש פשוט ביטל את תיבת הדו-שיח של הקובץ.
לכן תמיד צריך להוסיף את הקריאות לתוך משפט 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, אפשר לפתוח קובץ כמו קודם. הקובץ המיובא מצויר ישירות על הלוח. אני יכול לבצע את העריכות ולשמור אותן באמצעות תיבת דו-שיח אמיתית לשמירה, שבה אוכל לבחור את השם ואת מיקום האחסון של הקובץ. עכשיו הקובץ מוכן לשמירה לנצח.
ממשקי ה-API של Web Share ו-Web Share Target
מלבד שמירת התמונה לתמיד, אולי אני רוצה לשתף את כרטיס האיחול שלי. אפשר לעשות זאת באמצעות Web Share API ו-Web Share Target API. מערכות הפעלה לנייד, ולאחרונה גם למחשב, כוללות מנגנוני שיתוף מובנים. לדוגמה, בהמשך מוצגת גיליון השיתוף של Safari למחשב ב-MacOS, שהופעל ממאמר בבלוג שלי. כשאתם לוחצים על הלחצן שיתוף הכתבה, אתם יכולים לשתף קישור לכתבה עם חבר, למשל דרך אפליקציית Messages ב-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.
קודם כול, צריך להכין אובייקט data
עם מערך files
שמכיל blob אחד, ואז 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, וחלון הווידג'ט של יצירת האימייל יופיע עם התמונה המצורפת.
Contact Picker API
בשלב הבא אדבר על אנשי קשר, כלומר על פנקס הכתובות או על אפליקציית ניהול אנשי הקשר של המכשיר. כשכותבים כרטיס ברכה, לא תמיד קל לכתוב נכון את השם של מישהו. לדוגמה, יש לי חבר בשם סרגי, והוא מעדיף שהשם שלו ייכתב באותיות קיריליות. אני משתמש במקלדת QWERTZ גרמנית ואין לי מושג איך להקליד את השם שלו. זו בעיה שאפשר לפתור באמצעות Contact Picker API. מכיוון שהחבר שלי שמור באפליקציית אנשי הקשר בטלפון, באמצעות ה-API של בורר אנשי הקשר, אוכל לגשת לאנשי הקשר שלי מהאינטרנט.
קודם כול, צריך לציין את רשימת הנכסים שרוצים לגשת אליהם.
במקרה הזה, אני רוצה רק את השמות, אבל בתרחישי שימוש אחרים יכול להיות שאתעניין במספרי טלפון, באימיילים, בסמלי דמויות או בכתובות פיזיות.
בשלב הבא, מגדירים אובייקט 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);
}
};
עד עכשיו כבר הבנתם את התבנית: אנחנו טוענים את הקובץ רק כשיש תמיכה בפועל ב-API.
if ('contacts' in navigator) {
import('./contacts.mjs');
}
ב-Fugu Greeting, כשמקישים על הלחצן אנשי קשר ובוחרים את שני החברים הכי טובים, Сергей Михайлович Брин ו-劳伦斯·爱德华·"拉里"·佩奇, אפשר לראות שהכלי לבחירת אנשי קשר מוגבל להצגת השמות שלהם בלבד, אבל לא את כתובות האימייל שלהם או מידע אחר כמו מספרי הטלפון שלהם. לאחר מכן, השמות שלהם מצוירים על כרטיס הברכה שלי.
Asynchronous Clipboard API
בשלב הבא נסביר איך מעתיקים ומדביקים. אחת מהפעולות האהובות עלינו כמפתחי תוכנה היא העתקה והדבקה. ככותב כרטיסי ברכה, לפעמים אוכל לעשות את אותו הדבר. יכול להיות שארצה להדביק תמונה בכרטיס ברכה שאני עובד עליו, או להעתיק את כרטיס הברכה כדי שאוכל להמשיך לערוך אותו במקום אחר. Async Clipboard API תומך גם בטקסט וגם בתמונות. אראה לכם איך הוספתי תמיכה בהעתקה ובהדבקה לאפליקציית Fugu Greetings.
כדי להעתיק משהו ללוח המערכת, צריך לכתוב בו.
השיטה navigator.clipboard.write()
מקבלת מערך של פריטים מהלוח כפרמטר.
כל פריט בלוח העריכה הוא למעשה אובייקט עם blob כערך, וסוג ה-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 של המשאבים הזמינים.
אני קורא ל-method 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');
}
איך זה עובד בפועל? פותחים תמונה באפליקציית Preview ב-macOS ומעתיקים אותה ללוח. כשלחצתי על הדבקה, מוצגת לי בקשה באפליקציית Fugu Greetings אם לאפשר לאפליקציה לראות טקסט ותמונות בלוח העריכה.
בסיום, אחרי אישור ההרשאה, התמונה מועתקת לאפליקציה. אפשר גם להשתמש באפשרות ההפוכה. אעתיק כרטיס ברכה ללוח. כשפותחים את Preview ולוחצים על File ואז על New from Clipboard, כרטיס האיחול מועתק לתמונה חדשה ללא שם.
Badging API
ממשק API שימושי נוסף הוא Badging API.
כ-PWA שניתן להתקנה, לאפליקציית Fugu Greetings יש כמובן סמל אפליקציה שהמשתמשים יכולים למקם במעמד האפליקציות או במסך הבית.
דרך מהנה וקלה להדגים את ה-API היא להשתמש בו (לשימוש לרעה) ב-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');
}
בדוגמה הזו, ציירתי את המספרים מאחד עד שבעה באמצעות משיחת עט אחת לכל מספר. מונה התגים בסמל מגיע עכשיו ל-7.
Periodic Background Sync API
רוצים להתחיל כל יום מחדש עם משהו חדש? תכונה מגניבה באפליקציית Fugu Greetings היא שהיא יכולה להעניק לכם השראה בכל בוקר עם תמונה חדשה לרקע של כרטיס האיחול. האפליקציה משתמשת ב-Periodic Background Sync API כדי לעשות זאת.
השלב הראשון הוא רישום אירוע סנכרון תקופתי ברישום של ה-service worker.
הוא מחכה לתג סנכרון שנקרא '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
ב-service worker.
אם תג האירוע הוא '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,
});
});
})()
);
}
});
שוב, מדובר בשיפור הדרגתי, כך שהקוד נטען רק כשה-API נתמך בדפדפן.
הכלל הזה חל גם על קוד הלקוח וגם על קוד ה-service worker.
בדפדפנים שלא תומכים בכך, אף אחד מהם לא נטען.
שימו לב שבקובץ השירות, במקום 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 Greetings, לחיצה על הלחצן Wallpaper (טפט) חושפת את התמונה של כרטיס האיחול של היום, שמתעדכנת מדי יום באמצעות Periodic Background Sync API.
Notification Triggers API
לפעמים, גם אם יש לכם הרבה השראה, אתם צריכים דחיפה כדי לסיים כרטיס ברכה שכבר התחלתם ליצור. זוהי תכונה שמופעלת על ידי 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, היא מוצגת כמו כל התראה אחרת, אבל כפי שכתבתי קודם, לא נדרש חיבור לרשת.
Wake Lock API
אני רוצה לכלול גם את Wake Lock API. לפעמים צריך רק להביט במסך מספיק זמן עד שההשראה תגיע. במקרה כזה, הדבר הכי גרוע שיכול לקרות הוא שהמסך יכבה. API של Wake Lock יכול למנוע את זה.
השלב הראשון הוא לקבל נעילה מצב שינה באמצעות 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);
כן, זוהי שיפור הדרגתי, ולכן צריך לטעון אותו רק כשהדפדפן תומך ב-API.
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
import('./wake_lock.mjs');
}
ב-Fugu Greetings יש תיבת סימון Insomnia (נדודי שינה). אם מסמנים אותה, המסך לא יכבה.
ה-API לזיהוי מצב חוסר פעילות
לפעמים, גם אם מביטים במסך במשך שעות, זה פשוט לא עוזר ואין לכם מושג מה לעשות עם כרטיס האיחול. Idle Detection API מאפשר לאפליקציה לזהות את זמני ההשבתה של המשתמשים. אם המשתמש לא פעיל במשך זמן רב מדי, האפליקציה מתאפסת למצב הראשוני והקנבס נמחק. נכון לעכשיו, ה-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, הלוח נמחק כשתיבת הסימון זמני מסומנת והמשתמש לא פעיל במשך זמן רב מדי.
סגירה
אוף, איזו נסיעה. כל כך הרבה ממשקי API באפליקציית דוגמה אחת בלבד. חשוב לזכור: אף פעם לא מחייבים את המשתמש בתשלום על הורדה של תכונה שהדפדפן שלו לא תומך בה. באמצעות שיפור הדרגתי, אני מוודא שרק הקוד הרלוונטי נטען. מכיוון שב-HTTP/2, הבקשות זולות, התבנית הזו אמורה לפעול היטב בלא מעט אפליקציות, אבל כדאי לשקול שימוש ב-bundler לאפליקציות גדולות במיוחד.
האפליקציה עשויה להיראות קצת שונה בכל דפדפן, כי לא כל הפלטפורמות תומכות בכל התכונות, אבל הפונקציונליות הבסיסית תמיד קיימת – והיא משתפרת בהדרגה בהתאם ליכולות של הדפדפן הספציפי. חשוב לזכור שהיכולות האלה עשויות להשתנות גם באותו דפדפן, בהתאם לאופן שבו האפליקציה פועלת – כאפליקציה מותקנת או בכרטיסייה בדפדפן.
אם אתם מעוניינים באפליקציה Fugu Greetings, תוכלו למצוא אותה וללפצל אותה ב-GitHub.
צוות Chromium עובד קשה כדי לשפר את ממשקי ה-API המתקדמים של Fugu. כשאני משתמש בשיפור הדרגתי בפיתוח האפליקציה, אני מוודא שכולם מקבלים חוויה בסיסית טובה, אבל אנשים שמשתמשים בדפדפנים שתומכים בממשקי API נוספים של פלטפורמות אינטרנט מקבלים חוויה טובה עוד יותר. אני מחכה לראות איך תשתמשו בשיפור הדרגתי באפליקציות שלכם.
תודות
תודה ל-Christian Liebel ול-Hemanth HM שתרמו ל-Fugu Greetings.
המאמר הזה נבדק על ידי Joe Medley ו-Kayce Basques.
Jake Archibald עזר לי להבין את המצב של import()
דינמי בהקשר של שירות עובד.