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

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

เกี่ยวกับ Codelab นี้

subjectอัปเดตล่าสุดเมื่อ ต.ค. 13, 2021
account_circleเขียนโดย Thomas Steiner

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 นี้