1. ข้อควรทราบก่อนที่จะเริ่มต้น
Progressive Web Application (PWA) เป็นซอฟต์แวร์แอปพลิเคชันประเภทหนึ่งที่ส่งผ่านเว็บ ซึ่งสร้างขึ้นโดยใช้เทคโนโลยีเว็บทั่วไป ซึ่งรวมถึง HTML, CSS และ JavaScript โดยมีวัตถุประสงค์เพื่อให้ทํางานบนแพลตฟอร์มใดก็ได้ที่ใช้เบราว์เซอร์มาตรฐาน
ใน Codelab นี้ คุณจะต้องเริ่มต้นด้วย PWA พื้นฐานแล้วสํารวจความสามารถใหม่ๆ ของเบราว์เซอร์ที่ทําให้ PWA มีประสิทธิภาพสูงสุด 🦸
ความสามารถใหม่ๆ ของเบราว์เซอร์เหล่านี้กําลังดําเนินการอยู่และยังคงเป็นมาตรฐานอยู่ ดังนั้นในบางครั้งคุณอาจต้องตั้งค่าสถานะเบราว์เซอร์เพื่อใช้งาน
สิ่งที่ต้องมีก่อน
สําหรับ Codelab นี้ คุณควรทําความคุ้นเคยกับ JavaScript ที่ทันสมัย สัญญา และซิงค์ข้อมูล เนื่องจากบางขั้นตอนของ Codelab ไม่ได้รองรับทุกแพลตฟอร์ม การทดสอบจึงอาจมีข้อมูลในกรณีที่คุณมีอุปกรณ์เพิ่มเติม เช่น โทรศัพท์ Android หรือแล็ปท็อปที่ใช้ระบบปฏิบัติการอื่นที่ไม่ใช่อุปกรณ์ที่แก้ไขโค้ด คุณอาจลองใช้เครื่องจําลอง เช่น เครื่องจําลอง Android หรือบริการออนไลน์ เช่น BrowserStack ที่ทดสอบอุปกรณ์ที่มีอยู่ได้แทนอุปกรณ์จริง หากไม่ใช่ เราก็ไม่ต้องดําเนินการใดๆ ทั้งสิ้น และไม่ต้องพึ่งพากันและกัน
สิ่งที่คุณจะสร้าง
คุณจะสร้างแอปบนเว็บสําหรับการ์ดอวยพร และดูวิธีที่ความสามารถใหม่ๆ และเบราว์เซอร์ที่กําลังจะเปิดตัวนั้นช่วยปรับปรุงแอปของคุณได้ เพื่อมอบประสบการณ์การใช้งานขั้นสูงในบางเบราว์เซอร์ (แต่ยังคงมีประโยชน์ในเบราว์เซอร์รุ่นใหม่ทั้งหมด)
คุณจะได้ดูวิธีเพิ่มความสามารถในการรองรับ เช่น การเข้าถึงระบบไฟล์ การเข้าถึงคลิปบอร์ดของระบบ การเรียกข้อมูลรายชื่อติดต่อ การซิงค์ในเบื้องหลังเป็นระยะๆ การล็อกหน้าจอ ฟีเจอร์การแชร์ และอื่นๆ
หลังจากทํางานผ่าน Codelab แล้ว คุณจะเข้าใจลึกซึ้งเกี่ยวกับวิธีปรับปรุงเว็บแอปอย่างต่อเนื่องด้วยฟีเจอร์ใหม่ๆ ของเบราว์เซอร์ ทั้งหมดนี้จะช่วยลดภาระในการดาวน์โหลดของผู้ใช้ที่เบราว์เซอร์อาจใช้ร่วมกันไม่ได้ และที่สําคัญที่สุดคือการยกเว้นแอปเหล่านี้ไม่ให้รวมอยู่ในที่แรก
สิ่งที่ต้องมี
เบราว์เซอร์ที่รองรับโดยสมบูรณ์ในขณะนี้ได้แก่
ขอแนะนําให้ใช้เวอร์ชันที่กําลังพัฒนาที่กําหนด
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
หลังจากเล่นกับแอปพลิเคชันแล้ว ให้รีมิกซ์แอปเพื่อสร้างสําเนาที่คุณแก้ไขได้ URL ของรีมิกซ์จะมีลักษณะดังนี้ glitch.com/editupload/bouncy-candytuft ("bouncy-candytuft" เป็นอย่างอื่นสําหรับคุณ) คุณเข้าถึงรีมิกซ์นี้ได้โดยตรงทั่วโลก ลงชื่อเข้าใช้บัญชีที่มีอยู่หรือสร้างบัญชีใหม่ใน Glitch เพื่อบันทึกงาน คุณดูแอปได้โดยคลิกปุ่ม "🕶 แสดง" และ URL ของแอปที่โฮสต์จะเป็นดังนี้ bouncy-candytuft.glitch.me (โปรดทราบว่า .me
แทนที่จะเป็น .com
เป็นโดเมนระดับบนสุด)
ตอนนี้คุณก็พร้อมแก้ไขและปรับปรุงแอปแล้ว เมื่อคุณทําการเปลี่ยนแปลง แอปจะโหลดซ้ํา แล้วการเปลี่ยนแปลงจะมองเห็นได้โดยตรง
วิธีการต่อไปนี้ควรดําเนินการให้เสร็จสมบูรณ์ตามลําดับ แต่ตามที่ระบุไว้ข้างต้น คุณสามารถข้ามขั้นตอนได้ทุกเมื่อหากไม่มีสิทธิ์เข้าถึงอุปกรณ์ที่เข้ากันได้ อย่าลืมว่าแต่ละงานจะมีเครื่องหมาย 🐟 ปลาน้ําจืดที่ไม่เป็นอันตราย หรือ 🐡 ซึ่งเป็นเครื่องหมายคําพูด แล้วดูแลด้วยปลาฟุกุที่แจ้งเตือนการใช้ฟีเจอร์ทดลองหรือไม่
ตรวจสอบคอนโซลใน DevTools เพื่อดูว่ามีการรองรับ API ในอุปกรณ์ปัจจุบันแล้วหรือยัง นอกจากนี้เรายังใช้ Glitch เพื่อให้คุณตรวจสอบแอปเดียวกันในอุปกรณ์ต่างๆ ได้อย่างง่ายดาย เช่น ในโทรศัพท์มือถือและคอมพิวเตอร์เดสก์ท็อป
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 ของการตรวจจับรูปร่าง
บางครั้งผู้ใช้' ภาพวาดหรือภาพพื้นหลังที่ใช้อาจมีข้อมูลที่เป็นประโยชน์ เช่น บาร์โค้ด 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
แต่สุดท้ายแล้ว สําหรับการอัปเดตที่ยังไม่ได้เผยแพร่ คุณจะเข้าถึงเครื่องมือติดตาม API ของ Fugu ที่มีลิงก์ไปยังข้อเสนอทั้งหมดที่จัดส่ง อยู่ระหว่างการทดลองใช้ต้นทางหรือช่วงทดลองใช้ ข้อเสนอทั้งหมดซึ่งการทํางานได้เริ่มต้นแล้ว และทุกอย่างที่ได้รับการพิจารณาแต่ยังไม่ได้เริ่ม
Codelab นี้เขียนโดย Thomas Steiner (@tomayac) ฉันยินดีตอบคําถามของคุณ และหวังว่าจะได้อ่านความคิดเห็นจากคุณ ขอขอบคุณ Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) และ Jackie Han (@hanguokai) ที่ช่วยกําหนดแนวทางของ Codelab นี้