תכונות חדשות וקרובות בדפדפן ל-PWA: From Fugu With Love

1. לפני שמתחילים

אפליקציות אינטרנט מתקדמות (PWA) הן סוג של תוכנת אפליקציה שמופצת דרך האינטרנט, ונבנית באמצעות טכנולוגיות אינטרנט נפוצות, כולל HTML, ‏ CSS ו-JavaScript. הם מיועדים לפעול בכל פלטפורמה שמשתמשת בדפדפן שתואם לתקנים.

ב-codelab הזה תתחילו עם PWA בסיסית, ואז תגלו יכולות חדשות של הדפדפן שבסופו של דבר יתנו ל-PWA שלכם כוחות על 🦸.

חלק גדול מהיכולות החדשות האלה של הדפדפן נמצאות בתהליך פיתוח ועדיין לא עברו סטנדרטיזציה, ולכן לפעמים תצטרכו להגדיר דגלים בדפדפן כדי להשתמש בהן.

דרישות מוקדמות

כדי להשתתף בסדנת הקוד הזו, אתם צריכים להכיר את JavaScript מודרני, ובמיוחד את המושגים Promises ו-async/await. לא כל השלבים ב-codelab נתמכים בכל הפלטפורמות, ולכן כדאי לבצע את הבדיקה עם מכשירים נוספים, למשל טלפון Android או מחשב נייד עם מערכת הפעלה שונה מזו של המכשיר שבו עורכים את הקוד. כחלופה למכשירים אמיתיים, אפשר לנסות להשתמש בסימולטורים כמו סימולטור Android או בשירותים אונליין כמו BrowserStack, שמאפשרים לבצע בדיקות מהמכשיר הנוכחי. אפשר גם לדלג על כל שלב, כי הם לא תלויים אחד בשני.

מה תפַתחו

תבנו אפליקציית אינטרנט של כרטיסי ברכה ותלמדו איך יכולות חדשות ועתידיות של דפדפנים יכולות לשפר את האפליקציה שלכם, כך שהיא תספק חוויה מתקדמת בדפדפנים מסוימים (אבל תישאר שימושית בכל הדפדפנים המודרניים).

תלמדו איך להוסיף יכולות תמיכה, כמו גישה למערכת הקבצים, גישה ללוח ההעתקה של המערכת, אחזור אנשי קשר, סנכרון תקופתי ברקע, נעילת המסך במצב פעיל, תכונות שיתוף ועוד.

אחרי שתעברו על ה-codelab, תהיה לכם הבנה טובה של האופן שבו אפשר לשפר בהדרגה את אפליקציות האינטרנט באמצעות תכונות חדשות של הדפדפן, בלי להעמיס על קבוצת המשנה של המשתמשים שמשתמשים בדפדפנים לא תואמים, וחשוב מכל, בלי למנוע מהם להשתמש באפליקציה מלכתחילה.

מה נדרש

הדפדפנים הנתמכים באופן מלא כרגע הם:

  • Chrome
  • וגם ב-Edge שמבוסס על Chromium

מומלץ להשתמש בערוץ הפיתוח הספציפי.

2. פרויקט פוגו

אפליקציות מסוג Progressive Web App (PWA) מבוססות על ממשקי API מודרניים ומשופרות באמצעותם, כדי לספק יכולות משופרות, אמינות וקלות התקנה, תוך הגעה לכל משתמש באינטרנט, בכל מקום בעולם, בכל סוג של מכשיר.

חלק מממשקי ה-API האלה הם חזקים מאוד, ואם לא משתמשים בהם בצורה נכונה, עלולות לקרות בעיות. בדומה לדג הפוגו 🐡: אם חותכים אותו נכון, הוא מעדן, אבל אם חותכים אותו לא נכון, הוא יכול להיות קטלני (אבל אל דאגה, שום דבר לא יכול להתקלקל באמת ב-codelab הזה).

לכן שם הקוד הפנימי של פרויקט Web Capabilities (שבו החברות המעורבות מפתחות את ממשקי ה-API החדשים האלה) הוא Project Fugu.

יכולות האינטרנט מאפשרות כבר היום לעסקים גדולים וקטנים להסתמך על פתרונות מבוססי-דפדפן בלבד, ובמקרים רבים גם ליהנות מפריסה מהירה יותר ועלויות פיתוח נמוכות יותר בהשוואה לשימוש בפתרונות ספציפיים לפלטפורמה.

3. שנתחיל?

מורידים את אחד מהדפדפנים ומגדירים את דגל זמן הריצה הבא 🚩 על ידי מעבר אל about://flags, שפועל גם ב-Chrome וגם ב-Edge:

  • #enable-experimental-web-platform-features

אחרי שמפעילים את ההגדרה, צריך להפעיל מחדש את הדפדפן.

תשתמשו בפלטפורמה Glitch, כי היא מאפשרת לארח את ה-PWA ויש לה עורך סביר. בנוסף, Glitch תומך בייבוא ובייצוא אל GitHub, כך שאין תלות בספקים. כדי לנסות את האפליקציה, עוברים אל fugu-paint.glitch.me. זו אפליקציית ציור בסיסית 🎨 שתשפרו במהלך ה-codelab.

אפליקציית PWA בסיסית של Fugu Greetings עם קנבס גדול שבו מצוירת המילה Google.

אחרי שמתנסים באפליקציה, אפשר ליצור רמיקס של האפליקציה כדי ליצור עותק משלכם שתוכלו לערוך. כתובת ה-URL של הרמיקס תיראה בערך כך: glitch.com/edit/#!/bouncy-candytuft (המחרוזת bouncy-candytuft תהיה שונה אצלכם). אפשר לגשת לרימיקס הזה ישירות בכל העולם. כדי לשמור את העבודה, צריך להיכנס לחשבון קיים או ליצור חשבון חדש ב-Glitch. כדי לראות את האפליקציה, לוחצים על הלחצן '🕶 Show'. כתובת ה-URL של האפליקציה המתארחת תהיה משהו כמו bouncy-candytuft.glitch.me (שימו לב ל-.me במקום ל-.com כדומיין ברמה העליונה).

עכשיו אפשר לערוך את האפליקציה ולשפר אותה. בכל פעם שתבצעו שינויים, האפליקציה תיטען מחדש והשינויים יופיעו באופן ישיר.

‫Glitch IDE שבו מוצגת עריכה של מסמך HTML.

מומלץ לבצע את המשימות הבאות לפי הסדר, אבל כמו שצוין למעלה, תמיד אפשר לדלג על שלב אם אין לכם גישה למכשיר תואם. חשוב לזכור שכל משימה מסומנת בסמל של דג לא מזיק ממים מתוקים (🐟) או בסמל של דג פוגו (🐡) שצריך לטפל בו בזהירות. הסמלים האלה מציינים עד כמה התכונה ניסיונית.

כדי לבדוק אם ממשק API נתמך במכשיר הנוכחי, בודקים את ה-Console ב-DevTools. אנחנו משתמשים גם ב-Glitch כדי שתוכלו לבדוק בקלות את אותה אפליקציה במכשירים שונים, למשל בטלפון הנייד ובמחשב.

תאימות ה-API מתועדת במסוף בכלי הפיתוח.

4. 🐟 הוספת תמיכה ב-Web Share API

ליצור ציורים מדהימים זה משעמם אם אין מי שיעריך אותם. להוסיף תכונה שמאפשרת למשתמשים לשתף את הציורים שלהם עם העולם, בצורה של כרטיסי ברכה.

Web Share API תומך בשיתוף קבצים, וכפי שאתם בוודאי זוכרים, File הוא רק סוג ספציפי של Blob. לכן, בקובץ שנקרא share.mjs, מייבאים את לחצן השיתוף ואת פונקציית הנוחות toBlob() שממירה את התוכן של Canvas ל-Blob ומוסיפים את פונקציית השיתוף בהתאם לקוד שבהמשך.

אם הטמעתם את הלחצן הזה אבל הוא לא מופיע, זה קורה כי הדפדפן שלכם לא מטמיע את Web Share API.

import { shareButton, toBlob } from './script.mjs';

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);
  }
};

shareButton.style.display = 'block';
shareButton.addEventListener('click', async () => {
  return share('Fugu Greetings', 'From Fugu With Love', await toBlob());
});

5. 🐟 הוספת תמיכה ב-Web Share Target API

עכשיו המשתמשים יכולים לשתף כרטיסי ברכה שנוצרו באמצעות האפליקציה, אבל אתם יכולים גם לאפשר למשתמשים לשתף תמונות באפליקציה ולהפוך אותן לכרטיסי ברכה. לשם כך אפשר להשתמש ב-Web Share Target API.

בקובץ המניפסט של אפליקציית האינטרנט, צריך לציין לאפליקציה אילו סוגי קבצים היא יכולה לקבל ואיזו כתובת URL הדפדפן צריך להפעיל כשמשתפים קובץ אחד או יותר. קטע הקוד הבא מהקובץ manifest.webmanifest ממחיש זאת.

{
  "share_target": {
    "action": "./share-target/",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "files": [
        {
          "name": "image",
          "accept": ["image/jpeg", "image/png", "image/webp", "image/gif"]
        }
      ]
    }
  }
}

לאחר מכן, ה-service worker מטפל בקבצים שהתקבלו. כתובת ה-URL ./share-target/ לא קיימת בפועל, האפליקציה רק פועלת עליה ב-handler fetch ומפנה את הבקשה לכתובת ה-URL הבסיסית על ידי הוספת פרמטר שאילתה ?share-target:

self.addEventListener('fetch', (fetchEvent) => {
  /* 🐡 Start Web Share Target */
  if (
    fetchEvent.request.url.endsWith('/share-target/') &&
    fetchEvent.request.method === 'POST'
  ) {
    return fetchEvent.respondWith(
      (async () => {
        const formData = await fetchEvent.request.formData();
        const image = formData.get('image');
        const keys = await caches.keys();
        const mediaCache = await caches.open(
          keys.filter((key) => key.startsWith('media'))[0],
        );
        await mediaCache.put('shared-image', new Response(image));
        return Response.redirect('./?share-target', 303);
      })(),
    );
  }
  /* 🐡 End Web Share Target */

  /* ... */
});

כשהאפליקציה נטענת, היא בודקת אם פרמטר השאילתה הזה מוגדר, ואם כן, היא מציירת את התמונה המשותפת על אזור הציור ומוחקת אותה מהמטמון. כל זה קורה ב-script.mjs:

const restoreImageFromShare = async () => {
  const mediaCache = await getMediaCache();
  const image = await mediaCache.match('shared-image');
  if (image) {
    const blob = await image.blob();
    await drawBlob(blob);
    await mediaCache.delete('shared-image');
  }
};

הפונקציה הזו משמשת לאתחול האפליקציה.

if (location.search.includes('share-target')) {
  restoreImageFromShare();
} else {
  drawDefaultImage();
}

6. 🐟 הוספת תמיכה בייבוא תמונות

קשה לצייר הכול מאפס. מוסיפים תכונה שמאפשרת למשתמשים להעלות תמונה מקומית מהמכשיר שלהם לאפליקציה.

קודם כל, כדאי לקרוא על הפונקציה drawImage() של אזור הציור. בשלב הבא, כדאי להכיר את הרכיב <​input
type=file>
.

אחרי שתקראו את המידע הזה, תוכלו לערוך את הקובץ שנקרא import_image_legacy.mjs ולהוסיף את קטע הקוד הבא. בחלק העליון של הקובץ שמייבאים, מופיעים לחצן הייבוא ופונקציית נוחות drawBlob() שמאפשרת לצייר כתם על אזור הציור.

import { importButton, drawBlob } from './script.mjs';

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

importButton.style.display = 'block';
importButton.addEventListener('click', async () => {
  const file = await importImage();
  if (file) {
    await drawBlob(file);
  }
});

7. 🐟 הוספת תמיכה בייצוא תמונות

איך המשתמש ישמור במכשיר שלו קובץ שנוצר באפליקציה? באופן מסורתי, ההגדרה הזו מתבצעת באמצעות רכיב <​a
download>
.

בקובץ export_image_legacy.mjs מוסיפים את התוכן כמו שמופיע בהמשך. מייבאים את לחצן הייצוא ופונקציית נוחות toBlob() שממירה את התוכן של אזור הציור ל-blob.

import { exportButton, toBlob } from './script.mjs';

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

exportButton.style.display = 'block';
exportButton.addEventListener('click', async () => {
  exportImage(await toBlob());
});

8. 🐟 הוספת תמיכה ב-File System Access API

שיתוף הוא חשוב, אבל סביר להניח שהמשתמשים ירצו לשמור את העבודה הכי טובה שלהם במכשירים שלהם. הוספת תכונה שמאפשרת למשתמשים לשמור את הציורים שלהם (ולפתוח אותם מחדש).

בעבר השתמשתם בגישה מדור קודם לייבוא קבצים ובגישה מדור קודם לייצוא קבצים.<​input type=file><​a download> עכשיו נשתמש ב-File System Access API כדי לשפר את חוויית השימוש.

ה-API הזה מאפשר לפתוח ולשמור קבצים ממערכת הקבצים של מערכת ההפעלה. עורכים את שני הקבצים, import_image.mjs ו-export_image.mjs, ומוסיפים את התוכן שבהמשך. כדי שהקבצים האלה ייטענו, צריך להסיר את האימוג'י 🐡 מהקובץ script.mjs.

מחליפים את השורה הזו:

// Remove all the emojis for this feature test to succeed.
if ('show🐡Open🐡File🐡Picker' in window) {
  /* ... */
}

…עם השורה הזו:

if ('showOpenFilePicker' in window) {
  /* ... */
}

ב-import_image.mjs:

import { importButton, drawBlob } from './script.mjs';

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

importButton.style.display = 'block';
importButton.addEventListener('click', async () => {
  const file = await importImage();
  if (file) {
    await drawBlob(file);
  }
});

ב-export_image.mjs:

import { exportButton, toBlob } from './script.mjs';

const exportImage = async () => {
  try {
    const handle = await window.showSaveFilePicker({
      suggestedName: 'fugu-greetings.png',
      types: [
        {
          description: 'Image file',
          accept: {
            'image/png': ['.png'],
          },
        },
      ],
    });
    const blob = await toBlob();
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

exportButton.style.display = 'block';
exportButton.addEventListener('click', async () => {
  await exportImage();
});

9. 🐟 Add Contacts Picker API Support

יכול להיות שהמשתמשים ירצו להוסיף הודעה לכרטיס הברכה ולפנות למישהו באופן אישי. להוסיף תכונה שמאפשרת למשתמשים לבחור איש קשר אחד (או יותר) מהרשימה המקומית ולהוסיף את השם שלו להודעת השיתוף.

במכשיר Android או iOS, Contact Picker API מאפשר לכם לבחור אנשי קשר מאפליקציית ניהול אנשי הקשר של המכשיר ולהחזיר אותם לאפליקציה. עורכים את הקובץ contacts.mjs ומוסיפים את הקוד שבהמשך.

import { contactsButton, ctx, canvas } from './script.mjs';

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);
  }
};

contactsButton.style.display = 'block';
contactsButton.addEventListener('click', async () => {
  const contacts = await getContacts();
  if (contacts) {
    ctx.font = '1em Comic Sans MS';
    contacts.forEach((contact, index) => {
      ctx.fillText(contact.name.join(), 20, 16 * ++index, canvas.width);
    });
  }
});

10. ‫🐟 הוספת תמיכה ב-Async Clipboard API

יכול להיות שהמשתמשים ירצו להדביק תמונה מאפליקציה אחרת באפליקציה שלכם, או להעתיק ציור מהאפליקציה שלכם לאפליקציה אחרת. כדאי להוסיף תכונה שמאפשרת למשתמשים להעתיק ולהדביק תמונות באפליקציה ומחוצה לה. Async Clipboard API תומך בתמונות PNG, כך שעכשיו אפשר לקרוא ולכתוב נתוני תמונות בלוח העריכה.

מאתרים את הקובץ clipboard.mjs ומוסיפים את הטקסט הבא:

import { copyButton, pasteButton, toBlob, drawImage } from './script.mjs';

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

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);
  }
};

copyButton.style.display = 'block';
copyButton.addEventListener('click', async () => {
  await copy(await toBlob());
});

pasteButton.style.display = 'block';
pasteButton.addEventListener('click', async () => {
  const image = new Image();
  image.addEventListener('load', () => {
    drawImage(image);
  });
  image.src = URL.createObjectURL(await paste());
});

11. 🐟 הוספת תמיכה ב-Badging API

כשהמשתמשים מתקינים את האפליקציה, מופיע סמל במסך הבית שלהם. אפשר להשתמש בסמל הזה כדי להעביר מידע משעשע, כמו מספר המשיכות של המכחול בציור מסוים.

הוספת תכונה שסופרת את התג בכל פעם שהמשתמש יוצר משיכת מכחול חדשה. Badging API מאפשר להגדיר תג מספרי בסמל האפליקציה. אפשר לעדכן את התג בכל פעם שמתרחש אירוע pointerdown (כלומר, כשמציירים משהו), ולאפס את התג כשמנקים את לוח הציור.

מכניסים את הקוד הבא לקובץ badge.mjs:

import { canvas, clearButton } from './script.mjs';

let strokes = 0;

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

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

12. 🐟 הוספת תמיכה ב-Screen Wake Lock API

לפעמים, המשתמשים צריכים כמה רגעים כדי להתבונן בציור, עד שההשראה מגיעה. הוספת תכונה שמונעת את כיבוי המסך ומפסיקה את הפעלת שומר המסך. Screen Wake Lock API מונע ממסך המשתמש להיכנס למצב שינה. הנעילה למניעת שינה משתחררת אוטומטית כשמתרחש אירוע של שינוי בחשיפה, כפי שמוגדר בחשיפת הדף. לכן, צריך להפעיל מחדש את נעילת ההשכמה כשהדף חוזר לתצוגה.

מאתרים את הקובץ wake_lock.mjs ומוסיפים את התוכן שבהמשך. כדי לבדוק אם זה עובד, מגדירים את שומר המסך כך שיוצג אחרי דקה אחת.

import { wakeLockInput, wakeLockLabel } from './script.mjs';

let wakeLock = null;

const requestWakeLock = async () => {
  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => {
      console.log('Wake Lock was released');
    });
    console.log('Wake Lock is active');
  } catch (err) {
    console.error(err.name, err.message);
  }
};

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

document.addEventListener('visibilitychange', handleVisibilityChange);

wakeLockInput.style.display = 'block';
wakeLockLabel.style.display = 'block';
wakeLockInput.addEventListener('change', async () => {
  if (wakeLockInput.checked) {
    await requestWakeLock();
  } else {
    wakeLock.release();
  }
});

13. 🐟 הוספת תמיכה ב-Periodic Background Sync API

לפעמים משעמם להתחיל עם קנבס ריק. אתם יכולים להשתמש ב-Periodic Background Sync API כדי לאתחל את בד הציור של המשתמשים עם תמונה חדשה בכל יום, למשל תמונת הפוגו היומית של Unsplash.

לשם כך צריך שני קבצים: קובץ periodic_background_sync.mjs שרושם את הסנכרון התקופתי ברקע, וקובץ image_of_the_day.mjs שמטפל בהורדה של תמונת היום.

ב-periodic_background_sync.mjs:

import { periodicBackgroundSyncButton, drawBlob } from './script.mjs';

const getPermission = async () => {
  const status = await navigator.permissions.query({
    name: 'periodic-background-sync',
  });
  return status.state === 'granted';
};

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);
  }
};

navigator.serviceWorker.addEventListener('message', async (event) => {
  const fakeURL = event.data.image;
  const mediaCache = await getMediaCache();
  const response = await mediaCache.match(fakeURL);
  drawBlob(await response.blob());
});

const getMediaCache = async () => {
  const keys = await caches.keys();
  return await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
};

periodicBackgroundSyncButton.style.display = 'block';
periodicBackgroundSyncButton.addEventListener('click', async () => {
  if (await getPermission()) {
    await registerPeriodicBackgroundSync();
  }
  const mediaCache = await getMediaCache();
  let blob = await mediaCache.match('./assets/background.jpg');
  if (!blob) {
    blob = await mediaCache.match('./assets/fugu_greeting_card.jpg');
  }
  drawBlob(await blob.blob());
});

ב-image_of_the_day.mjs:

const getImageOfTheDay = async () => {
  try {
    const fishes = ['blowfish', 'pufferfish', 'fugu'];
    const fish = fishes[Math.floor(fishes.length * Math.random())];
    const response = await fetch(`https://source.unsplash.com/daily?${fish}`);
    if (!response.ok) {
      throw new Error('Response was', response.status, response.statusText);
    }
    return await response.blob();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const getMediaCache = async () => {
  const keys = await caches.keys();
  return await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
};

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        try {
          const blob = await getImageOfTheDay();
          const mediaCache = await getMediaCache();
          const fakeURL = './assets/background.jpg';
          await mediaCache.put(fakeURL, new Response(blob));
          const clients = await self.clients.matchAll();
          clients.forEach((client) => {
            client.postMessage({
              image: fakeURL,
            });
          });
        } catch (err) {
          console.error(err.name, err.message);
        }
      })(),
    );
  }
});

14. 🐟 הוספת תמיכה ב-Shape Detection API

לפעמים הציורים של המשתמשים או תמונות הרקע שבהם הם משתמשים עשויים להכיל מידע שימושי, כמו ברקודים. אפשר לחלץ את המידע הזה באמצעות Shape Detection API, ובאופן ספציפי, באמצעות Barcode Detection API. הוספת תכונה שמנסה לזהות ברקודים בציורים של המשתמשים. מאתרים את הקובץ barcode.mjs ומוסיפים את התוכן שבהמשך. כדי לבדוק את התכונה הזו, פשוט טוענים או מדביקים תמונה עם ברקוד על הלוח. אפשר להעתיק ברקוד לדוגמה מחיפוש תמונות של קודי QR.

/* global BarcodeDetector */
import {
  scanButton,
  clearButton,
  canvas,
  ctx,
  CANVAS_BACKGROUND,
  CANVAS_COLOR,
  floor,
} from './script.mjs';

const barcodeDetector = new BarcodeDetector();

const detectBarcodes = async (canvas) => {
  return await barcodeDetector.detect(canvas);
};

scanButton.style.display = 'block';
let seenBarcodes = [];
clearButton.addEventListener('click', () => {
  seenBarcodes = [];
});
scanButton.addEventListener('click', async () => {
  const barcodes = await detectBarcodes(canvas);
  if (barcodes.length) {
    barcodes.forEach((barcode) => {
      const rawValue = barcode.rawValue;
      if (seenBarcodes.includes(rawValue)) {
        return;
      }
      seenBarcodes.push(rawValue);
      ctx.font = '1em Comic Sans MS';
      ctx.textAlign = 'center';
      ctx.fillStyle = CANVAS_BACKGROUND;
      const boundingBox = barcode.boundingBox;
      const left = boundingBox.left;
      const top = boundingBox.top;
      const height = boundingBox.height;
      const oneThirdHeight = floor(height / 3);
      const width = boundingBox.width;
      ctx.fillRect(left, top + oneThirdHeight, width, oneThirdHeight);
      ctx.fillStyle = CANVAS_COLOR;
      ctx.fillText(
        rawValue,
        left + floor(width / 2),
        top + floor(height / 2),
        width,
      );
    });
  }
});

15. 🐡 הוספת תמיכה ב-Idle Detection API

אם נניח שהאפליקציה שלכם פועלת בהגדרה שדומה לדוכן מידע, תכונה שימושית תהיה איפוס של אזור הציור אחרי פרק זמן מסוים של חוסר פעילות. Idle Detection API מאפשר לזהות מתי משתמש מפסיק לקיים אינטראקציה עם המכשיר שלו.

מוצאים את הקובץ idle_detection.mjs ומדביקים בו את התוכן שבהמשך.

import { ephemeralInput, ephemeralLabel, clearCanvas } from './script.mjs';

let controller;

ephemeralInput.style.display = 'block';
ephemeralLabel.style.display = 'block';

ephemeralInput.addEventListener('change', async () => {
  if (ephemeralInput.checked) {
    const state = await IdleDetector.requestPermission();
    if (state !== 'granted') {
      ephemeralInput.checked = false;
      return alert('Idle detection permission must be granted!');
    }
    try {
      controller = new AbortController();
      const idleDetector = new IdleDetector();
      idleDetector.addEventListener('change', (e) => {
        const { userState, screenState } = e.target;
        console.log(`idle change: ${userState}, ${screenState}`);
        if (userState === 'idle') {
          clearCanvas();
        }
      });
      idleDetector.start({
        threshold: 60000,
        signal: controller.signal,
      });
    } catch (err) {
      console.error(err.name, err.message);
    }
  } else {
    console.log('Idle detection stopped.');
    controller.abort();
  }
});

16. 🐡 הוספת תמיכה ב-File Handling API

מה אם המשתמשים יוכלו פשוט ללחוץ לחיצה כפולה על קובץ תמונה והאפליקציה שלכם תופיע? File Handling API מאפשר לכם לעשות בדיוק את זה.

צריך לרשום את ה-PWA ככלי לטיפול בקבצים של תמונות. זה קורה בקובץ המניפסט של אפליקציית האינטרנט. קטע הקוד הבא של הקובץ manifest.webmanifest מראה את זה. (היא כבר חלק מקובץ המניפסט, אין צורך להוסיף אותה בעצמכם).

{
  "file_handlers": [
    {
      "action": "./",
      "accept": {
        "image/*": [".jpg", ".jpeg", ".png", ".webp", ".svg"]
      }
    }
  ]
}

כדי לטפל בפועל בקבצים שנפתחו, מוסיפים את הקוד הבא לקובץ file-handling.mjs:

import { drawBlob } from './script.mjs';

const handleLaunchFiles = () => {
  window.launchQueue.setConsumer((launchParams) => {
    if (!launchParams.files.length) {
      return;
    }
    launchParams.files.forEach(async (handle) => {
      const file = await handle.getFile();
      drawBlob(file);
    });
  });
};

handleLaunchFiles();

17. מזל טוב

🎉 יש, הגעת!

במסגרת פרויקט פוגו 🐡 מפתחים כל כך הרבה ממשקי API מעניינים לדפדפן, שאי אפשר להציג את כולם במעבדת הקוד הזו.

כדי לקבל מידע נוסף או להעמיק בנושא, אפשר לעיין בפרסומים שלנו באתר web.dev.

דף הנחיתה של הקטע &#39;יכולות&#39; באתר web.dev.

אבל זה לא נגמר כאן. לגבי עדכונים שעדיין לא פורסמו, אפשר לגשת אל כלי המעקב אחר Fugu API עם קישורים לכל ההצעות שפורסמו, נמצאות בתקופת ניסיון או בתקופת ניסיון למפתחים, לכל ההצעות שהעבודה עליהן התחילה ולכל מה שנמצא בבדיקה, אבל עדיין לא התחיל.

אתר למעקב אחר Fugu API

ה-codelab הזה נכתב על ידי Thomas Steiner (@tomayac). אשמח לענות על השאלות שלכם ולשמוע את המשוב שלכם. תודה מיוחדת ל-Hemanth H.M (@GNUmanth),‏ Christian Liebel (@christianliebel),‏ Sven May (@Svenmay),‏ Lars Knudsen (@larsgk) ו-Jackie Han (@hanguokai) שעזרו לנו ליצור את ה-codelab הזה.