การสร้างสำหรับเบราว์เซอร์ที่ทันสมัยและการเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปเหมือนกับปี 2003
เผยแพร่: 29 มิถุนายน 2020
เมื่อเดือนมีนาคม 2003 Nick Finck และ Steve Champeon ได้สร้างความตกตะลึงให้กับวงการออกแบบเว็บ ด้วยแนวคิดการเพิ่มประสิทธิภาพแบบก้าวหน้า ซึ่งเป็นกลยุทธ์การออกแบบเว็บที่เน้นการโหลดเนื้อหาหลักของหน้าเว็บก่อน จากนั้นจึงค่อยๆ เพิ่มเลเยอร์การนำเสนอและฟีเจอร์ที่ซับซ้อนมากขึ้น และมีความเข้มงวดทางเทคนิคมากขึ้นไว้ด้านบนของเนื้อหา ในขณะที่ในปี 2003 การเพิ่มประสิทธิภาพแบบก้าวหน้าคือการใช้ฟีเจอร์ CSS ที่ทันสมัยในขณะนั้น JavaScript ที่ไม่รบกวน และแม้แต่ Scalable Vector Graphics การเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปในปี 2020 และหลังจากนั้นคือการใช้ความสามารถของเบราว์เซอร์ที่ทันสมัย

JavaScript ที่ทันสมัย
เมื่อพูดถึง JavaScript สถานการณ์การรองรับเบราว์เซอร์สำหรับฟีเจอร์ JavaScript หลักล่าสุดของ ES 2015 นั้นดีมาก มาตรฐานใหม่นี้รวมถึง Promise, โมดูล, คลาส, สตริงเทมเพลต, ฟังก์ชันลูกศร, let
และ const
พารามิเตอร์เริ่มต้น, ตัวสร้าง, การกำหนดค่าการทำลาย, Rest และ Spread
Map
/Set
, WeakMap
/WeakSet
และอื่นๆ อีกมากมาย
รองรับทั้งหมด

ฟังก์ชันแบบไม่พร้อมกัน ซึ่งเป็นฟีเจอร์ ES 2017 และเป็นหนึ่งในฟีเจอร์ที่ฉันชื่นชอบ
ใช้ได้
ในเบราว์เซอร์หลักทั้งหมด
คีย์เวิร์ด async
และ await
ช่วยให้เขียนลักษณะการทำงานแบบอะซิงโครนัสที่อิงตาม Promise ได้ในรูปแบบที่สะอาดกว่า โดยไม่ต้องกำหนดค่า Promise Chain อย่างชัดเจน

และแม้แต่การเพิ่มภาษา ES 2020 ที่เพิ่งเกิดขึ้นอย่าง การเชื่อมโยงแบบออปชันนัลและ การรวมค่า Null ก็ได้รับการรองรับอย่างรวดเร็ว เมื่อพูดถึงฟีเจอร์หลักของ JavaScript ก็คงไม่มีอะไรดีไปกว่านี้แล้ว
เช่น
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0

แอปตัวอย่าง: Fugu Greetings
ในเอกสารนี้ ฉันจะทำงานกับ PWA ที่ชื่อ Fugu Greetings (GitHub) ชื่อแอปนี้เป็นการแสดงความเคารพต่อ Project Fugu 🐡 ซึ่งเป็นความพยายามที่จะมอบ ความสามารถทั้งหมดของแอปพลิเคชัน Android, iOS และเดสก์ท็อปให้กับเว็บ อ่านข้อมูลเพิ่มเติมเกี่ยวกับโปรเจ็กต์ได้ในหน้า Landing Page
Fugu Greetings เป็นแอปวาดภาพที่ให้คุณสร้างการ์ดอวยพรเสมือนและส่งให้คนที่คุณรัก ซึ่งแสดงให้เห็นแนวคิดหลักของ PWA โดยแอปนี้เชื่อถือได้และเปิดใช้แบบออฟไลน์ได้อย่างเต็มที่ ดังนั้นแม้จะไม่มีเครือข่าย คุณก็ยังใช้งานได้ นอกจากนี้ยังติดตั้งในหน้าจอหลักของอุปกรณ์และผสานรวมกับระบบปฏิบัติการได้อย่างราบรื่น ในรูปแบบแอปพลิเคชันแบบสแตนด์อโลน

การเพิ่มประสิทธิภาพแบบต่อเนื่อง
เมื่อจัดการเรื่องนี้เรียบร้อยแล้ว ก็ถึงเวลาพูดถึงการเพิ่มประสิทธิภาพแบบต่อเนื่อง อภิธานศัพท์ MDN Web Docs ให้คำจำกัดความ แนวคิดนี้ไว้ดังนี้
การเพิ่มประสิทธิภาพแบบก้าวหน้าเป็นปรัชญาการออกแบบที่ให้เนื้อหาและฟังก์ชันการทำงานที่จำเป็นขั้นพื้นฐานแก่ผู้ใช้ให้ได้มากที่สุด ในขณะเดียวกันก็มอบประสบการณ์ที่ดีที่สุดเท่าที่จะเป็นไปได้แก่ผู้ใช้เบราว์เซอร์ที่ทันสมัยที่สุดซึ่งสามารถเรียกใช้โค้ดที่จำเป็นทั้งหมดได้เท่านั้น
การตรวจหาฟีเจอร์ โดยทั่วไปจะใช้เพื่อพิจารณาว่าเบราว์เซอร์สามารถรองรับฟังก์ชันที่ทันสมัยกว่าได้หรือไม่ ขณะที่มักใช้ Polyfill เพื่อเพิ่มฟีเจอร์ที่ขาดหายไปด้วย JavaScript
[…]
การเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปเป็นเทคนิคที่มีประโยชน์ซึ่งช่วยให้นักพัฒนาเว็บมุ่งเน้น การพัฒนาเว็บไซต์ที่ดีที่สุดเท่าที่จะเป็นไปได้ ในขณะเดียวกันก็ทำให้เว็บไซต์เหล่านั้นทำงานได้ ใน User Agent ที่ไม่รู้จักหลายรายการ การลดระดับอย่างราบรื่น มีความเกี่ยวข้อง แต่ไม่ใช่สิ่งเดียวกัน และมักถูกมองว่าเป็นการดำเนินการในทิศทางตรงกันข้าม กับการเพิ่มประสิทธิภาพแบบก้าวหน้า ในความเป็นจริงแล้ว ทั้ง 2 แนวทางนี้ใช้ได้และมักจะเสริมซึ่งกันและกัน
ผู้มีส่วนร่วมใน MDN
การเริ่มสร้างการ์ดอวยพรตั้งแต่ต้นอาจเป็นเรื่องยุ่งยาก
แล้วทำไมเราถึงไม่มีฟีเจอร์ที่อนุญาตให้ผู้ใช้นำเข้ารูปภาพและเริ่มต้นจากตรงนั้น
ในแนวทางแบบเดิม คุณจะต้องใช้องค์ประกอบ
<input type=file>
เพื่อให้เกิดการดำเนินการนี้
ก่อนอื่น คุณต้องสร้างองค์ประกอบ ตั้งค่า type
เป็น 'file'
และเพิ่มประเภท MIME
ลงในพร็อพเพอร์ตี้ accept
จากนั้นจึง "คลิก" องค์ประกอบดังกล่าวโดยอัตโนมัติและรอฟังการเปลี่ยนแปลง
เมื่อเลือกรูปภาพ ระบบจะนําเข้าลงใน Canvas โดยตรง
const importImage = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
เมื่อมีฟีเจอร์นำเข้า ก็ควรมีฟีเจอร์ส่งออกด้วย
เพื่อให้ผู้ใช้บันทึกการ์ดอวยพรไว้ในเครื่องได้
วิธีดั้งเดิมในการบันทึกไฟล์คือการสร้างลิงก์ Anchor
ด้วยแอตทริบิวต์ download
และใช้ URL ของ Blob เป็น href
นอกจากนี้ คุณยังสามารถ "คลิก" โดยอัตโนมัติเพื่อทริกเกอร์การดาวน์โหลด
และอย่าลืมเพิกถอน URL ของออบเจ็กต์ Blob เพื่อป้องกันการรั่วไหลของหน่วยความจำ
const exportImage = async (blob) => {
const a = document.createElement('a');
a.download = 'fugu-greeting.png';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', (e) => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
แต่เดี๋ยวก่อน ในใจคุณไม่ได้ "ดาวน์โหลด" การ์ดอวยพร แต่คุณได้ "บันทึก" การ์ดนั้น แทนที่จะแสดงกล่องโต้ตอบ "บันทึก" ที่ให้คุณเลือกตำแหน่งที่จะวางไฟล์ เบราว์เซอร์จะดาวน์โหลดการ์ดอวยพรโดยตรงโดยไม่ต้องมีการโต้ตอบจากผู้ใช้ และวางไว้ในโฟลเดอร์ดาวน์โหลดโดยตรง ซึ่งไม่ดีนัก
จะเกิดอะไรขึ้นหากมีวิธีที่ดีกว่านี้ จะเกิดอะไรขึ้นหากคุณเพียงแค่เปิดไฟล์ในเครื่อง แก้ไข แล้วบันทึกการแก้ไข ไม่ว่าจะบันทึกลงในไฟล์ใหม่หรือบันทึกกลับไปยังไฟล์ต้นฉบับที่คุณเปิดไว้ตอนแรก ซึ่งก็คือ File System Access API ที่ช่วยให้คุณเปิดและสร้างไฟล์และ ไดเรกทอรี รวมถึงแก้ไขและบันทึกไฟล์ได้
แล้วฉันจะตรวจหาฟีเจอร์ของ API ได้อย่างไร
File System Access API แสดงเมธอดใหม่ window.chooseFileSystemEntries()
ดังนั้นฉันจึงต้องโหลดโมดูลการนำเข้าและส่งออกที่แตกต่างกันแบบมีเงื่อนไข ทั้งนี้ขึ้นอยู่กับว่ามีเมธอดนี้หรือไม่
const loadImportAndExport = () => {
if ('chooseFileSystemEntries' in window) {
Promise.all([
import('./import_image.mjs'),
import('./export_image.mjs'),
]);
} else {
Promise.all([
import('./import_image_legacy.mjs'),
import('./export_image_legacy.mjs'),
]);
}
};
แต่ก่อนที่จะเจาะลึกรายละเอียด File System Access API เราขอเน้นรูปแบบการเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปที่นี่ ในเบราว์เซอร์ที่ไม่รองรับ File System Access API ฉันจะโหลดสคริปต์เดิม


แต่ใน Chrome ซึ่งเป็นเบราว์เซอร์ที่รองรับ API จะโหลดเฉพาะสคริปต์ใหม่เท่านั้น
ซึ่งทำได้อย่างสวยงามด้วยimport()
แบบไดนามิกที่เบราว์เซอร์ที่ทันสมัยทั้งหมดรองรับ
อย่างที่บอกไปก่อนหน้านี้ว่าช่วงนี้หญ้าเขียวชอุ่มมาก

File System Access API
ตอนนี้เราได้พูดถึงเรื่องนี้แล้ว ก็ถึงเวลามาดูการใช้งานจริงโดยอิงตาม File System Access API
สำหรับการนำเข้ารูปภาพ ฉันเรียกใช้ window.chooseFileSystemEntries()
และส่งพร็อพเพอร์ตี้ accepts
ที่ฉันบอกว่าต้องการไฟล์รูปภาพ
ระบบรองรับทั้งนามสกุลไฟล์และประเภท MIME
ซึ่งจะทำให้ได้แฮนเดิลไฟล์ จากนั้นฉันจะรับไฟล์จริงได้โดยการเรียกใช้ getFile()
const importImage = async () => {
try {
const handle = await window.chooseFileSystemEntries({
accepts: [
{
description: 'Image files',
mimeTypes: ['image/*'],
extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
},
],
});
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
การส่งออกรูปภาพแทบจะเหมือนกัน แต่คราวนี้ฉันต้องส่งพารามิเตอร์ประเภท 'save-file'
ไปยังเมธอด chooseFileSystemEntries()
จากนั้นฉันจะได้รับกล่องโต้ตอบบันทึกไฟล์
เมื่อเปิดไฟล์ไว้ คุณไม่จำเป็นต้องทำเช่นนี้เนื่องจาก 'open-file'
เป็นค่าเริ่มต้น
ฉันตั้งค่าพารามิเตอร์ accepts
ในลักษณะเดียวกับก่อนหน้านี้ แต่ครั้งนี้จำกัดเฉพาะรูปภาพ PNG
อีกครั้งที่ฉันได้รับแฮนเดิลไฟล์ แต่คราวนี้ฉันสร้างสตรีมที่เขียนได้โดยการเรียกใช้ createWritable()
แทนที่จะรับไฟล์
จากนั้นฉันก็เขียน Blob ซึ่งเป็นรูปภาพการ์ดอวยพรลงในไฟล์
สุดท้าย ฉันก็ปิดสตรีมที่เขียนได้
ทุกอย่างอาจล้มเหลวได้เสมอ เช่น ดิสก์อาจมีพื้นที่ไม่เพียงพอ
อาจเกิดข้อผิดพลาดในการเขียนหรืออ่าน หรือผู้ใช้อาจยกเลิกกล่องโต้ตอบไฟล์
ด้วยเหตุนี้ ฉันจึงมักจะห่อหุ้มการเรียกใช้ด้วยคำสั่ง try...catch
const exportImage = async (blob) => {
try {
const handle = await window.chooseFileSystemEntries({
type: 'save-file',
accepts: [
{
description: 'Image file',
extensions: ['png'],
mimeTypes: ['image/png'],
},
],
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
} catch (err) {
console.error(err.name, err.message);
}
};
การใช้การเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปด้วย File System Access API ฉันสามารถเปิดไฟล์ได้เหมือนเดิม ระบบจะวาดไฟล์ที่นำเข้าลงใน Canvas โดยตรง ฉันสามารถทำการแก้ไขและบันทึกด้วยกล่องโต้ตอบการบันทึกจริง ซึ่งฉันสามารถเลือกชื่อและตำแหน่งจัดเก็บของไฟล์ได้ ตอนนี้ไฟล์ก็พร้อมให้เก็บรักษาไว้ตลอดกาลแล้ว



Web Share และ Web Share Target API
นอกจากการจัดเก็บไว้ตลอดกาลแล้ว ฉันอาจต้องการแชร์การ์ดอวยพรจริงๆ Web Share API และ Web Share Target API ช่วยให้ฉันทำสิ่งนี้ได้ ระบบปฏิบัติการบนอุปกรณ์เคลื่อนที่และระบบปฏิบัติการบนเดสก์ท็อปที่เพิ่งเปิดตัวมีกลไกการแชร์ในตัว
เช่น ชีตการแชร์ของ Safari บนเดสก์ท็อปใน macOS จะทริกเกอร์เมื่อผู้ใช้คลิกแชร์บทความในบล็อกของฉัน คุณแชร์ลิงก์ไปยังบทความกับเพื่อนโดยใช้แอปข้อความของ macOS ได้
โดยฉันจะเรียกใช้ navigator.share()
และส่ง title
, text
และ url
ที่ไม่บังคับในออบเจ็กต์
แต่หากต้องการแนบรูปภาพจะต้องทำอย่างไร Web Share API ระดับ 1 ยังไม่รองรับฟีเจอร์นี้
ข่าวดีคือ Web Share ระดับ 2 ได้เพิ่มความสามารถในการแชร์ไฟล์แล้ว
try {
await navigator.share({
title: 'Check out this article:',
text: `"${document.title}" by @tomayac:`,
url: document.querySelector('link[rel=canonical]').href,
});
} catch (err) {
console.warn(err.name, err.message);
}
ฉันจะแสดงให้ดูว่าคุณจะทำให้การ์ดอวยพร Fugu ทำงานได้อย่างไร
ก่อนอื่น ฉันต้องเตรียมออบเจ็กต์ data
ที่มีอาร์เรย์ files
ซึ่งประกอบด้วย Blob 1 รายการ จากนั้นก็มี title
และ text
จากนั้น ตามแนวทางปฏิบัติแนะนำ เราจะใช้วิธีการใหม่ navigator.canShare()
ซึ่งทำตามชื่อของมัน
นั่นคือบอกเราว่าเบราว์เซอร์แชร์ออบเจ็กต์ data
ที่เราพยายามแชร์ได้หรือไม่
หาก navigator.canShare()
บอกว่าแชร์ข้อมูลได้ ฉันก็พร้อมที่จะ
โทรหา navigator.share()
เหมือนเดิม
เนื่องจากทุกอย่างอาจล้มเหลวได้ ฉันจึงใช้บล็อก try...catch
อีกครั้ง
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);
}
};
ฉันใช้การเพิ่มประสิทธิภาพแบบต่อเนื่องเช่นเคย
หากมีทั้ง 'share'
และ 'canShare'
ในออบเจ็กต์ navigator
ฉันจึงจะดำเนินการต่อและ
โหลด share.mjs
โดยใช้ import()
แบบไดนามิก
ในเบราว์เซอร์ เช่น Safari บนอุปกรณ์เคลื่อนที่ ซึ่งมีเพียง 1 ใน 2 เงื่อนไขเท่านั้น ฉันจะไม่โหลดฟังก์ชันการทำงาน
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
ใน Fugu Greetings หากฉันแตะปุ่มแชร์ในเบราว์เซอร์ที่รองรับ เช่น Chrome บน Android ชีตการแชร์ในตัวจะเปิดขึ้น เช่น ฉันเลือก Gmail แล้ววิดเจ็ตเครื่องมือแต่งอีเมลจะปรากฏขึ้นพร้อมกับ รูปภาพที่แนบมา


Contact Picker API
ต่อไปเราจะพูดถึงรายชื่อติดต่อ ซึ่งหมายถึงสมุดที่อยู่ของอุปกรณ์ หรือแอปจัดการรายชื่อติดต่อ เมื่อเขียนการ์ดอวยพร คุณอาจเขียนชื่อของใครบางคน ให้ถูกต้องได้ไม่เสมอไป เช่น ฉันมีเพื่อนชื่อ Sergey ที่ชอบให้สะกดชื่อของเขาด้วยตัวอักษรซิริลลิก I'm using a German QWERTZ keyboard and have no idea how to type their name. Contact Picker API ช่วยแก้ปัญหานี้ได้ เนื่องจากฉันบันทึกเพื่อนไว้ในแอปรายชื่อติดต่อของโทรศัพท์ ฉันจึงใช้ Contacts Picker API เพื่อเข้าถึงรายชื่อติดต่อจากเว็บได้
ก่อนอื่น ฉันต้องระบุรายการพร็อพเพอร์ตี้ที่ต้องการเข้าถึง
ในกรณีนี้ ฉันต้องการเฉพาะชื่อ
แต่สำหรับกรณีการใช้งานอื่นๆ ฉันอาจสนใจหมายเลขโทรศัพท์ อีเมล รูปโปรไฟล์
ไอคอน หรือที่อยู่จริง
จากนั้นฉันจะกำหนดค่าออบเจ็กต์ options
และตั้งค่า multiple
เป็น true
เพื่อให้เลือกรายการได้มากกว่า 1 รายการ
สุดท้าย ฉันสามารถเรียกใช้ navigator.contacts.select()
ซึ่งจะแสดงพร็อพเพอร์ตี้ที่เหมาะสม
สำหรับรายชื่อติดต่อที่ผู้ใช้เลือก
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);
}
};
และตอนนี้คุณอาจทราบรูปแบบแล้วว่า ฉันจะโหลดไฟล์เมื่อ API รองรับจริงๆ เท่านั้น
if ('contacts' in navigator) {
import('./contacts.mjs');
}
ใน Fugu Greeting เมื่อฉันแตะปุ่มรายชื่อติดต่อและเลือกเพื่อนสนิท 2 คน Сергей Михайлович Брин และ 劳伦斯·爱德华·"拉里"·佩奇 คุณจะเห็นว่า เครื่องมือเลือกรายชื่อติดต่อจำกัดให้แสดงเฉพาะชื่อ แต่ไม่แสดงอีเมลหรือข้อมูลอื่นๆ เช่น หมายเลขโทรศัพท์ จากนั้นเราจะวาดชื่อของบุคคลดังกล่าวลงในการ์ดอวยพร


Asynchronous Clipboard API
ถัดไปคือการคัดลอกและวาง การคัดลอกและวางเป็นหนึ่งในการดำเนินการที่เราชอบมากที่สุดในฐานะนักพัฒนาซอฟต์แวร์ ในฐานะผู้เขียนการ์ดอวยพร บางครั้งฉันก็อาจต้องการทำเช่นเดียวกัน ฉันอาจต้องการวางรูปภาพลงในการ์ดอวยพรที่กำลังทำอยู่ หรือคัดลอกการ์ดอวยพรเพื่อแก้ไขต่อจากที่อื่น Async Clipboard API รองรับทั้งข้อความและรูปภาพ มาดูวิธีเพิ่มการรองรับการคัดลอกและวางในแอป Fugu Greetings กัน
หากต้องการคัดลอกบางอย่างไปยังคลิปบอร์ดของระบบ ฉันต้องเขียนลงในคลิปบอร์ด
เมธอด navigator.clipboard.write()
จะใช้อาร์เรย์ของรายการในคลิปบอร์ดเป็นพารามิเตอร์
โดยพื้นฐานแล้วแต่ละรายการในคลิปบอร์ดคือออบเจ็กต์ที่มี Blob เป็นค่า และมีประเภทของ Blob
เป็นคีย์
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
หากต้องการวาง ฉันต้องวนซ้ำรายการในคลิปบอร์ดที่ได้รับจากการเรียกใช้
navigator.clipboard.read()
สาเหตุที่เป็นเช่นนี้เนื่องจากอาจมีรายการในคลิปบอร์ดหลายรายการในคลิปบอร์ดใน
การแสดงที่แตกต่างกัน
รายการในคลิปบอร์ดแต่ละรายการมีฟิลด์ types
ที่บอกประเภท MIME ของทรัพยากรที่ใช้ได้
ฉันเรียกใช้เมธอด getType()
ของรายการในคลิปบอร์ด โดยส่งประเภท MIME ที่ฉันได้รับก่อนหน้านี้
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);
}
};
และตอนนี้ก็แทบไม่ต้องพูดถึงแล้ว ฉันจะทำสิ่งนี้ในเบราว์เซอร์ที่รองรับเท่านั้น
if ('clipboard' in navigator && 'write' in navigator.clipboard) {
import('./clipboard.mjs');
}
แล้วในทางปฏิบัติฟีเจอร์นี้ทำงานอย่างไร ฉันเปิดรูปภาพในแอปแสดงตัวอย่างของ macOS และ คัดลอกไปยังคลิปบอร์ด เมื่อคลิกวาง แอป Fugu Greetings จะถามฉัน ว่าต้องการอนุญาตให้แอปดูข้อความและรูปภาพในคลิปบอร์ดหรือไม่

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

Badging API
API อีกตัวที่ควรทราบคือ Badging API
แน่นอนว่า Fugu Greetings ในฐานะ PWA ที่ติดตั้งได้มีไอคอนแอป
ที่ผู้ใช้วางไว้ในแถบแอปหรือหน้าจอหลักได้
วิธีที่สนุกในการสาธิต API คือการใช้ API ใน Fugu Greetings
เป็นตัวนับลายเส้น
ฉันได้เพิ่ม Listener เหตุการณ์ที่จะเพิ่มตัวนับการลากปากกาเมื่อใดก็ตามที่เกิดเหตุการณ์ pointerdown
แล้วตั้งค่าป้ายไอคอนที่อัปเดต
ทุกครั้งที่ล้าง Canvas ตัวนับจะรีเซ็ตและระบบจะนำป้ายออก
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
ฟีเจอร์นี้เป็นการปรับปรุงแบบค่อยเป็นค่อยไป ดังนั้นตรรกะการโหลดจึงเป็นไปตามปกติ
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
ในตัวอย่างนี้ ฉันวาดตัวเลข 1-7 โดยใช้การลากปากกา 1 ครั้ง ต่อตัวเลข 1 ตัว ตอนนี้ตัวนับป้ายบนไอคอนอยู่ที่ 7


Periodic Background Sync API
หากต้องการเริ่มต้นวันใหม่ด้วยสิ่งใหม่ๆ ฟีเจอร์ที่ยอดเยี่ยมของแอป Fugu Greetings คือแอปสามารถสร้างแรงบันดาลใจให้คุณได้ทุกเช้า ด้วยภาพพื้นหลังใหม่เพื่อเริ่มต้นการ์ดอวยพร แอปใช้ Periodic Background Sync API เพื่อให้บรรลุเป้าหมายนี้
ขั้นตอนแรกคือลงทะเบียนเหตุการณ์การซิงค์เป็นระยะใน Service Worker
registration โดยจะรอรับแท็กการซิงค์ที่ชื่อ 'image-of-the-day'
และมีช่วงเวลาขั้นต่ำ 1 วัน
เพื่อให้ผู้ใช้ได้รับภาพพื้นหลังใหม่ทุกๆ 24 ชั่วโมง
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);
}
};
ขั้นตอนที่ 2 คือการฟังเหตุการณ์ periodicsync
ใน Service Worker
หากแท็กเหตุการณ์คือ 'image-of-the-day'
ซึ่งเป็นแท็กที่ลงทะเบียนไว้ก่อนหน้านี้
ระบบจะดึงรูปภาพของวันนั้นด้วยฟังก์ชัน getImageOfTheDay()
และเผยแพร่ผลลัพธ์ไปยังไคลเอ็นต์ทั้งหมดเพื่อให้ไคลเอ็นต์อัปเดต Canvas และ
แคชได้
self.addEventListener('periodicsync', (syncEvent) => {
if (syncEvent.tag === 'image-of-the-day-sync') {
syncEvent.waitUntil(
(async () => {
const blob = await getImageOfTheDay();
const clients = await self.clients.matchAll();
clients.forEach((client) => {
client.postMessage({
image: blob,
});
});
})()
);
}
});
อีกครั้งที่นี่เป็นการเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปอย่างแท้จริง ดังนั้นระบบจะโหลดโค้ดเมื่อเบราว์เซอร์รองรับ API เท่านั้น
ซึ่งจะมีผลกับทั้งโค้ดไคลเอ็นต์และโค้ด Service Worker
ในเบราว์เซอร์ที่ไม่รองรับ ระบบจะไม่โหลดทั้ง 2 อย่าง
โปรดสังเกตว่าใน Service Worker แทนที่จะใช้ import()
แบบไดนามิก
(ซึ่งยังไม่รองรับในบริบทของ Service Worker)
ฉันใช้ importScripts()
แบบคลาสสิก
// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
importScripts('./image_of_the_day.mjs');
}
ใน Fugu Greetings การกดปุ่มวอลเปเปอร์จะแสดงภาพการ์ดอวยพร ประจำวันซึ่งอัปเดตทุกวันด้วย Periodic Background Sync API

Notification Triggers API
บางครั้งแม้จะมีแรงบันดาลใจมากเพียงใด คุณก็อาจต้องได้รับการกระตุ้นให้ทำการ์ดอวยพรที่เริ่มไว้ให้เสร็จ ฟีเจอร์นี้เปิดใช้โดย Notification Triggers API ในฐานะผู้ใช้ ฉันสามารถป้อนเวลาที่ต้องการให้ระบบช่วยเตือนให้ทำการ์ดอวยพรให้เสร็จได้ เมื่อถึงเวลาดังกล่าว ฉันจะได้รับการแจ้งเตือนว่ามีการ์ดอวยพรที่รออยู่
หลังจากแจ้งเวลาเป้าหมายแล้ว
แอปพลิเคชันจะตั้งเวลาการแจ้งเตือนด้วย showTrigger
ซึ่งอาจเป็นTimestampTrigger
ที่มีวันที่เป้าหมายที่เลือกไว้ก่อนหน้านี้
การแจ้งเตือนการช่วยเตือนจะทริกเกอร์ในเครื่อง ไม่จำเป็นต้องใช้เครือข่ายหรือฝั่งเซิร์ฟเวอร์
const targetDate = promptTargetDate();
if (targetDate) {
const registration = await navigator.serviceWorker.ready;
registration.showNotification('Reminder', {
tag: 'reminder',
body: "It's time to finish your greeting card!",
showTrigger: new TimestampTrigger(targetDate),
});
}
เช่นเดียวกับทุกอย่างที่ฉันได้แสดงไปแล้ว นี่คือการเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไป ดังนั้นระบบจะโหลดโค้ดแบบมีเงื่อนไขเท่านั้น
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
เมื่อเลือกช่องทำเครื่องหมายการช่วยเตือนใน Fugu Greetings ระบบจะแสดงข้อความแจ้งเพื่อถามฉันว่าต้องการให้ระบบช่วยเตือนเมื่อใดเพื่อทำโปสการ์ดให้เสร็จ

เมื่อการแจ้งเตือนที่กำหนดเวลาไว้ทริกเกอร์ใน Fugu Greetings การแจ้งเตือนจะแสดงเหมือนกับการแจ้งเตือนอื่นๆ แต่ตามที่ฉันเขียนไว้ก่อนหน้านี้ การแจ้งเตือนนี้ไม่จำเป็นต้องเชื่อมต่อเครือข่าย

Wake Lock API
ฉันต้องการรวม Wake Lock API ด้วย บางครั้งคุณเพียงแค่มองหน้าจอเป็นเวลานานพอจนกว่าแรงบันดาลใจจะมาหาคุณ สิ่งที่แย่ที่สุดที่อาจเกิดขึ้นคือหน้าจอจะปิด Wake Lock API ช่วยป้องกันไม่ให้เกิดเหตุการณ์นี้ได้
ขั้นตอนแรกคือการขอ Wake Lock ด้วย navigator.wakelock.request method()
ฉันส่งสตริง 'screen'
ไปยังฟังก์ชันนี้เพื่อรับ Wake Lock หน้าจอ
จากนั้นฉันก็เพิ่ม Listener เหตุการณ์เพื่อให้ระบบแจ้งเตือนเมื่อมีการปล่อย Wake Lock
ซึ่งอาจเกิดขึ้นได้ เช่น เมื่อระดับการมองเห็นของแท็บเปลี่ยนไป
หากเกิดกรณีนี้ขึ้น เมื่อแท็บกลับมาแสดงอีกครั้ง ฉันจะขอ Wake Lock อีกครั้งได้
let wakeLock = null;
const requestWakeLock = async () => {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Wake Lock was released');
});
console.log('Wake Lock is active');
};
const handleVisibilityChange = () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
requestWakeLock();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);
ใช่ นี่เป็นการเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไป ดังนั้นฉันจึงต้องโหลดเฉพาะเมื่อเบราว์เซอร์ รองรับ API
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
import('./wake_lock.mjs');
}
ใน Fugu Greetings มีช่องทำเครื่องหมาย Insomnia ซึ่งเมื่อเลือกไว้จะทำให้หน้าจอเปิดอยู่

Idle Detection API
บางครั้งแม้ว่าคุณจะจ้องหน้าจอเป็นเวลาหลายชั่วโมง ก็ไม่มีประโยชน์และคุณก็คิดไม่ออกว่าจะทำอะไรกับการ์ดอวยพร Idle Detection API อนุญาตให้แอปตรวจหาเวลาที่ผู้ใช้ไม่ได้ใช้งาน หากผู้ใช้ไม่ได้ใช้งานนานเกินไป แอปจะรีเซ็ตเป็นสถานะเริ่มต้น และล้าง Canvas API นี้จะอยู่ภายใต้สิทธิ์การแจ้งเตือน เนื่องจากกรณีการใช้งานจริงหลายกรณีของการตรวจหาการไม่ได้ใช้งานเกี่ยวข้องกับการแจ้งเตือน เช่น เพื่อส่งการแจ้งเตือนไปยังอุปกรณ์ที่ผู้ใช้ใช้งานอยู่เท่านั้น
หลังจากตรวจสอบว่าได้รับสิทธิ์การแจ้งเตือนแล้ว ฉันก็สร้างอินสแตนซ์ของ เครื่องตรวจจับการไม่มีการใช้งาน ฉันลงทะเบียน Listener เหตุการณ์ที่รอการเปลี่ยนแปลงสถานะว่าง ซึ่งรวมถึงสถานะผู้ใช้และหน้าจอ ผู้ใช้อาจใช้งานอยู่หรือไม่ได้ใช้งาน และหน้าจออาจปลดล็อกหรือล็อกอยู่ หากผู้ใช้ไม่มีการใช้งาน ระบบจะล้าง Canvas ฉันให้ค่าเกณฑ์ของตัวตรวจจับการไม่มีการใช้งานเป็น 60 วินาที
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
const userState = idleDetector.userState;
const screenState = idleDetector.screenState;
console.log(`Idle change: ${userState}, ${screenState}.`);
if (userState === 'idle') {
clearCanvas();
}
});
await idleDetector.start({
threshold: 60000,
signal,
});
และเช่นเคย ฉันจะโหลดโค้ดนี้เฉพาะเมื่อเบราว์เซอร์รองรับเท่านั้น
if ('IdleDetector' in window) {
import('./idle_detection.mjs');
}
ในแอป Fugu Greetings Canvas จะล้างเมื่อเลือกช่องทำเครื่องหมายชั่วคราวและผู้ใช้ไม่ได้ใช้งานนานเกินไป

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

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



คุณสามารถ Fork Fugu ใน GitHub ได้
ทีม Chromium กำลังพยายามอย่างเต็มที่เพื่อพัฒนา API ของ Fugu ขั้นสูง การใช้การเพิ่มประสิทธิภาพแบบค่อยเป็นค่อยไปเมื่อสร้างแอป ช่วยให้มั่นใจได้ว่าทุกคนจะได้รับประสบการณ์พื้นฐานที่ดีและมั่นคง แต่ผู้ที่ใช้เบราว์เซอร์ที่รองรับ API ของแพลตฟอร์มเว็บเพิ่มเติมจะได้รับประสบการณ์ที่ดียิ่งขึ้น เราหวังว่าจะได้เห็นสิ่งที่คุณทำกับ Progressive Enhancement ในแอป
คำขอบคุณ
ขอขอบคุณ Christian Liebel และ
Hemanth HM ที่มีส่วนร่วมในการสร้าง Fugu Greetings
เอกสารนี้ได้รับการตรวจสอบโดย Joe Medley และ
Kayce Basques
Jake Archibald ช่วยฉันค้นหาสถานการณ์
ที่มี import()
แบบไดนามิกในบริบทของ Service Worker