สํารวจความสามารถใหม่ๆ ของเบราว์เซอร์ที่กําลังจะมีขึ้นสําหรับ PWA: จาก Fugu With Love

1. ข้อควรทราบก่อนที่จะเริ่มต้น

Progressive Web Application (PWA) เป็นซอฟต์แวร์แอปพลิเคชันประเภทหนึ่งที่ส่งผ่านเว็บ ซึ่งสร้างขึ้นโดยใช้เทคโนโลยีเว็บทั่วไป ซึ่งรวมถึง HTML, CSS และ JavaScript โดยมีวัตถุประสงค์เพื่อให้ทํางานบนแพลตฟอร์มใดก็ได้ที่ใช้เบราว์เซอร์มาตรฐาน

ใน Codelab นี้ คุณจะต้องเริ่มต้นด้วย PWA พื้นฐานแล้วสํารวจความสามารถใหม่ๆ ของเบราว์เซอร์ที่ทําให้ PWA มีประสิทธิภาพสูงสุด 🦸

ความสามารถใหม่ๆ ของเบราว์เซอร์เหล่านี้กําลังดําเนินการอยู่และยังคงเป็นมาตรฐานอยู่ ดังนั้นในบางครั้งคุณอาจต้องตั้งค่าสถานะเบราว์เซอร์เพื่อใช้งาน

สิ่งที่ต้องมีก่อน

สําหรับ Codelab นี้ คุณควรทําความคุ้นเคยกับ JavaScript ที่ทันสมัย สัญญา และซิงค์ข้อมูล เนื่องจากบางขั้นตอนของ Codelab ไม่ได้รองรับทุกแพลตฟอร์ม การทดสอบจึงอาจมีข้อมูลในกรณีที่คุณมีอุปกรณ์เพิ่มเติม เช่น โทรศัพท์ Android หรือแล็ปท็อปที่ใช้ระบบปฏิบัติการอื่นที่ไม่ใช่อุปกรณ์ที่แก้ไขโค้ด คุณอาจลองใช้เครื่องจําลอง เช่น เครื่องจําลอง Android หรือบริการออนไลน์ เช่น BrowserStack ที่ทดสอบอุปกรณ์ที่มีอยู่ได้แทนอุปกรณ์จริง หากไม่ใช่ เราก็ไม่ต้องดําเนินการใดๆ ทั้งสิ้น และไม่ต้องพึ่งพากันและกัน

สิ่งที่คุณจะสร้าง

คุณจะสร้างแอปบนเว็บสําหรับการ์ดอวยพร และดูวิธีที่ความสามารถใหม่ๆ และเบราว์เซอร์ที่กําลังจะเปิดตัวนั้นช่วยปรับปรุงแอปของคุณได้ เพื่อมอบประสบการณ์การใช้งานขั้นสูงในบางเบราว์เซอร์ (แต่ยังคงมีประโยชน์ในเบราว์เซอร์รุ่นใหม่ทั้งหมด)

คุณจะได้ดูวิธีเพิ่มความสามารถในการรองรับ เช่น การเข้าถึงระบบไฟล์ การเข้าถึงคลิปบอร์ดของระบบ การเรียกข้อมูลรายชื่อติดต่อ การซิงค์ในเบื้องหลังเป็นระยะๆ การล็อกหน้าจอ ฟีเจอร์การแชร์ และอื่นๆ

หลังจากทํางานผ่าน Codelab แล้ว คุณจะเข้าใจลึกซึ้งเกี่ยวกับวิธีปรับปรุงเว็บแอปอย่างต่อเนื่องด้วยฟีเจอร์ใหม่ๆ ของเบราว์เซอร์ ทั้งหมดนี้จะช่วยลดภาระในการดาวน์โหลดของผู้ใช้ที่เบราว์เซอร์อาจใช้ร่วมกันไม่ได้ และที่สําคัญที่สุดคือการยกเว้นแอปเหล่านี้ไม่ให้รวมอยู่ในที่แรก

สิ่งที่ต้องมี

เบราว์เซอร์ที่รองรับโดยสมบูรณ์ในขณะนี้ได้แก่

  • Chrome
  • และEdge ที่ใช้ Chromium

ขอแนะนําให้ใช้เวอร์ชันที่กําลังพัฒนาที่กําหนด

2. โปรเจ็กต์ฟุกุ

Progressive Web App (PWA) สร้างและปรับปรุงด้วย API ที่ทันสมัยเพื่อมอบความสามารถขั้นสูง ความน่าเชื่อถือ และความสามารถในการติดตั้ง รวมถึงเข้าถึงทุกคนในเว็บได้จากทุกที่ ด้วยอุปกรณ์ทุกประเภท

API เหล่านี้บางรายการมีประสิทธิภาพมาก และระบบอาจจัดการหากมีความผิดพลาดบางอย่าง ปลาปักเป้าก็เหมือนกัน 🐡: การแล่เนื้อให้พอดีก็เป็นอาหารอันน่าตื่นตาตื่นใจ แต่ถ้าคุณตัดผิดไป อาจถึงเพียงอันตรายได้ (แต่ไม่ต้องกังวล เพราะไม่มีอะไรจะทําลายใน Codelab นี้ได้จริงๆ)

นี่คือเหตุผลที่ชื่อรหัสภายในของโปรเจ็กต์ความสามารถของเว็บ (ที่บริษัทที่เกี่ยวข้องกําลังพัฒนา API ใหม่เหล่านี้) คือ Project Fugu

ปัจจุบันความสามารถด้านเว็บซึ่งจะช่วยให้องค์กรทั้งขนาดเล็กและใหญ่สร้างโซลูชันแบบเบราว์เซอร์เพียงอย่างเดียวได้ ซึ่งมักทําให้การติดตั้งใช้งานรวดเร็วขึ้นโดยมีต้นทุนในการพัฒนาน้อยกว่าเมื่อเทียบกับการใช้เฉพาะแพลตฟอร์ม

3. เริ่มต้น

ดาวน์โหลดเบราว์เซอร์อย่างใดอย่างหนึ่ง แล้วตั้งค่าสถานะธงต่อไปนี้ 🚩 โดยไปที่ about://flags ซึ่งใช้งานได้ทั้งใน Chrome และ Edge:

  • #enable-experimental-web-platform-features

หลังจากเปิดใช้แล้ว ให้รีสตาร์ทเบราว์เซอร์

โดยต้องใช้แพลตฟอร์ม Glitch เนื่องจากจะช่วยให้คุณโฮสต์ PWA ได้ และเนื่องจากมีตัวแก้ไขที่ดี นอกจากนี้ Glitch ยังรองรับการนําเข้าและส่งออกไปยัง GitHub เลย ดังนั้นจะไม่มีการล็อกผู้ให้บริการ ไปที่ fugu-paint.glitch.me เพื่อลองใช้แอปพลิเคชัน แอปนี้เป็นแอปวาดรูปพื้นฐาน 🎨 ที่ปรับปรุงได้ระหว่าง Codelab

Fugu Greetings เวอร์ชัน PWA พื้นฐานที่มีภาพพิมพ์แคนวาสขนาดใหญ่ที่มีคําว่า "amp"ldquo;Google” วาดบนภาพ

หลังจากเล่นกับแอปพลิเคชันแล้ว ให้รีมิกซ์แอปเพื่อสร้างสําเนาที่คุณแก้ไขได้ URL ของรีมิกซ์จะมีลักษณะดังนี้ glitch.com/editupload/bouncy-candytuft ("bouncy-candytuft" เป็นอย่างอื่นสําหรับคุณ) คุณเข้าถึงรีมิกซ์นี้ได้โดยตรงทั่วโลก ลงชื่อเข้าใช้บัญชีที่มีอยู่หรือสร้างบัญชีใหม่ใน Glitch เพื่อบันทึกงาน คุณดูแอปได้โดยคลิกปุ่ม "🕶 แสดง" และ URL ของแอปที่โฮสต์จะเป็นดังนี้ bouncy-candytuft.glitch.me (โปรดทราบว่า .me แทนที่จะเป็น .com เป็นโดเมนระดับบนสุด)

ตอนนี้คุณก็พร้อมแก้ไขและปรับปรุงแอปแล้ว เมื่อคุณทําการเปลี่ยนแปลง แอปจะโหลดซ้ํา แล้วการเปลี่ยนแปลงจะมองเห็นได้โดยตรง

Glitch IDE ที่แสดงการแก้ไขเอกสาร HTML

วิธีการต่อไปนี้ควรดําเนินการให้เสร็จสมบูรณ์ตามลําดับ แต่ตามที่ระบุไว้ข้างต้น คุณสามารถข้ามขั้นตอนได้ทุกเมื่อหากไม่มีสิทธิ์เข้าถึงอุปกรณ์ที่เข้ากันได้ อย่าลืมว่าแต่ละงานจะมีเครื่องหมาย 🐟 ปลาน้ําจืดที่ไม่เป็นอันตราย หรือ 🐡 ซึ่งเป็นเครื่องหมายคําพูด แล้วดูแลด้วยปลาฟุกุที่แจ้งเตือนการใช้ฟีเจอร์ทดลองหรือไม่

ตรวจสอบคอนโซลใน DevTools เพื่อดูว่ามีการรองรับ API ในอุปกรณ์ปัจจุบันแล้วหรือยัง นอกจากนี้เรายังใช้ 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. 🐟 เพิ่มการรองรับ API เป้าหมายสําหรับการแชร์เว็บ

ตอนนี้ผู้ใช้สามารถแชร์การ์ดอวยพรที่สร้างโดยใช้แอป แต่คุณยังอนุญาตให้ผู้ใช้แชร์รูปภาพในแอปและเปลี่ยนเป็นการ์ดอวยพรได้ โดยคุณอาจใช้ Web Share Target API ได้

ในไฟล์ Manifest ของเว็บแอปพลิเคชัน คุณต้องบอกแอปว่าคุณจะยอมรับไฟล์ประเภทใดได้บ้าง และ URL ใดที่เบราว์เซอร์ควรเรียกใช้เมื่อแชร์ไฟล์อย่างน้อย 1 ไฟล์ ข้อความที่ตัดตอนมาด้านล่างของไฟล์ 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/ไม่มีอยู่จริง แอปเพียงแค่ดําเนินการกับ URL นั้นในเครื่องจัดการ 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. 🐟 เพิ่มการรองรับการนําเข้ารูปภาพ

การวาดทุกอย่างตั้งแต่ต้นนั้นเป็นเรื่องยาก เพิ่มฟีเจอร์ที่อนุญาตให้ผู้ใช้อัปโหลดรูปภาพในเครื่องจากอุปกรณ์ของตนลงในแอป

ก่อนอื่นให้อ่านฟังก์ชัน Canvas' 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 นี้ใช้เพื่อเปิดและบันทึกไฟล์จากระบบไฟล์ของระบบปฏิบัติการ แก้ไขไฟล์ 2 ไฟล์ ได้แก่ 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 สําหรับรายชื่อติดต่อ

ผู้ใช้อาจต้องการเพิ่มข้อความลงในการ์ดอวยพรแล้วสนทนาเป็นรายบุคคล เพิ่มฟีเจอร์ที่อนุญาตให้ผู้ใช้เลือกรายชื่อติดต่อในพื้นที่อย่างน้อย 1 รายการ (หรือหลายคน) แล้วเพิ่มชื่อในข้อความที่แชร์

ในอุปกรณ์ Android หรือ iOS นั้น Contact Selecter 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. 🐟 เพิ่มการรองรับ API ของคลิปบอร์ด Async

ผู้ใช้อาจต้องการวางรูปภาพจากแอปอื่นลงในแอป หรือคัดลอกภาพวาดจากแอปลงในแอปอื่น เพิ่มฟีเจอร์ที่ช่วยให้ผู้ใช้คัดลอกและวางรูปภาพลงในและออกจากแอปได้ 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. 🐟 เพิ่มการสนับสนุน Wake Lock API สําหรับหน้าจอ

ในบางครั้งผู้ใช้อาจต้องใช้เวลาสักครู่ในการจ้องมองไปที่ภาพวาด ซึ่งนานพอที่จะเกิดแรงบันดาลใจได้ เพิ่มฟีเจอร์ที่ทําให้หน้าจอตื่นตัวและหยุดโปรแกรมรักษาหน้าจอไว้ Screen Wake Lock API ป้องกันไม่ให้หน้าจอของผู้ใช้เข้าสู่โหมดสลีป Wake Lock จะปล่อยโดยอัตโนมัติเมื่อเกิดเหตุการณ์การเปลี่ยนแปลงระดับการเข้าถึงตามที่กําหนดไว้ในระดับการเข้าถึงหน้าเว็บ ดังนั้น ระบบจึงต้องเปิดใช้งาน Wake Lock ใหม่เมื่อหน้าเว็บเข้ามาอยู่ในมุมมองอีกครั้ง

ค้นหาไฟล์ wake_lock.mjs แล้วเพิ่มเนื้อหาด้านล่าง หากต้องการทดสอบว่าใช้ได้ไหม ให้กําหนดค่าโปรแกรมรักษาหน้าจอให้แสดงหลังผ่านไป 1 นาที

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. 🐟 เพิ่มการรองรับ Background Sync API เป็นระยะ

การเริ่มต้นด้วยผืนผ้าใบเปล่าอาจน่าเบื่อ คุณสามารถใช้ API การซิงค์พื้นหลังเป็นระยะๆ เพื่อเริ่มผืนผ้าใบของผู้ใช้ใหม่ด้วยรูปภาพใหม่ทุกวัน เช่น รูปภาพปลาปักเป้ารายวัน

การดําเนินการนี้ต้องใช้ 2 ไฟล์ คือไฟล์ 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 ของการตรวจจับรูปร่าง

บางครั้งผู้ใช้&#39 ภาพวาดหรือภาพพื้นหลังที่ใช้อาจมีข้อมูลที่เป็นประโยชน์ เช่น บาร์โค้ด Shape Detection API โดยเฉพาะอย่างยิ่ง Barcode Detection API จะช่วยให้คุณแยกข้อมูลนี้ได้ เพิ่มฟีเจอร์ที่พยายามตรวจหาบาร์โค้ดจากผู้ใช้ของคุณ ##39 ภาพวาด ค้นหาไฟล์ barcode.mjs แล้วเพิ่มเนื้อหาด้านล่าง หากต้องการทดสอบฟีเจอร์นี้ เพียงโหลดหรือวางรูปภาพที่มีบาร์โค้ดบนผืนผ้าใบ คุณคัดลอกบาร์โค้ดตัวอย่างได้จากการค้นหารูปภาพสําหรับคิวอาร์โค้ด

/* 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 ของเว็บแอปพลิเคชัน ซึ่งตัดตอนมาจากไฟล์ manifest.webmanifest ด้านล่าง (นี่เป็นส่วนหนึ่งของไฟล์ Manifest อยู่แล้ว จึงไม่จําเป็นต้องเพิ่มเอง)

{
  "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 🐡 ที่ Codelab นี้อาจถูพื้นผิวต่างๆ ได้ยาก

หากต้องการทราบข้อมูลแบบเจาะลึก หรือดูข้อมูลเพิ่มเติมได้ในเว็บไซต์ web.dev

หน้า Landing Page ของส่วน &ldquo;Capability&rdquo; ของเว็บไซต์ web.dev

แต่สุดท้ายแล้ว สําหรับการอัปเดตที่ยังไม่ได้เผยแพร่ คุณจะเข้าถึงเครื่องมือติดตาม API ของ Fugu ที่มีลิงก์ไปยังข้อเสนอทั้งหมดที่จัดส่ง อยู่ระหว่างการทดลองใช้ต้นทางหรือช่วงทดลองใช้ ข้อเสนอทั้งหมดซึ่งการทํางานได้เริ่มต้นแล้ว และทุกอย่างที่ได้รับการพิจารณาแต่ยังไม่ได้เริ่ม

เว็บไซต์เครื่องมือติดตาม Fugu API

Codelab นี้เขียนโดย Thomas Steiner (@tomayac) ฉันยินดีตอบคําถามของคุณ และหวังว่าจะได้อ่านความคิดเห็นจากคุณ ขอขอบคุณ Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) และ Jackie Han (@hanguokai) ที่ช่วยกําหนดแนวทางของ Codelab นี้