1. ก่อนเริ่มต้น
Progressive Web Application (PWA) เป็นซอฟต์แวร์แอปพลิเคชันประเภทหนึ่งที่ให้บริการผ่านเว็บ ซึ่งสร้างขึ้นโดยใช้เทคโนโลยีเว็บทั่วไป ได้แก่ HTML, CSS และ JavaScript โดยออกแบบมาให้ทำงานบนแพลตฟอร์มใดก็ได้ที่ใช้เบราว์เซอร์ที่เป็นไปตามมาตรฐาน
ในโค้ดแล็บนี้ คุณจะเริ่มต้นด้วย PWA พื้นฐาน จากนั้นสำรวจความสามารถใหม่ๆ ของเบราว์เซอร์ที่จะช่วยให้ PWA ของคุณมีพลังวิเศษ 🦸 ในที่สุด
ความสามารถใหม่ๆ ของเบราว์เซอร์เหล่านี้หลายอย่างยังอยู่ระหว่างการพัฒนาและยังคงอยู่ระหว่างการกำหนดมาตรฐาน ดังนั้นในบางครั้งคุณจะต้องตั้งค่าสถานะของเบราว์เซอร์เพื่อใช้งาน
ข้อกำหนดเบื้องต้น
สำหรับ Codelab นี้ คุณควรคุ้นเคยกับ JavaScript สมัยใหม่ โดยเฉพาะ Promise และ async/await เนื่องจากแพลตฟอร์มบางแพลตฟอร์มไม่รองรับบางขั้นตอนของโค้ดแล็บ การมีอุปกรณ์เพิ่มเติมไว้ในมือจึงช่วยในการทดสอบได้ เช่น โทรศัพท์ Android หรือแล็ปท็อปที่ใช้ระบบปฏิบัติการอื่นที่ไม่ใช่อุปกรณ์ที่คุณใช้แก้ไขโค้ด คุณสามารถลองใช้โปรแกรมจำลอง เช่น โปรแกรมจำลอง Android หรือบริการออนไลน์ เช่น BrowserStack ที่ให้คุณทดสอบจากอุปกรณ์ที่ใช้อยู่แทนการใช้อุปกรณ์จริง หรือคุณจะข้ามขั้นตอนใดก็ได้ เนื่องจากขั้นตอนต่างๆ ไม่ได้ขึ้นอยู่กับกัน
สิ่งที่คุณจะสร้าง
คุณจะได้สร้างเว็บแอปการ์ดอวยพร และเรียนรู้วิธีที่ความสามารถใหม่ๆ และความสามารถที่กำลังจะเปิดตัวของเบราว์เซอร์จะช่วยปรับปรุงแอปของคุณให้มอบประสบการณ์ขั้นสูงในบางเบราว์เซอร์ (แต่ยังคงมีประโยชน์ในเบราว์เซอร์สมัยใหม่ทั้งหมด) ได้
คุณจะได้เรียนรู้วิธีเพิ่มความสามารถในการรองรับ เช่น การเข้าถึงระบบไฟล์ การเข้าถึงคลิปบอร์ดของระบบ การดึงข้อมูลรายชื่อติดต่อ การซิงค์ข้อมูลในเบื้องหลังเป็นระยะ การล็อกการปลุกหน้าจอ ฟีเจอร์การแชร์ และอื่นๆ
หลังจากทำตาม Codelab แล้ว คุณจะมีความเข้าใจอย่างถ่องแท้เกี่ยวกับวิธีปรับปรุงเว็บแอปอย่างค่อยเป็นค่อยไปด้วยฟีเจอร์ใหม่ๆ ของเบราว์เซอร์ โดยไม่ต้องสร้างภาระในการดาวน์โหลดให้กับกลุ่มย่อยของผู้ใช้ที่ใช้เบราว์เซอร์ที่ไม่รองรับ และที่สำคัญที่สุดคือไม่ต้องกีดกันผู้ใช้เหล่านั้นออกจากแอปตั้งแต่แรก
สิ่งที่คุณต้องมี
เบราว์เซอร์ที่รองรับอย่างเต็มรูปแบบในขณะนี้ ได้แก่
เราขอแนะนำให้ใช้ช่อง Dev ที่เฉพาะเจาะจง
2. Project Fugu
Progressive Web App (PWA) สร้างและเพิ่มประสิทธิภาพด้วย API ที่ทันสมัยเพื่อมอบความสามารถที่ดียิ่งขึ้น ความน่าเชื่อถือ และการติดตั้งได้ ในขณะที่เข้าถึงได้ทุกคนบนเว็บ ทุกที่ในโลก และใช้อุปกรณ์ทุกประเภท
API บางรายการมีประสิทธิภาพสูงมาก และหากจัดการไม่ถูกต้อง อาจเกิดข้อผิดพลาดได้ เช่นเดียวกับปลาปักเป้า 🐡: หากตัดอย่างถูกต้องก็จะเป็นอาหารรสเลิศ แต่หากตัดผิดก็อาจเป็นอันตรายถึงชีวิต (แต่ไม่ต้องกังวล ในโค้ดแล็บนี้ไม่มีอะไรเสียหายจริงๆ)
ด้วยเหตุนี้ ชื่อรหัสภายในของโปรเจ็กต์ความสามารถของเว็บ (ที่บริษัทที่เกี่ยวข้องกำลังพัฒนา API ใหม่เหล่านี้) จึงเป็นโปรเจ็กต์ Fugu
ความสามารถของเว็บช่วยให้องค์กรทั้งขนาดเล็กและใหญ่สามารถสร้างโซลูชันที่ใช้เบราว์เซอร์โดยเฉพาะได้ ซึ่งมักจะช่วยให้ติดตั้งใช้งานได้เร็วขึ้นและมีต้นทุนการพัฒนาต่ำกว่าการใช้เส้นทางเฉพาะแพลตฟอร์ม
3. เริ่มต้นใช้งาน
ดาวน์โหลดเบราว์เซอร์ใดเบราว์เซอร์หนึ่ง แล้วตั้งค่าสถานะรันไทม์ต่อไปนี้ 🚩 โดยไปที่ about://flags
ซึ่งใช้ได้ทั้งใน Chrome และ Edge
#enable-experimental-web-platform-features
หลังจากเปิดใช้แล้ว ให้รีสตาร์ทเบราว์เซอร์
คุณจะใช้แพลตฟอร์ม Glitch เนื่องจากแพลตฟอร์มนี้ช่วยให้คุณโฮสต์ PWA ได้และมีโปรแกรมแก้ไขที่ใช้งานได้ดี นอกจากนี้ Glitch ยังรองรับการนำเข้าและส่งออกไปยัง GitHub ด้วย จึงไม่มีการล็อกอินของผู้ให้บริการ ไปที่ fugu-paint.glitch.me เพื่อลองใช้แอปพลิเคชัน ซึ่งเป็นแอปวาดภาพพื้นฐาน 🎨 ที่คุณจะปรับปรุงในระหว่างการทำโค้ดแล็บ
หลังจากลองใช้แอปพลิเคชันแล้ว ให้รีมิกซ์แอปเพื่อสร้างสำเนาของคุณเองซึ่งคุณสามารถแก้ไขได้ URL ของรีมิกซ์จะมีลักษณะคล้ายกับ glitch.com/edit/#!/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. 🐟 เพิ่มการรองรับ Web Share Target 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 */
/* ... */
});
เมื่อแอปโหลดแล้ว แอปจะตรวจสอบว่ามีการตั้งค่าพารามิเตอร์การค้นหานี้หรือไม่ หากมีการตั้งค่า แอปจะวาดรูปภาพที่แชร์ลงใน Canvas แล้วลบออกจากแคช โดยทั้งหมดนี้จะเกิดขึ้นใน 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()
ของ Canvas จากนั้นทำความคุ้นเคยกับองค์ประกอบ <input
type=file>
เมื่อมีความรู้ดังกล่าวแล้ว คุณจะแก้ไขไฟล์ที่ชื่อ import_image_legacy.mjs
และเพิ่มข้อมูลโค้ดต่อไปนี้ได้ ที่ด้านบนของไฟล์ที่คุณนำเข้าจะมีปุ่มนำเข้าและฟังก์ชันอำนวยความสะดวก drawBlob()
ที่ให้คุณวาด Blob ลงใน Canvas
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()
ฟังก์ชันอำนวยความสะดวกที่แปลงเนื้อหา Canvas เป็น 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. 🐟 เพิ่มการรองรับ Contact Picker API
ผู้ใช้อาจต้องการเพิ่มข้อความในการ์ดอวยพรและกล่าวถึงบุคคลใดบุคคลหนึ่งเป็นการส่วนตัว เพิ่มฟีเจอร์ที่ช่วยให้ผู้ใช้เลือกรายชื่อติดต่อในเครื่อง 1 รายการ (หรือหลายรายการ) และเพิ่มชื่อลงในข้อความแชร์ได้
ในอุปกรณ์ Android หรือ iOS Contact Picker API จะช่วยให้คุณเลือกรายชื่อติดต่อจากแอปตัวจัดการรายชื่อติดต่อของอุปกรณ์และส่งกลับไปยังแอปพลิเคชันได้ แก้ไขไฟล์ contacts.mjs
และเพิ่มโค้ดด้านล่าง
import { contactsButton, ctx, canvas } from './script.mjs';
const getContacts = async () => {
const properties = ['name'];
const options = { multiple: true };
try {
return await navigator.contacts.select(properties, options);
} catch (err) {
console.error(err.name, err.message);
}
};
contactsButton.style.display = 'block';
contactsButton.addEventListener('click', async () => {
const contacts = await getContacts();
if (contacts) {
ctx.font = '1em Comic Sans MS';
contacts.forEach((contact, index) => {
ctx.fillText(contact.name.join(), 20, 16 * ++index, canvas.width);
});
}
});
10. 🐟 เพิ่มการรองรับ Async Clipboard API
ผู้ใช้อาจต้องการวางรูปภาพจากแอปอื่นลงในแอปของคุณ หรือคัดลอกภาพวาดจากแอปของคุณไปยังแอปอื่น ให้เพิ่มฟีเจอร์ที่อนุญาตให้ผู้ใช้คัดลอกและวางรูปภาพเข้าและออกจากแอปของคุณ Async Clipboard API รองรับรูปภาพ PNG ดังนั้นตอนนี้คุณจึงอ่านและเขียนข้อมูลรูปภาพไปยังคลิปบอร์ดได้แล้ว
ค้นหาไฟล์ clipboard.mjs
แล้วเพิ่มข้อมูลต่อไปนี้
import { copyButton, pasteButton, toBlob, drawImage } from './script.mjs';
const copy = async (blob) => {
try {
await navigator.clipboard.write([
/* global ClipboardItem */
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
const paste = async () => {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
try {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
return blob;
}
} catch (err) {
console.error(err.name, err.message);
}
}
} catch (err) {
console.error(err.name, err.message);
}
};
copyButton.style.display = 'block';
copyButton.addEventListener('click', async () => {
await copy(await toBlob());
});
pasteButton.style.display = 'block';
pasteButton.addEventListener('click', async () => {
const image = new Image();
image.addEventListener('load', () => {
drawImage(image);
});
image.src = URL.createObjectURL(await paste());
});
11. 🐟 เพิ่มการรองรับ Badging API
เมื่อผู้ใช้ติดตั้งแอปของคุณ ไอคอนจะปรากฏบนหน้าจอหลักของผู้ใช้ คุณสามารถใช้ไอคอนนี้เพื่อสื่อข้อมูลสนุกๆ เช่น จำนวนการลากเส้นของภาพวาดที่กำหนด
เพิ่มฟีเจอร์ที่นับจำนวนป้ายทุกครั้งที่ผู้ใช้ของคุณวาดเส้นใหม่ Badging API อนุญาตให้ตั้งป้ายตัวเลขบนไอคอนแอป คุณสามารถอัปเดตป้ายเมื่อเกิดpointerdown
เหตุการณ์ (นั่นคือเมื่อมีการปัดแปรง) และรีเซ็ตป้ายเมื่อล้างผืนผ้าใบ
ใส่โค้ดด้านล่างในไฟล์ badge.mjs
import { canvas, clearButton } from './script.mjs';
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
12. 🐟 เพิ่มการรองรับ Screen Wake Lock API
บางครั้งผู้ใช้อาจต้องใช้เวลาสักครู่เพื่อจ้องมองภาพวาดนานพอที่จะเกิดแรงบันดาลใจ เพิ่มฟีเจอร์ที่ทำให้หน้าจอสว่างอยู่เสมอและหยุดไม่ให้โปรแกรมรักษาหน้าจอทำงาน Screen Wake Lock API จะป้องกันไม่ให้หน้าจอของผู้ใช้เข้าสู่โหมดสลีป ระบบจะปล่อย Wake Lock โดยอัตโนมัติเมื่อเกิดเหตุการณ์การเปลี่ยนแปลงระดับการเข้าถึงตามที่กำหนดโดย Page Visibility ดังนั้นจึงต้องขอ 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. 🐟 เพิ่มการรองรับ Periodic Background Sync API
การเริ่มต้นด้วยผืนผ้าใบเปล่าอาจน่าเบื่อ คุณสามารถใช้ Periodic Background Sync API เพื่อเริ่มต้นใช้งาน Canvas ของผู้ใช้ด้วยรูปภาพใหม่ทุกวันได้ เช่น รูปภาพปลาปักเป้าประจำวันของ Unsplash
ซึ่งต้องใช้ไฟล์ 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. 🐟 เพิ่มการรองรับ Shape Detection API
บางครั้งภาพวาดของผู้ใช้หรือรูปภาพพื้นหลังที่ใช้ อาจมีข้อมูลที่เป็นประโยชน์ เช่น บาร์โค้ด Shape Detection API และโดยเฉพาะอย่างยิ่ง Barcode Detection API ช่วยให้คุณดึงข้อมูลนี้ได้ เพิ่มฟีเจอร์ที่พยายามตรวจหาบาร์โค้ดจากภาพวาดของผู้ใช้ ค้นหาไฟล์ barcode.mjs
แล้วเพิ่มเนื้อหาด้านล่าง หากต้องการทดสอบฟีเจอร์นี้ เพียงโหลดหรือวางรูปภาพที่มีบาร์โค้ดลงใน Canvas คุณสามารถคัดลอกบาร์โค้ดตัวอย่างจากการค้นหารูปภาพสำหรับคิวอาร์โค้ด
/* 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
หากคุณจินตนาการว่าแอปของคุณทำงานในการตั้งค่าที่คล้ายกับคีออส ฟีเจอร์ที่มีประโยชน์คือการรีเซ็ต Canvas หลังจากไม่มีการใช้งานเป็นระยะเวลาหนึ่ง 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 ของเบราว์เซอร์ที่น่าตื่นเต้นมากมายกำลังได้รับการพัฒนาในบริบทของโปรเจ็กต์ Fugu 🐡 ทำให้ Codelab นี้แทบจะไม่ได้แตะพื้นผิวเลย
หากต้องการดูข้อมูลเพิ่มเติม โปรดติดตามสิ่งพิมพ์ของเราในเว็บไซต์ web.dev
แต่ยังไม่หมดเพียงเท่านี้ สำหรับข้อมูลอัปเดตที่ยังไม่ได้เผยแพร่ คุณสามารถเข้าถึงเครื่องมือติดตาม Fugu API ของเราพร้อมลิงก์ไปยังข้อเสนอทั้งหมดที่เผยแพร่แล้ว อยู่ในการทดลองใช้ใน Origin หรือการทดลองใช้สำหรับนักพัฒนาแอป ข้อเสนอทั้งหมดที่เริ่มดำเนินการแล้ว และทุกอย่างที่กำลังพิจารณาแต่ยังไม่ได้เริ่ม
Codelab นี้เขียนโดย Thomas Steiner (@tomayac) เรายินดีที่จะตอบคำถามของคุณและหวังว่าจะได้อ่านความคิดเห็นของคุณ ขอขอบคุณ Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) และ Jackie Han (@hanguokai) ที่ช่วยสร้าง Codelab นี้