สร้างขึ้นเพื่อเบราว์เซอร์ที่ทันสมัยและเพิ่มประสิทธิภาพอย่างต่อเนื่องเหมือนกับปี 2003
ย้อนกลับไปในเดือนมีนาคม 2003 Nick Finck และ Steve Champeon สร้างความตื่นตะลึงให้กับโลกแห่งการออกแบบเว็บโดยมีแนวคิดการเพิ่มประสิทธิภาพแบบก้าวหน้า ซึ่งเป็นกลยุทธ์สำหรับการออกแบบเว็บที่เน้นการโหลดเนื้อหาหน้าเว็บหลักก่อน จากนั้นจึงค่อยๆ เพิ่มเลเยอร์การนำเสนอและฟีเจอร์ที่เข้มงวดขึ้นในส่วนของเนื้อหา ขณะที่ในปี 2003 การเพิ่มประสิทธิภาพแบบก้าวหน้าคือการใช้ฟีเจอร์ CSS ที่ทันสมัย, JavaScript ที่ไม่ก่อให้เกิดความรำคาญ และแม้กระทั่ง Vector Graphics ที่ปรับขนาดได้ การเพิ่มประสิทธิภาพแบบก้าวหน้าในปี 2020 และปีต่อๆ ไปคือการใช้ความสามารถที่ทันสมัยของเบราว์เซอร์
![การออกแบบเว็บที่ไม่แบ่งแยกเพื่ออนาคตพร้อมการเพิ่มประสิทธิภาพแบบต่อเนื่อง สไลด์ชื่อจากงานนำเสนอต้นฉบับของ Finck และ Champeon](https://web.dev/static/articles/progressively-enhance-your-pwa/image/inclusive-web-design-the-8dd761f5f7ef6.png?authuser=2&hl=th)
JavaScript สมัยใหม่
เมื่อพูดถึง JavaScript แล้ว สถานการณ์การรองรับเบราว์เซอร์สำหรับฟีเจอร์ JavaScript หลักล่าสุดของ ES 2015 นั้นยอดเยี่ยมมาก
มาตรฐานใหม่รวมถึงคำสัญญา โมดูล คลาส ลิเทอรัลเทมเพลต ฟังก์ชันลูกศร let
และ const
พารามิเตอร์เริ่มต้น เครื่องมือสร้าง การกำหนดลดโครงสร้าง การพักและการกระจาย Map
/Set
WeakMap
/WeakSet
และอื่นๆ อีกมากมาย
ทั้งหมดรองรับ
![ตารางการสนับสนุน CanIUse สำหรับฟีเจอร์ ES6 ที่แสดงการสนับสนุนในเบราว์เซอร์หลักๆ ทั้งหมด](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-caniuse-support-table-fea500f90ffcf.png?authuser=2&hl=th)
ฟังก์ชันอะซิงโครนัส ซึ่งเป็นฟีเจอร์สำหรับ ES 2017 และหนึ่งในรายการโปรดส่วนตัวที่ผมใช้ได้
ในเบราว์เซอร์หลักๆ ทั้งหมด
คีย์เวิร์ด async
และ await
ช่วยให้เขียนลักษณะการทำงานแบบไม่พร้อมกันและเป็นไปตามสัญญาได้อย่างสะอาดตา โดยไม่จำเป็นต้องกำหนดค่า Prompt Chain มากเกินไป
![ตารางการสนับสนุน CanIUse สำหรับฟังก์ชันอะซิงโครนัสที่แสดงการสนับสนุนในเบราว์เซอร์หลักๆ ทั้งหมด](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-caniuse-support-table-bec0941fba39c.png?authuser=2&hl=th)
แม้แต่ส่วนเพิ่มเติมล่าสุดในภาษา ES 2020 ที่เพิ่มเข้ามา เช่น ตัวเลือกการทำเชน (ไม่บังคับ) และการรวมข้อมูลแบบ Nullish ก็เข้าถึงการสนับสนุนได้อย่างรวดเร็วมาก คุณดูตัวอย่างโค้ดได้ที่ด้านล่าง เมื่อพูดถึงฟีเจอร์หลักของ JavaScript หญ้าคงจะเป็นสีเขียวกว่าที่เป็นอยู่ตอนนี้
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
![ภาพพื้นหลังสนามหญ้าสีเขียวของ Windows XP ที่เป็นเอกลักษณ์](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-iconic-windows-xp-gre-52c72bfaafb9d.png?authuser=2&hl=th)
แอปตัวอย่าง: คำทักทาย Fugu
ในบทความนี้ เราทํางานร่วมกับ PWA แบบง่ายที่เรียกว่า Fugu Greetings (GitHub) ชื่อของแอปนี้เป็นเคล็ดลับสำคัญของ Project Fugu 🐡 ซึ่งเป็นความพยายามที่จะทำให้เว็บ มีความสามารถทั้งหมดของแอปพลิเคชัน Android/iOS/เดสก์ท็อป อ่านข้อมูลเพิ่มเติมเกี่ยวกับโปรเจ็กต์ได้ในหน้า Landing Page
Fugu Greetings เป็นแอปวาดภาพที่ช่วยให้คุณสร้างบัตรอวยพรเสมือนจริงและส่ง บัตรอวยพรให้คนที่คุณรัก โดยยกตัวอย่างแนวคิดหลักของ PWA บริการนี้เชื่อถือได้และเปิดใช้งานแบบออฟไลน์อย่างสมบูรณ์ ดังนั้นแม้ว่าคุณจะไม่มีเครือข่าย คุณก็ยังใช้เครือข่ายนั้นได้ นอกจากนี้ยังสามารถติดตั้งได้ลงในหน้าจอหลักของอุปกรณ์ และผสานรวมกับระบบปฏิบัติการเป็นแอปพลิเคชันแบบสแตนด์อโลนได้อย่างราบรื่น
![Fugu Greetings PWA ที่มีภาพวาดคล้ายโลโก้ชุมชน PWA](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-pwa-a-dra-964a41cbe2963.png?authuser=2&hl=th)
การเพิ่มประสิทธิภาพแบบต่อเนื่อง
คราวนี้ก็ได้เวลาพูดถึงการเพิ่มประสิทธิภาพแบบก้าวหน้ากันแล้ว อภิธานศัพท์เว็บเอกสาร MDN ได้ให้แนวคิดต่อไปนี้
การเพิ่มประสิทธิภาพแบบต่อเนื่องคือปรัชญาการออกแบบที่ให้ข้อมูลพื้นฐานเกี่ยวกับเนื้อหาและฟังก์ชันการทำงานที่จำเป็นแก่ผู้ใช้จำนวนมากที่สุดเท่าที่จะเป็นไปได้ ขณะเดียวกันก็มอบประสบการณ์ที่ดีที่สุดเท่าที่เป็นไปได้แก่ผู้ใช้เบราว์เซอร์ที่ทันสมัยที่สุดซึ่งเรียกใช้โค้ดที่จำเป็นทั้งหมดได้เท่านั้น
โดยทั่วไปแล้ว การตรวจหาฟีเจอร์จะใช้เพื่อพิจารณาว่าเบราว์เซอร์จะจัดการฟังก์ชันการทำงานที่ทันสมัยกว่าได้หรือไม่ ขณะที่ polyfills มักจะใช้เพื่อเพิ่มฟีเจอร์ที่ขาดหายไปด้วย 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();
});
};
เมื่อมีฟีเจอร์import ควรมีฟีเจอร์importเพื่อให้ผู้ใช้บันทึกการ์ดอวยพรของตนไว้ในเครื่องได้
วิธีการบันทึกไฟล์แบบดั้งเดิมคือการสร้างลิงก์ 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 ในขณะนี้ คุณสามารถดูแท็บเครือข่ายของ Firefox และ Safari ได้ที่ด้านล่าง
![ตัวตรวจสอบเว็บของ Safari แสดงการโหลดไฟล์เดิม](https://web.dev/static/articles/progressively-enhance-your-pwa/image/safari-web-inspector-show-38b32feb7b4f.png?authuser=2&hl=th)
![เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ Firefox ที่แสดงการโหลดไฟล์เดิม](https://web.dev/static/articles/progressively-enhance-your-pwa/image/firefox-developer-tools-s-60e8cb93aaa6b.png?authuser=2&hl=th)
อย่างไรก็ตาม ใน Chrome ซึ่งเป็นเบราว์เซอร์ที่รองรับ API ระบบจะโหลดเฉพาะสคริปต์ใหม่เท่านั้น
ซึ่งทั้งหมดนี้เป็นไปได้อย่างงดงามด้วยimport()
แบบไดนามิก ซึ่งเบราว์เซอร์ที่ทันสมัยทั้งหมดรองรับ
อย่างที่บอกไปก่อนหน้านี้ ปัจจุบันหญ้าเริ่มเป็นสีเขียว
![เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ที่แสดงการโหลดไฟล์สมัยใหม่](https://web.dev/static/articles/progressively-enhance-your-pwa/image/chrome-devtools-showing-770ae149354f5.png?authuser=2&hl=th)
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 ผมจะเปิดไฟล์ได้เหมือนเดิม ไฟล์ที่นำเข้าจะถูกวาดลงบนผืนผ้าใบโดยตรง ผมสามารถแก้ไขและได้บันทึกการแก้ไขด้วยกล่องโต้ตอบการบันทึกจริง ซึ่งผมสามารถเลือกชื่อและตำแหน่งจัดเก็บไฟล์ได้ ตอนนี้ไฟล์ก็พร้อมสำหรับการเก็บรักษาไว้ชั่วนิรันดร์
![แอปทักทาย Fugu ที่มีกล่องโต้ตอบเปิดไฟล์](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-a-fil-e1040300ddcaf.png?authuser=2&hl=th)
![แอปทักทาย Fugu พร้อมรูปภาพที่นำเข้าแล้ว](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-with-56c3523778222.png?authuser=2&hl=th)
![แอปทักทาย Fugu ที่มีรูปภาพที่แก้ไขแล้ว](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-the-m-1a86c627405ad.png?authuser=2&hl=th)
API เป้าหมายของการแชร์เว็บและการแชร์เว็บ
นอกจากเก็บไว้ไปตลอดกาลแล้ว บางทีฉันอาจจะอยากแชร์บัตรอวยพรของฉัน นี่เป็นสิ่งที่ Web Share API และ Web Share Target API ช่วยให้ผมทำได้ มีกลไกการแชร์ในตัวและระบบปฏิบัติการบนเดสก์ท็อปอยู่แล้ว ตัวอย่างด้านล่างเป็นชีตการแชร์ของ Safari ในเดสก์ท็อปใน macOS ที่ทริกเกอร์จากบทความในบล็อกของฉัน เมื่อคลิกปุ่มแชร์บทความ คุณจะแชร์ลิงก์ไปยังบทความกับเพื่อนได้ เช่น ผ่านแอป Messages ของ macOS
![ชีตการแชร์ของ Safari ในเดสก์ท็อปใน macOS ที่ทริกเกอร์จากปุ่มแชร์ของบทความ](https://web.dev/static/articles/progressively-enhance-your-pwa/image/desktop-safaris-share-sh-8fbd756c55ba8.png?authuser=2&hl=th)
โดยโค้ดที่ใช้นั้นค่อนข้างตรงไปตรงมา ฉันเรียก 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
ที่ประกอบด้วย 1 Blob แล้วตามด้วย 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 บนอุปกรณ์เคลื่อนที่ที่เป็นไปตามเงื่อนไขข้อใดข้อหนึ่งใน 2 ข้อข้างต้น ผมจะไม่โหลดฟังก์ชันการทำงาน
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
ในคำทักทาย Fugu ถ้าฉันแตะปุ่มแชร์ในเบราว์เซอร์ที่รองรับ เช่น Chrome บน Android ชีตการแชร์ในตัวจะเปิดขึ้น ตัวอย่างเช่น ผมสามารถเลือก Gmail แล้ววิดเจ็ตเครื่องมือเขียนอีเมลก็จะปรากฏขึ้นพร้อมรูปภาพที่แนบ
![ชีตการแชร์ระดับระบบปฏิบัติการที่แสดงแอปต่างๆ สำหรับแชร์รูปภาพ](https://web.dev/static/articles/progressively-enhance-your-pwa/image/os-level-share-sheet-show-3161a8aab13b2.png?authuser=2&hl=th)
![วิดเจ็ตการเขียนอีเมลของ Gmail ที่แนบรูปภาพ](https://web.dev/static/articles/progressively-enhance-your-pwa/image/gmails-email-compose-wid-fb4dcf2c7e4d4.png?authuser=2&hl=th)
API เครื่องมือเลือกรายชื่อติดต่อ
ต่อไปฉันจะพูดถึงรายชื่อติดต่อ ซึ่งหมายถึงสมุดที่อยู่ของอุปกรณ์หรือแอปโปรแกรมจัดการรายชื่อติดต่อ เมื่อคุณเขียนการ์ดอวยพร การเขียนชื่อคนอื่นอย่างถูกต้อง อาจไม่ใช่เรื่องง่ายเสมอไป ตัวอย่างเช่น ฉันมีเพื่อนที่ชื่อ Sergey ต้องการให้ชื่อของเขาสะกดเป็นตัวอักษรซิริลลิก ฉันใช้แป้นพิมพ์ QWERTZ ภาษาเยอรมันอยู่และไม่รู้ว่าจะพิมพ์ชื่ออย่างไร ซึ่งเป็นปัญหาที่ Contact Picker API สามารถแก้ไขได้ เนื่องจากฉันเก็บเพื่อนไว้ในแอปรายชื่อติดต่อในโทรศัพท์ ผ่าน 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 เมื่อแตะปุ่มรายชื่อติดต่อแล้วเลือกเพื่อนสนิท 2 คน серค่ะей การจัดจำแนก เก็บไว้ นอกจากนี้ เราเคยดูเพียงชื่ออื่นๆ ของคำว่า 劳伦斯·爱德 Security·"拉里"·佩奇 คุณจะเห็นว่าข้อมูลผู้ติดต่อนั้นถูกจำกัดไว้เพียงชื่อ แล้วระบบจะวาดชื่อของพวกเขาไว้บนการ์ดอวยพรของฉัน
![เครื่องมือเลือกรายชื่อติดต่อแสดงชื่อของรายชื่อติดต่อ 2 รายในสมุดที่อยู่](https://web.dev/static/articles/progressively-enhance-your-pwa/image/contacts-picker-showing-4d7400f689224.png?authuser=2&hl=th)
![ชื่อของผู้ติดต่อสองคนที่เลือกไว้ก่อนหน้านี้บนการ์ดอวยพร](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-names-the-previousl-58fa638399f8c.png?authuser=2&hl=th)
API คลิปบอร์ดแบบอะซิงโครนัส
ถัดไปคือการคัดลอกและวาง การดำเนินการโปรดอย่างหนึ่งของเราในฐานะนักพัฒนาซอฟต์แวร์คือการคัดลอกและวาง ในฐานะผู้เขียนการ์ดอวยพร บางครั้งฉันเองก็อาจอยากทำแบบนี้ ฉันสามารถวางรูปภาพลงในการ์ดอวยพรที่กำลังทำอยู่ หรือคัดลอกการ์ดอวยพรของฉันไว้แก้ไขจากที่อื่นต่อก็ได้ Async Clipboard API รองรับทั้งข้อความและรูปภาพ ผมจะแนะนำวิธีที่ผมเพิ่มการรองรับ การคัดลอกและวางลงในแอปทักทาย Fugu
ฉันต้องเขียนไปยังคลิปบอร์ดเพื่อที่จะคัดลอกเนื้อหาลงในคลิปบอร์ดของระบบ
เมธอด 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 จะถามฉันว่าต้องการอนุญาตให้แอปดูข้อความและรูปภาพในคลิปบอร์ดไหม
![แอป Fugu Greetings แสดงข้อความแจ้งสิทธิ์ของคลิปบอร์ด](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-showin-2da915014bf66.png?authuser=2&hl=th)
สุดท้าย หลังจากยอมรับสิทธิ์แล้ว ระบบจะวางรูปภาพลงในแอปพลิเคชัน ส่วนอีกวิธีก็ใช้ได้เช่นกัน ขอคัดลอกการ์ดอวยพรไปยังคลิปบอร์ด จากนั้นเปิด "ดูตัวอย่าง" แล้วคลิกไฟล์ จากนั้นคลิกรายการใหม่จากคลิปบอร์ด ระบบจะวางการ์ดอวยพรลงในรูปภาพใหม่ที่ไม่มีชื่อ
![แอปดูตัวอย่าง macOS ที่มีรูปภาพเพิ่งวางที่ไม่มีชื่อ](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-macos-preview-app-an-9ec120ebd7ad8.png?authuser=2&hl=th)
API การติดป้าย
API ที่มีประโยชน์อีกอย่างหนึ่งคือ Badging API
แน่นอนว่าเนื่องจากเป็น PWA ที่ติดตั้งได้ คำทักทายของ Fugu จะมีไอคอนแอป
ที่ผู้ใช้วางบนแท่นชาร์จแอปหรือหน้าจอหลักได้
วิธีที่สนุกและง่ายในการสาธิต API คือ (ab) ใช้ในคําทักทาย Fugu
โดยเป็นตัวนับการยิงปากกา
ฉันได้เพิ่ม Listener เหตุการณ์ที่จะเพิ่มตัวนับการลากปากกาได้เมื่อใดก็ตามที่เกิดเหตุการณ์ pointerdown
จากนั้นจึงตั้งค่าป้ายไอคอนที่อัปเดต
เมื่อใดก็ตามที่ผืนผ้าใบถูกล้างออกไป ตัวนับจะรีเซ็ตและนำป้ายออก
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
![ตัวเลขจาก 1 ถึง 7 วาดลงบนบัตรอวยพร ซึ่งแต่ละใบวาดด้วยปากกา 1 เส้น](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-numbers-one-seven-d-890d712e8df6d.png?authuser=2&hl=th)
![ไอคอนป้ายในแอปทักทาย Fugu แสดงเลข 7](https://web.dev/static/articles/progressively-enhance-your-pwa/image/badge-icon-the-fugu-gree-bc1d070282039.png?authuser=2&hl=th)
API การซิงค์ในเบื้องหลังตามระยะเวลา
หากต้องการเริ่มต้นวันใหม่ด้วยสิ่งใหม่ๆ คุณสมบัติที่ดีมากของแอป Fugu Greetings คือ สร้างแรงบันดาลใจให้คุณทุกเช้าด้วยภาพพื้นหลังใหม่เพื่อใช้เริ่มต้นการ์ดอวยพร ซึ่งแอปจะใช้ Periodic Background Sync API เพื่อดำเนินการดังกล่าว
ขั้นตอนแรกคือregisterเหตุการณ์การซิงค์เป็นระยะในการลงทะเบียน 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
ในโปรแกรมทำงานของบริการ
หากแท็กเหตุการณ์คือ '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 แทน 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 การกดปุ่มวอลเปเปอร์จะแสดงรูปภาพการ์ดอวยพรประจำวันที่มีการอัปเดตทุกวันผ่าน Periodic Background Sync API
![แอปทักทาย Fugu ที่มีรูปภาพการ์ดอวยพรใหม่ประจำวัน](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-a-gr-d81b949b1fb1c.png?authuser=2&hl=th)
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 จะมีข้อความแจ้งให้ฉันแสดงขึ้นเมื่อฉันต้องการได้รับการช่วยเตือนให้ทำการ์ดอวยพรให้เสร็จสิ้น
![แอป Fugu Greetings ที่มีข้อความแจ้งถามผู้ใช้ว่าต้องการรับการแจ้งเตือนเมื่อการ์ดอวยพรให้เสร็จสิ้นเมื่อใด](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-a-pro-5fd5c6e04511a.png?authuser=2&hl=th)
เมื่อมีการทริกเกอร์การแจ้งเตือนที่กำหนดเวลาไว้ในคำทักทายของ Fugu การแจ้งเตือนจะแสดงเหมือนกับการแจ้งเตือนอื่นๆ แต่อย่างที่ฉันเคยเขียนไว้ก่อนหน้านี้ ซึ่งไม่ต้องอาศัยการเชื่อมต่อเครือข่าย
![ศูนย์การแจ้งเตือนของ macOS แสดงการแจ้งเตือนที่มีการทริกเกอร์จากคำทักทาย Fugu](https://web.dev/static/articles/progressively-enhance-your-pwa/image/macos-notification-center-87f383f0a103b.png?authuser=2&hl=th)
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 จะมีช่องทำเครื่องหมายนอนไม่หลับซึ่งเมื่อเลือกช่องนี้จะทำให้หน้าจอยังเปิดอยู่ตลอด
![หากเลือกช่องทำเครื่องหมายโรคนอนไม่หลับจะทำให้หน้าจอเปิดอยู่เสมอ](https://web.dev/static/articles/progressively-enhance-your-pwa/image/the-insomnia-checkbox-c-fc49b7954974a.png?authuser=2&hl=th)
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 Greetings ผ้าใบจะล้างออกเมื่อมีการเลือกช่องทำเครื่องหมายชั่วคราว และผู้ใช้ไม่มีการใช้งานนานเกินไป
![แอป Fugu Greetings ที่ล้างผ้าใบหลังจากที่ผู้ใช้ไม่มีการใช้งานนานเกินไป](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-app-a-cle-559af8955ecad.png?authuser=2&hl=th)
เปิดจากขอบ
โอ้โห สนุกจัง API จำนวนมากในแอปตัวอย่างเพียงแอปเดียว และอย่าลืมว่าฉันจะไม่ทำให้ผู้ใช้ต้องจ่ายค่าดาวน์โหลด สำหรับฟีเจอร์ที่เบราว์เซอร์ไม่รองรับ การใช้การเพิ่มประสิทธิภาพแบบต่อเนื่องจะช่วยให้มั่นใจได้ว่าระบบจะโหลดเฉพาะโค้ดที่เกี่ยวข้องเท่านั้น และเนื่องจากคำขอ HTTP/2 นั้นมีราคาถูก รูปแบบนี้จึงจะเหมาะกับแอปพลิเคชันจำนวนมาก แม้ว่าคุณอาจต้องการพิจารณาใช้ Bundler สำหรับแอปขนาดใหญ่ก็ตาม
![แผง Chrome DevTools Network จะแสดงเฉพาะคำขอสำหรับไฟล์ที่มีรหัสซึ่งเบราว์เซอร์ปัจจุบันรองรับ](https://web.dev/static/articles/progressively-enhance-your-pwa/image/chrome-devtools-network-p-c51d72a3bad2.png?authuser=2&hl=th)
แอปอาจมีหน้าตาแตกต่างกันเล็กน้อยในแต่ละเบราว์เซอร์ เนื่องจากมีเพียงบางแพลตฟอร์มเท่านั้นที่รองรับฟีเจอร์ทั้งหมด แต่ฟังก์ชันการทำงานหลักจะมีอยู่เสมอ โดยจะปรับปรุงอย่างต่อเนื่องตามความสามารถของเบราว์เซอร์นั้นๆ โปรดทราบว่าความสามารถเหล่านี้อาจมีการเปลี่ยนแปลงแม้ในเบราว์เซอร์เดียวหรือเบราว์เซอร์เดียวกัน ขึ้นอยู่กับว่าแอปทำงานเป็นแอปที่ติดตั้งหรือในแท็บเบราว์เซอร์
![คำทักทาย Fugu กำลังทำงานบน Chrome ของ Android โดยแสดงฟีเจอร์ที่ใช้ได้มากมาย](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-running-a-058338175547c.png?authuser=2&hl=th)
![ข้อความทักทาย Fugu กำลังทำงานบน Safari บนเดสก์ท็อป โดยแสดงฟีเจอร์ที่ใช้ได้น้อยลง](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-running-d-446d17fc11442.png?authuser=2&hl=th)
![ข้อความทักทาย Fugu กำลังทำงานบน Chrome บนเดสก์ท็อป โดยแสดงฟีเจอร์ที่พร้อมใช้งานมากมาย](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-running-d-9006f53b391af.png?authuser=2&hl=th)
ถ้าสนใจแอป Fugu Greetings ให้ค้นหาและแยกแอปดังกล่าวใน GitHub
![ที่เก็บบัตรทักทาย Fugu ใน GitHub](https://web.dev/static/articles/progressively-enhance-your-pwa/image/fugu-greetings-repo-gith-f95acb5949892.png?authuser=2&hl=th)
ทีม Chromium กำลังพยายามอย่างเต็มที่เพื่อทำให้สนามหญ้าเขียวชอุ่มมากขึ้นเมื่อต้องใช้ Fugu API ขั้นสูง การใช้การเพิ่มประสิทธิภาพแบบต่อเนื่องในการพัฒนาแอปทำให้ผมมั่นใจว่าทุกคนจะได้รับประสบการณ์พื้นฐานที่ดีและมีคุณภาพ แต่คนที่ใช้เบราว์เซอร์ที่รองรับ API ของแพลตฟอร์มเว็บจำนวนมากขึ้นจะได้รับประสบการณ์ที่ดียิ่งขึ้นไปอีก เราหวังเป็นอย่างยิ่งว่าจะได้เห็นคุณใช้การเพิ่มประสิทธิภาพแบบต่อเนื่องในแอป
ข้อความแสดงการยอมรับ
ฉันรู้สึกขอบคุณ Christian Liebel และ Hemanth HM ที่มาร่วมกิจกรรมทักทาย Fugu
บทความนี้ได้รับการตรวจสอบโดย Joe Medley และ Kayce Basques
Jake Archibald ช่วยฉันหาสถานการณ์เกี่ยวกับ import()
แบบไดนามิกในบริบทของ Service Worker