การสร้างสําหรับเบราว์เซอร์สมัยใหม่และการเพิ่มประสิทธิภาพอย่างต่อเนื่องเหมือนเป็นปี 2003
เมื่อเดือนมีนาคม 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
และอื่นๆ อีกมากมาย
รองรับทั้งหมด
ฟังก์ชัน Async ซึ่งเป็นฟีเจอร์ของ ES 2017 และเป็นหนึ่งในฟีเจอร์โปรดของฉันใช้ได้ในเบราว์เซอร์หลักทุกรุ่น
คีย์เวิร์ด async
และ await
ช่วยให้เขียนลักษณะการทำงานแบบแอซิงโครนัสที่อิงตามสัญญาในสไตล์ที่สะอาดขึ้นได้โดยไม่ต้องกำหนดค่าเชนสัญญาอย่างชัดเจน
และแม้แต่ภาษา 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 ให้คำจำกัดความแนวคิดนี้ดังนี้
การปรับปรุงแบบเป็นขั้นเป็นตอนเป็นปรัชญาการออกแบบที่ระบุพื้นฐานของเนื้อหาและฟังก์ชันการทํางานที่จําเป็นสําหรับผู้ใช้จํานวนมากที่สุดเท่าที่จะเป็นไปได้ พร้อมกับมอบประสบการณ์การใช้งานที่ดีที่สุดแก่ผู้ใช้เบราว์เซอร์ที่ทันสมัยที่สุดซึ่งสามารถเรียกใช้โค้ดที่จําเป็นทั้งหมดได้เท่านั้น
โดยทั่วไปแล้วการตรวจหาฟีเจอร์จะใช้เพื่อระบุว่าเบราว์เซอร์จัดการฟังก์ชันการทำงานที่ทันสมัยมากขึ้นได้หรือไม่ ส่วนโพลีฟิลล์มักใช้เพื่อเพิ่มฟีเจอร์ที่ขาดหายไปด้วย JavaScript
[…]
การเพิ่มประสิทธิภาพแบบต่อเนื่องเป็นเทคนิคที่มีประโยชน์ที่ช่วยให้นักพัฒนาเว็บมุ่งเน้นการพัฒนาเว็บไซต์ที่ดีที่สุดเท่าที่จะเป็นไปได้ ในขณะที่ทำให้เว็บไซต์เหล่านั้นทำงานกับ User Agent ที่ไม่รู้จักหลายรายการ การลดระดับอย่างราบรื่นนั้นมีความเกี่ยวข้องกัน แต่ไม่ใช่สิ่งเดียวกัน และมักถูกมองว่าเป็นการดำเนินการที่สวนทางกับการปรับปรุงแบบเป็นขั้นเป็นตอน ในความเป็นจริง ทั้ง 2 วิธีนั้นถูกต้องและมักจะช่วยเสริมซึ่งกันและกันได้
ผู้มีส่วนร่วม MDN
การเริ่มต้นการ์ดอวยพรแต่ละใบตั้งแต่ต้นอาจเป็นเรื่องยุ่งยาก
ดังนั้นทำไมถึงไม่สร้างฟีเจอร์ที่อนุญาตให้ผู้ใช้นำเข้ารูปภาพและเริ่มดำเนินการต่อจากตรงนั้น
แนวทางแบบดั้งเดิมคือคุณต้องใช้องค์ประกอบ <input type=file>
เพื่อดำเนินการนี้
ก่อนอื่น คุณจะต้องสร้างองค์ประกอบ ตั้งค่า type
เป็น 'file'
และเพิ่มประเภท MIME ลงในพร็อพเพอร์ตี้ accept
จากนั้นจึง "คลิก" องค์ประกอบและฟังการเปลี่ยนแปลงแบบเป็นโปรแกรม
เมื่อเลือกรูปภาพ ระบบจะนำเข้ารูปภาพนั้นลงในผืนผ้าใบโดยตรง
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();
});
};
เมื่อมีฟีเจอร์นำเข้า ก็ควรมีฟีเจอร์ส่งออกด้วยเพื่อให้ผู้ใช้บันทึกการ์ดอวยพรไว้ในเครื่องได้
วิธีที่เก่าแก่ในการบันทึกไฟล์คือการสร้างลิงก์แอตทริบิวต์ 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 ในขณะนี้ คุณดูแท็บเครือข่ายของ Firefox และ Safari ได้ที่ด้านล่าง
อย่างไรก็ตาม ใน 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 ฉันสามารถเปิดไฟล์ได้เหมือนเดิม ไฟล์ที่นำเข้าจะถูกวาดลงบนผืนผ้าใบ ฉันสามารถแก้ไขและบันทึกไฟล์ด้วยกล่องโต้ตอบการบันทึกจริงได้ ซึ่งฉันสามารถเลือกชื่อและตำแหน่งการจัดเก็บของไฟล์ได้ ตอนนี้ไฟล์ก็พร้อมที่จะเก็บไว้ตลอดกาลแล้ว
API เป้าหมายของการแชร์เว็บและการแชร์เว็บ
นอกจากเก็บไว้ตลอดไปแล้ว ฉันอาจต้องการแชร์การ์ดอวยพรด้วย การดำเนินการนี้เป็นสิ่งที่ Web Share API และ Web Share Target API อนุญาตให้ฉันทำได้ ระบบปฏิบัติการบนอุปกรณ์เคลื่อนที่และเดสก์ท็อปในปัจจุบันมีกลไกการแชร์ในตัว ตัวอย่างเช่น ด้านล่างนี้คือชีตการแชร์ของ Safari บนเดสก์ท็อปใน macOS ที่เรียกให้แสดงจากบทความในบล็อกของฉัน เมื่อคลิกปุ่มแชร์บทความ คุณจะแชร์ลิงก์ไปยังบทความกับเพื่อนได้ เช่น ผ่านแอปรับส่งข้อความของ macOS
โค้ดที่ใช้ดำเนินการนี้ค่อนข้างตรงไปตรงมา ฉันเรียกใช้ navigator.share()
และส่ง title
, text
และ url
ที่ไม่บังคับในออบเจ็กต์
แต่หากฉันต้องการแนบรูปภาพล่ะ ระดับ 1 ของ Web Share API ยังไม่รองรับการดำเนินการนี้
ข่าวดีคือ 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 บนอุปกรณ์เคลื่อนที่ที่เป็นไปตามเงื่อนไขข้อใดข้อหนึ่งเท่านั้น เราจะไม่โหลดฟังก์ชันการทำงาน
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
ใน Fugu Greetings หากฉันแตะปุ่มแชร์ในเบราว์เซอร์ที่รองรับ เช่น Chrome ใน Android ชีตการแชร์ในตัวจะเปิดขึ้น เช่น ฉันเลือก Gmail แล้ววิดเจ็ตเครื่องมือเขียนอีเมลจะปรากฏขึ้นพร้อมแนบรูปภาพ
Contact Picker API
ต่อไปเราจะพูดถึงรายชื่อติดต่อ ซึ่งหมายถึงสมุดที่อยู่ของอุปกรณ์หรือแอปตัวจัดการรายชื่อติดต่อ เมื่อเขียนการ์ดอวยพร การเขียนชื่อให้ถูกต้องอาจไม่ใช่เรื่องง่ายเสมอไป เช่น ฉันมีเพื่อนชื่อ Sergey ที่ต้องการให้สะกดชื่อเป็นอักษรซีริลลิก ฉันใช้แป้นพิมพ์ QWERTZ ของเยอรมันและไม่รู้วิธีพิมพ์ชื่อ ปัญหานี้แก้ไขได้ด้วย 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
ในฐานะ PWA ที่ติดตั้งได้ Fugu Greetings จึงมีไอคอนแอปที่ผู้ใช้วางไว้ในแท่นชาร์จแอปหรือหน้าจอหลักได้
วิธีสนุกๆ และง่ายในการสาธิต 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 ครั้งต่อตัวเลข ตอนนี้ตัวนับป้ายบนไอคอนอยู่ที่ 7
Periodic Background Sync API
หากต้องการเริ่มต้นวันใหม่ด้วยสิ่งใหม่ๆ ฟีเจอร์ที่น่าสนใจของแอป Fugu Greetings คือแอปสามารถช่วยสร้างแรงบันดาลใจให้คุณในทุกเช้าด้วยภาพพื้นหลังใหม่เพื่อเริ่มต้นการ์ดอวยพร แอปใช้ Periodic Background Sync API เพื่อดำเนินการนี้
ขั้นตอนแรกคือลงทะเบียนเหตุการณ์การซิงค์เป็นระยะในการลงทะเบียน Service Worker
โดยจะคอยฟังแท็กการซิงค์ชื่อ '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 เท่านั้น
ซึ่งจะมีผลกับทั้งรหัสไคลเอ็นต์และรหัสโปรแกรมทำงานของบริการ
ระบบจะไม่โหลดเบราว์เซอร์ใดในเบราว์เซอร์ที่ไม่รองรับ
สังเกตวิธีการในการทำงานของโปรแกรมทำงานของบริการ แทนที่จะเป็นการทำงานแบบไดนามิก 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
เหตุการณ์นี้อาจเกิดขึ้นได้ เช่น เมื่อระดับการแชร์แท็บมีการเปลี่ยนแปลง
หากเกิดกรณีเช่นนี้ เมื่อแท็บปรากฏขึ้นอีกครั้ง ฉันจะรับการล็อกการปลุกอีกครั้งได้
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 จะมีช่องทำเครื่องหมายนอนไม่หลับ ซึ่งเมื่อเลือกตัวเลือกนี้จะเป็นการเปิดหน้าจอค้างไว้
Idle Detection API
ในบางครั้ง แม้ว่าคุณจะจ้องไปที่หน้าจอเป็นเวลาหลายชั่วโมง ก็คงจะไร้ประโยชน์ และคุณไม่สามารถคิดแม้แต่นิดเดียวว่าจะทำอะไรกับการ์ดอวยพรของคุณ Idle Detection API ช่วยให้แอปตรวจจับเวลาที่ไม่ได้ใช้งานของผู้ใช้ หากผู้ใช้ไม่มีการใช้งานนานเกินไป แอปจะรีเซ็ตเป็นสถานะเริ่มต้นและล้าง Canvas ปัจจุบัน API นี้อยู่ภายใต้สิทธิ์การแจ้งเตือนเนื่องจาก Use Case จำนวนมากในเวอร์ชันที่ใช้งานจริงของการตรวจหาการใช้งานอยู่เกี่ยวข้องกับการแจ้งเตือน เช่น เพื่อส่งการแจ้งเตือนไปยังอุปกรณ์ที่ผู้ใช้ใช้งานอยู่เท่านั้น
หลังจากตรวจสอบว่าได้ให้สิทธิ์การแจ้งเตือนแล้ว เราจะสร้างอินสแตนซ์ของโปรแกรมตรวจหาการใช้งานอยู่ ฉันจะลงทะเบียน Listener เหตุการณ์ที่รอฟังการเปลี่ยนแปลงเมื่อไม่มีการใช้งาน ซึ่งรวมถึงผู้ใช้และสถานะหน้าจอ ผู้ใช้อาจใช้งานอยู่หรือไม่ได้ใช้งาน และหน้าจออาจปลดล็อกหรือล็อกอยู่ หากผู้ใช้ไม่มีความเคลื่อนไหว แคนวาสจะล้างข้อมูล ฉันกำหนดเกณฑ์ให้ตัวตรวจจับไม่มีการใช้งานไว้ที่ 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 ภาพวาดแคนวาสจะล้างออกเมื่อเลือกช่องทำเครื่องหมายชั่วคราวไว้และผู้ใช้ไม่ได้ใช้งานเป็นเวลานานเกินไป
เปิดจากขอบ
ว้าว อะไรจะขนาดนั้น API มากมายขนาดนี้ในตัวอย่างแอปเพียงแอปเดียว และอย่าลืมว่าเราไม่เคยเรียกเก็บค่าดาวน์โหลดจากผู้ใช้สำหรับฟีเจอร์ที่เบราว์เซอร์ของผู้ใช้ไม่รองรับ การใช้การเพิ่มประสิทธิภาพแบบต่อเนื่องช่วยให้มั่นใจได้ว่าระบบจะโหลดเฉพาะโค้ดที่เกี่ยวข้อง และเนื่องจาก HTTP/2 คำขอต่างๆ มีราคาถูก รูปแบบนี้จึงควรใช้ได้ดีสำหรับแอปพลิเคชันจำนวนมาก แต่คุณอาจต้องพิจารณาใช้ Bundler สำหรับแอปขนาดใหญ่จริงๆ ด้วย
แอปอาจดูแตกต่างออกไปเล็กน้อยในเบราว์เซอร์แต่ละประเภท เนื่องจากแพลตฟอร์มบางแพลตฟอร์มไม่รองรับฟีเจอร์บางรายการ แต่ฟังก์ชันหลักจะยังคงอยู่เสมอ โดยได้รับการปรับปรุงอย่างต่อเนื่องตามความสามารถของเบราว์เซอร์แต่ละประเภท โปรดทราบว่าความสามารถเหล่านี้อาจเปลี่ยนแปลงได้แม้ในเบราว์เซอร์เดียวกัน ทั้งนี้ขึ้นอยู่กับว่าแอปทำงานเป็นแอปที่ติดตั้งหรือในแท็บเบราว์เซอร์
หากสนใจแอป Fugu Greetings ให้ค้นหาและแยกแอปนี้ใน GitHub
ทีม Chromium กำลังพยายามอย่างเต็มที่เพื่อทำให้หญ้าเขียวขึ้นเมื่อพูดถึง Fugu API ขั้นสูง การใช้การปรับปรุงแบบเป็นขั้นเป็นตอนในการพัฒนาแอปช่วยให้มั่นใจได้ว่าทุกคนจะได้รับประสบการณ์การใช้งานพื้นฐานที่ดีและมั่นคง แต่ผู้ใช้เบราว์เซอร์ที่รองรับ API ของแพลตฟอร์มเว็บจำนวนมากขึ้นจะได้รับประสบการณ์การใช้งานที่ดีขึ้นไปอีก ผมจะตั้งตารอดูการใช้งานการเพิ่มประสิทธิภาพแบบต่อเนื่องในแอปของคุณ
กิตติกรรมประกาศ
ขอขอบคุณ Christian Liebel และ Hemanth HM ที่ได้มีส่วนร่วมในฟีเจอร์คําอวยพรของ Fugu
บทความนี้ผ่านการตรวจสอบโดย Joe Medley และ Kayce Basques
Jake Archibald ช่วยให้ฉันทราบสถานการณ์เกี่ยวกับimport()
แบบไดนามิกในบริบทของ Service Worker