פיתוח לדפדפנים מודרניים ושיפור הדרגתי כמו שעשינו ב-2003
במרץ 2003, ניק פינק וסטיב צ'מפיון הדהימו את עולם עיצוב האינטרנט עם הרעיון של שיפור הדרגתי – אסטרטגיה לעיצוב אינטרנט שמתמקדת תחילה בטעינה של תוכן הליבה של דף האינטרנט, ולאחר מכן מוסיפים בהדרגה שכבות עשירות יותר של הצגה ותכונות, עם דגש על איכות טכנית גבוהה. בשנת 2003, שיפור הדרגתי התבסס על שימוש בתכונות מודרניות של CSS, ב-JavaScript לא פולשני ואפילו רק ב-Scalable Vector Graphics. שיפור הדרגתי בשנת 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);
}
};
הייצוא של תמונה הוא כמעט זהה, אבל הפעם צריך להעביר למתודה chooseFileSystemEntries()
פרמטר מסוג 'save-file'
.
מכאן מופיעה תיבת דו-שיח לשמירת קובץ.
כשהקובץ פתוח, לא צריך לעשות זאת כי '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, וחלון הווידג'ט של יצירת האימייל יופיע עם התמונה המצורפת.
ממשק ה-API של Contact Picker
בשלב הבא אדבר על אנשי קשר, כלומר על פנקס הכתובות או על אפליקציית ניהול אנשי הקשר של המכשיר. כשכותבים כרטיס ברכה, לא תמיד קל לכתוב נכון את השם של מישהו. לדוגמה, יש לי חבר בשם סרגיי, והוא מעדיף שהשם שלו ייכתב באותיות קיריליות. אני משתמש במקלדת 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, כשמקישים על הלחצן אנשי קשר ובוחרים את שני החברים הכי טובים, Сергей Михайлович Брин ו-劳伦斯·爱德华·"拉里"·佩奇, אפשר לראות שהכלי לבחירת אנשי קשר מוגבל להצגת השמות שלהם בלבד, אבל לא את כתובות האימייל שלהם או מידע אחר כמו מספרי הטלפון שלהם. לאחר מכן השמות שלהם מופיעים בכרטיס הברכה שלי.
ממשק 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 בתור מונה של משיכות עט.
הוספתי event listener שמגדיל את מונה התנועות של העט בכל פעם שהאירוע 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, תוך שימוש בתנועת עט אחת לכל מספר. מונה התגים שמופיע על הסמל הוא עכשיו שבע.
Periodic Background Sync API
רוצים להתחיל כל יום מחדש עם משהו חדש? תכונה מגניבה באפליקציית Fugu Greetings היא שהיא יכולה להעניק לכם השראה בכל בוקר עם תמונה חדשה לרקע של כרטיס האיחול. האפליקציה משתמשת ב-Periodic Background Sync API כדי לעשות זאת.
השלב הראשון הוא register של אירוע סנכרון תקופתי ברישום של קובץ השירות (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, היא מוצגת כמו כל התראה אחרת, אבל כפי שכתבתי קודם, לא נדרש חיבור לרשת.
ממשק ה-API של 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);
כן, זוהי שיפור הדרגתי, ולכן צריך לטעון אותו רק כשהדפדפן תומך ב-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. כשאני משתמש בשיפור הדרגתי בפיתוח האפליקציה, אני מוודא שכולם מקבלים חוויית שימוש בסיסית טובה, אבל שאנשים שמשתמשים בדפדפנים שתומכים ב-APIs נוספים של פלטפורמות אינטרנט מקבלים חוויה טובה עוד יותר. אני מחכה לראות איך תשתמשו בשיפור הדרגתי באפליקציות שלכם.
תודות
אני רוצה להודות לכריסטיאן ליבל ולHemanth HM שתרמו לברכות Fugu.
המאמר הזה נבדק על ידי ג'ו מדלי וקייס בסקי.
Jake Archibald עזר לי להבין את המצב של import()
דינמי בהקשר של שירות עובד.