פיתוח לדפדפנים מודרניים ושיפור הדרגתי כמו בשנת 2003
פורסם: 29 ביוני 2020
במרץ 2003, ניק פינק וסטיב צ'מפיון הדהימו את עולם עיצוב האתרים עם הרעיון של שיפור הדרגתי, אסטרטגיה לעיצוב אתרים שמתמקדת בטעינה של תוכן הליבה של דף האינטרנט קודם, ואז מוסיפה בהדרגה שכבות מורכבות יותר של הצגה ותכונות על גבי התוכן. בשנת 2003, שיפור מתקדם התייחס לשימוש בתכונות CSS מודרניות (באותה תקופה), ב-JavaScript לא פולשני ואפילו רק בגרפיקה וקטורית שניתנת להרחבה. שיפור הדרגתי בשנת 2020 ואילך מתבסס על שימוש ביכולות של דפדפנים מודרניים.
JavaScript מודרני
אם כבר מדברים על JavaScript, מצב התמיכה בדפדפנים בתכונות הליבה העדכניות של ES 2015 JavaScript מצוין. התקן החדש כולל הבטחות, מודולים, מחלקות, תבניות מילוליות, פונקציות חץ, let ו-const, פרמטרים שמוגדרים כברירת מחדל, גנרטורים, הקצאת ערכים למשתנים, rest ו-spread, Map/Set, WeakMap/WeakSet ועוד.
יש תמיכה בכל הפורמטים.
פונקציות אסינכרוניות, תכונה של ES 2017 ואחת מהתכונות האהובות עליי,
אפשר להשתמש בהן
בכל הדפדפנים העיקריים.
מילות המפתח async ו-await מאפשרות לכתוב התנהגות אסינכרונית שמבוססת על הבטחות בסגנון נקי יותר, בלי צורך להגדיר במפורש שרשראות של הבטחות.
ואפילו תוספות לשפת ES 2020 מהזמן האחרון, כמו שרשור אופציונלי ומיזוג של ערכים שהם null או undefined, זכו לתמיכה במהירות רבה. כשמדובר בתכונות הליבה של 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). שם האפליקציה הוא מחווה לפרויקט Fugu 🐡, שנועד להעניק לאינטרנט את כל היכולות של אפליקציות ל-Android, ל-iOS ולמחשב. מידע נוסף על הפרויקט זמין בדף הנחיתה שלו.
Fugu Greetings היא אפליקציית ציור שמאפשרת ליצור כרטיסי ברכה וירטואליים ולשלוח אותם ליקרים לכם. הוא מדגים את מושגי הליבה של PWA. הוא אמין וניתן להפעלה במצב אופליין מלא, כך שאפשר להשתמש בו גם אם אין לכם רשת. אפשר גם להתקין אותה במסך הבית של המכשיר, והיא משתלבת בצורה חלקה עם מערכת ההפעלה כאפליקציה עצמאית.
שיפור הדרגתי
אחרי שהסברנו את זה, הגיע הזמן לדבר על שיפור הדרגתי. במילון המונחים של MDN Web Docs מוגדר המושג כך:
שיפור הדרגתי הוא פילוסופיית עיצוב שמספקת בסיס של תוכן ופונקציונליות חיוניים לכמה שיותר משתמשים, תוך מתן החוויה הטובה ביותר רק למשתמשים בדפדפנים הכי מודרניים שיכולים להריץ את כל הקוד הנדרש.
זיהוי תכונות משמש בדרך כלל כדי לקבוע אם דפדפנים יכולים להתמודד עם פונקציונליות מודרנית יותר, בעוד שרכיבי 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 מאפשר לכם לפתוח וליצור קבצים וספריות, וגם לשנות ולשמור אותם .
אז איך מבצעים זיהוי תכונות של 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, אני טוען את הסקריפטים מדור קודם.
עם זאת, ב-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 בלבד.
שוב מקבלים חזרה את ה-handle של הקובץ, אבל במקום לקבל את הקובץ, הפעם יוצרים זרם שניתן לכתיבה על ידי קריאה ל-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 מופעל כשמשתמש לוחץ על שיתוף המאמר בבלוג שלי. אתם יכולים לשתף קישור למאמר עם חבר באמצעות אפליקציית ההודעות של 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, אם מקישים על הלחצן Share בדפדפן נתמך כמו Chrome ב-Android, נפתח גיליון השיתוף המובנה. לדוגמה, אני יכול לבחור ב-Gmail, ואז ווידג'ט הכתיבה של האימייל יקפוץ עם התמונה המצורפת.
Contact Picker API
עכשיו אני רוצה לדבר על אנשי קשר, כלומר על פנקס הכתובות של המכשיר או על אפליקציה לניהול אנשי קשר. כשכותבים כרטיס ברכה, לא תמיד קל לכתוב נכון את השם של מישהו. לדוגמה, יש לי חבר בשם סרגיי שמעדיף שהשם שלו ייכתב באותיות קיריליות. אני משתמש במקלדת QWERTZ גרמנית ואין לי מושג איך להקליד את השם שלהם. זו בעיה ש-Contact Picker API יכול לפתור. החבר שלי שמור באפליקציית אנשי הקשר בטלפון שלי, ולכן באמצעות Contacts Picker 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, כשאני מקיש על הלחצן Contacts ובוחר את שני החברים הכי טובים שלי, Сергей Михайлович Брин ו-劳伦с·Эдвард·"Ларри"·Пейдж, אפשר לראות שהרשימה של אנשי הקשר מוגבלת להצגת השמות שלהם בלבד, ולא כתובות האימייל שלהם או מידע אחר כמו מספרי הטלפון שלהם. השמות שלהם מצוירים על כרטיס הברכה שלי.
Asynchronous Clipboard API
השלב הבא הוא העתקה והדבקה. אחת הפעולות האהובות עלינו כמפתחי תוכנה היא העתקה והדבקה. ככותב כרטיסי ברכה, לפעמים אני רוצה לעשות את אותו הדבר. לדוגמה, יכול להיות שאני רוצה להדביק תמונה בכרטיס ברכה שאני עובד עליו, או להעתיק את כרטיס הברכה כדי שאוכל להמשיך לערוך אותו ממקום אחר. Async Clipboard API תומך גם בטקסט וגם בתמונות. אני אסביר לך איך הוספתי תמיכה בהעתקה והדבקה לאפליקציית Fugu Greetings.
כדי להעתיק משהו ללוח של המערכת, אני צריך לכתוב בו.
ה-method 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 של המשאבים הזמינים.
אני קורא לשיטה 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 שואלת אותי אם אני רוצה לאפשר לאפליקציה לראות טקסט ותמונות בלוח העריכה.
בסיום, אחרי שמאשרים את ההרשאה, התמונה מודבקת באפליקציה. אפשר גם להשתמש בשיטה ההפוכה. אני רוצה להעתיק כרטיס ברכה ללוח. אחר כך, כשאני פותח את 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');
}
בדוגמה הזו, ציירתי את המספרים אחד עד שבע, באמצעות משיכת עט אחת לכל מספר. המונה של התג בסמל מראה עכשיו שבע.
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, היא מוצגת כמו כל התראה אחרת, אבל כמו שכתבתי קודם, לא נדרש חיבור לרשת.
The Wake Lock API
אני רוצה לכלול גם את Wake Lock API. לפעמים צריך פשוט להסתכל על המסך מספיק זמן עד שמקבלים השראה. הדבר הכי גרוע שיכול לקרות הוא שהמסך יכבה. ה-API של Wake Lock יכול למנוע את זה.
השלב הראשון הוא לקבל חסימת מצב שינה באמצעות navigator.wakelock.request method().
אני מעביר את המחרוזת 'screen' כדי לקבל חסימת מצב שינה במסך.
לאחר מכן, אני מוסיף event listener כדי לקבל הודעה כשה-wake lock משתחרר.
מצב כזה יכול לקרות, לדוגמה, כשמשנים את סטטוס החשיפה של הכרטיסייה.
אם זה קורה, כשהכרטיסייה חוזרת להיות גלויה, אני יכול לקבל מחדש את נעילת ההשכמה.
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. אם מסמנים אותה, המסך נשאר פעיל.
Idle Detection API
לפעמים, גם אם תבהו במסך במשך שעות, לא תצליחו להבין מה לעשות עם כרטיס הברכה. Idle Detection API מאפשר לאפליקציה לזהות את זמן ההמתנה של המשתמש. אם המשתמש לא מבצע פעולה במשך זמן רב מדי, האפליקציה מתאפסת למצב הראשוני ומנקה את אזור הציור. הגישה ל-API הזה מוגבלת על ידי הרשאת ההתראות, כי הרבה תרחישי שימוש בסביבת ייצור של זיהוי מצב לא פעיל קשורים להתראות. לדוגמה, כדי לשלוח התראה רק למכשיר שהמשתמש נמצא בו בפעילות.
אחרי שמוודאים שהרשאת ההתראות ניתנה, יוצרים מופע של גלאי חוסר הפעילות. אני רושם listener לאירועים שמקשיב לשינויים במצב חוסר פעילות, כולל המשתמש ומצב המסך. המשתמש יכול להיות פעיל או לא פעיל, והמסך יכול להיות נעול או לא נעול. אם המשתמש לא פעיל, הלוח נמחק. הגדרתי לגלאי חוסר הפעילות סף של 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, אזור הציור מתנקה כשמסמנים את תיבת הסימון Ephemeral והמשתמש לא פעיל יותר מדי זמן.
סגירה
איזו נסיעה מטורפת. כל כך הרבה ממשקי API באפליקציה לדוגמה אחת. ואל תשכח, אני אף פעם לא מחייב את המשתמש בעלות ההורדה של תכונה שהדפדפן שלו לא תומך בה. באמצעות שיפור הדרגתי, אני מוודא שרק הקוד הרלוונטי נטען. ובגלל שעם HTTP/2, הבקשות זולות, התבנית הזו אמורה לעבוד טוב בהרבה אפליקציות, למרות שאולי כדאי לשקול שימוש בכלי לאיגוד מודולים באפליקציות גדולות מאוד.
יכול להיות שהאפליקציה תיראה קצת שונה בכל דפדפן, כי לא כל הפלטפורמות תומכות בכל התכונות, אבל פונקציונליות הליבה תמיד קיימת – היא משתפרת בהדרגה בהתאם ליכולות של הדפדפן הספציפי. היכולות האלה עשויות להשתנות גם באותו דפדפן, בהתאם לשאלה אם האפליקציה פועלת כאפליקציה מותקנת או בכרטיסיית דפדפן.
אפשר ליצור Fork של Fugu ב-GitHub.
צוות Chromium משקיע מאמצים רבים כדי לשפר את ממשקי ה-API המתקדמים של Fugu. כשמיישמים שיפור הדרגתי בפיתוח האפליקציה, מוודאים שכולם יקבלו חוויית שימוש טובה ויציבה, אבל אנשים שמשתמשים בדפדפנים שתומכים ביותר ממשקי API של פלטפורמת האינטרנט יקבלו חוויה טובה עוד יותר. אשמח לראות מה תעשו עם שיפורים הדרגתיים באפליקציות שלכם.
תודות
אני רוצה להודות לChristian Liebel ול-Hemanth HM שתרמו לפרויקט Fugu Greetings.
המסמך הזה נבדק על ידי Joe Medley וKayce Basques.
Jake Archibald עזר לי להבין את המצב עם import() דינמי בהקשר של service worker.