יכולות חדשות ועתידיות של PWA: ב-Fugu With Love

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

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

בשיעור הקוד הזה, תתחילו PWA בסיסית. לאחר מכן, תוכלו לגלות יכולות דפדפן חדשות שיספקו בסופו של דבר את היכולות המתקדמות של PWA 🦸.

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

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

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

מה תפתחו

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

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

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

מה תצטרך להכין

נכון לעכשיו, דפדפנים נתמכים באופן מלא:

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

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

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

חלק מממשקי ה-API האלה חזקים מאוד, ואם הטיפול בהם שגוי, לפעמים יש שגיאות. בדיוק כמו דג פוגו 🐡: זה טעים

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

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

3. כדי להתחיל:

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

  • #enable-experimental-web-platform-features

לאחר הפעלת האפשרות, יש להפעיל מחדש את הדפדפן.

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

ברכת "פוגו ברכות" את פסל ה-PWA הראשי, שעליו כתוב ציור גדול על המילה “Google”

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

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

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

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

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

תאימות API שרשומה במסוף Console בכלי פיתוח.

4. 🐟 הוספת תמיכה בממשק ה-API של שיתוף באינטרנט

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

API של שיתוף באינטרנט תומך בשיתוף קבצים, וכפי שאפשר לזכור, File הוא סוג ספציפי של Blob. לכן, בקובץ שנקרא share.mjs, מייבאים את לחצן השיתוף ופונקציית נוחות toBlob() שממירה את התוכן של אזור העריכה ל-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(), שמאפשרת לצייר blob באזור העריכה.

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. 🐟 הוספת תמיכה בממשק ה-API לבורר אנשי קשר

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

במכשיר Android או iOS, ממשק ה-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

ייתכן שהמשתמשים שלך ירצו להדביק תמונה מאפליקציה אחרת באפליקציה, או להעתיק שרטוט מהאפליקציה שלך לאפליקציה אחרת. יש להוסיף תכונה שמאפשרת למשתמשים להעתיק ולהדביק תמונות מתוך האפליקציה. 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. 🐟 הוספת תמיכה בממשק ה-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

לפעמים יכול להיות שהמשתמשים שלכם יצטרכו להקדיש כמה רגעים כדי להביט בשרטוט, מספיק זמן כדי לקבל את ההשראה. הוסיפו תכונה ששומרת על מסך לא פעיל ומונעת את שומר המסך להיטען. 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. 🐟 הוספת תמיכה תקופתית בסנכרון רקע באמצעות API

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

לשם כך יש צורך בשני קבצים, קובץ 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. 🐟 הוספת תמיכה בנושא זיהוי צורות של API

לפעמים המשתמשים שלך' שרטוטים או תמונות הרקע המשומשות עשויים להכיל מידע שימושי כמו למשל ברקודים. צורת הזיהוי של API ובמיוחד ה-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. 🐡 הוספה של תמיכה ב-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. 🐡 הוספת תמיכה ב-API לטיפול בקבצים

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

עליך לרשום את ה-PWA כ-handler של תמונות. פעולה זו מתרחשת במניפסט של אפליקציית האינטרנט, הקטע הבא בקובץ 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 מלהיבים של דפדפן מתפתחים בהקשר של Project Fugu 🐡 שלא ניתן לשרוד את מעבדת הקוד הזו על פני השטח.

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

דף הנחיתה של הקטע &ldquo;Capabilities&rdquo; של האתר web.dev.

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

אתר מעקב של Fugu API

Codelab זה נכתב על ידי תומאס סטינר (@tomayac), אני רוצה לענות על השאלות שלך ואשמח לקרוא את המשוב! תודה מיוחדת ל-Hmanth H.M (@GNUmanth), כריסטיאן ליבל (@christianliebel), Sven במאי (@Svenmay), לארס קנודסן (@larsgk) וג'קי האן (@hanguokai) שעזרו לנו לעצב את מעבדת הקוד הזו.