Xây dựng cho các trình duyệt hiện đại và cải tiến dần như thể đang ở năm 2003
Vào tháng 3 năm 2003, Nick Finck và Steve Champeon đã gây chấn động thế giới thiết kế web bằng khái niệm tăng cường dần. Đây là chiến lược thiết kế web nhấn mạnh việc tải nội dung cốt lõi của trang web trước tiên, sau đó tăng dần các lớp trình bày và tính năng tinh tế hơn cũng như nghiêm ngặt hơn về mặt kỹ thuật lên trên nội dung. Trong khi đó, vào năm 2003, tính năng cải tiến dần dần là về việc sử dụng các tính năng CSS hiện đại, JavaScript không gây phiền toái và thậm chí chỉ là Đồ hoạ vectơ có thể mở rộng. Cải tiến từng bước trong năm 2020 trở đi là về việc sử dụng các tính năng của trình duyệt hiện đại.
JavaScript hiện đại
Nói về JavaScript, tình hình hỗ trợ trình duyệt cho các tính năng JavaScript cốt lõi mới nhất của ES 2015 rất tuyệt vời.
Tiêu chuẩn mới bao gồm các lời hứa, mô-đun, lớp, giá trị cố định của mẫu, hàm mũi tên, let
và const
, tham số mặc định, trình tạo, gán cấu trúc phân ly, phần còn lại và truyền, Map
/Set
, WeakMap
/WeakSet
và nhiều tính năng khác.
Tất cả đều được hỗ trợ.
Bạn có thể sử dụng hàm không đồng bộ (một tính năng của ES 2017 và là một trong những tính năng yêu thích của tôi) trong tất cả các trình duyệt chính.
Từ khoá async
và await
cho phép viết hành vi không đồng bộ, dựa trên lời hứa theo kiểu gọn gàng hơn, tránh việc phải định cấu hình rõ ràng các chuỗi lời hứa.
Thậm chí, ngay cả các tính năng bổ sung mới nhất cho ngôn ngữ ES 2020 như chuỗi tuỳ chọn và hợp nhất giá trị rỗng cũng đã được hỗ trợ rất nhanh chóng. Bạn có thể xem mã mẫu bên dưới. Khi nói đến các tính năng cốt lõi của JavaScript, mọi thứ không thể tốt hơn được nữa.
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Ứng dụng mẫu: Fugu Greetings
Trong bài viết này, tôi sẽ làm việc với một PWA đơn giản có tên là Fugu Greetings (GitHub). Tên của ứng dụng này là một lời tri ân đến Dự án Fugu 🐡, một nỗ lực nhằm mang đến cho web tất cả các tính năng của ứng dụng Android/iOS/máy tính. Bạn có thể đọc thêm về dự án này trên trang đích của dự án.
Fugu Greetings là một ứng dụng vẽ giúp bạn tạo thiệp chúc mừng ảo và gửi cho những người thân yêu. Ứng dụng này minh hoạ các khái niệm cốt lõi của PWA. Công cụ này đáng tin cậy và có thể hoạt động hoàn toàn khi không có mạng, vì vậy, ngay cả khi không có mạng, bạn vẫn có thể sử dụng công cụ này. Bạn cũng có thể Cài đặt ứng dụng này vào màn hình chính của thiết bị và tích hợp liền mạch với hệ điều hành dưới dạng một ứng dụng độc lập.
Cải tiến tăng dần
Giờ đây, chúng ta có thể nói về tính năng nâng cao cải tiến tăng dần. Từ vựng Tài liệu web MDN xác định khái niệm này như sau:
Cải tiến tăng dần là một triết lý thiết kế cung cấp đường cơ sở về nội dung và chức năng thiết yếu cho nhiều người dùng nhất có thể, đồng thời chỉ mang lại trải nghiệm tốt nhất có thể cho người dùng các trình duyệt hiện đại nhất có thể chạy tất cả mã bắt buộc.
Phương thức phát hiện tính năng thường được dùng để xác định xem trình duyệt có thể xử lý chức năng hiện đại hơn hay không, còn polyfill thường được dùng để thêm các tính năng bị thiếu bằng JavaScript.
[…]
Cải tiến dần là một kỹ thuật hữu ích cho phép nhà phát triển web tập trung vào việc phát triển những trang web tốt nhất có thể, đồng thời giúp các trang web đó hoạt động trên nhiều tác nhân người dùng không xác định. Suy giảm linh hoạt có liên quan nhưng không giống nhau và thường được xem là đi theo hướng ngược lại với việc cải tiến dần. Trong thực tế, cả hai phương pháp đều hợp lệ và thường có thể bổ sung cho nhau.
Những người đóng góp cho MDN
Việc bắt đầu mỗi thiệp chúc mừng từ đầu có thể rất rườm rà.
Vậy tại sao không có một tính năng cho phép người dùng nhập hình ảnh và bắt đầu từ đó?
Với phương pháp truyền thống, bạn sẽ sử dụng phần tử <input type=file>
để thực hiện việc này.
Trước tiên, bạn sẽ tạo phần tử, đặt type
thành 'file'
và thêm các loại MIME vào thuộc tính accept
, sau đó "nhấp" vào phần tử đó theo phương thức lập trình và theo dõi các thay đổi.
Khi bạn chọn một hình ảnh, hình ảnh đó sẽ được nhập thẳng vào 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();
});
};
Khi có tính năng nhập, bạn nên có tính năng xuất để người dùng có thể lưu thiệp chúc mừng trên thiết bị.
Cách lưu tệp truyền thống là tạo một đường liên kết neo bằng thuộc tính download
và URL blob làm href
.
Bạn cũng sẽ "nhấp" vào nút này theo phương thức lập trình để kích hoạt quá trình tải xuống, đồng thời đừng quên thu hồi URL đối tượng blob để ngăn rò rỉ bộ nhớ.
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();
};
Nhưng chờ đã. Trong tâm trí, bạn không "tải" thiệp chúc mừng xuống mà đã "lưu" thiệp đó. Thay vì hiển thị hộp thoại "lưu" cho phép bạn chọn vị trí đặt tệp, trình duyệt đã trực tiếp tải thiệp chúc mừng xuống mà không cần người dùng tương tác và đã đặt tệp đó ngay vào thư mục Downloads (Tải xuống). Điều này không tốt.
Nếu có cách tốt hơn thì sao? Điều gì sẽ xảy ra nếu bạn chỉ có thể mở một tệp cục bộ, chỉnh sửa tệp đó rồi lưu nội dung sửa đổi vào một tệp mới hoặc quay lại tệp gốc mà bạn đã mở ban đầu? Hóa ra có. File System Access API (API truy cập hệ thống tệp) cho phép bạn mở và tạo các tệp và thư mục, cũng như sửa đổi và lưu các tệp và thư mục đó .
Vậy làm cách nào để phát hiện tính năng của API?
API Truy cập hệ thống tệp hiển thị một phương thức mới window.chooseFileSystemEntries()
.
Do đó, tôi cần tải có điều kiện các mô-đun nhập và xuất khác nhau tuỳ thuộc vào việc phương thức này có sẵn hay không. Tôi đã hướng dẫn cách thực hiện việc này ở bên dưới.
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'),
]);
}
};
Tuy nhiên, trước khi đi sâu vào thông tin chi tiết về API Truy cập hệ thống tệp, hãy để tôi nhanh chóng nêu bật mẫu cải tiến dần dần tại đây. Trên các trình duyệt hiện không hỗ trợ API Quyền truy cập vào hệ thống tệp, tôi sẽ tải các tập lệnh cũ. Bạn có thể xem các thẻ mạng của Firefox và Safari ở bên dưới.
Tuy nhiên, trên Chrome, một trình duyệt hỗ trợ API, chỉ các tập lệnh mới được tải.
Điều này có thể thực hiện một cách thanh lịch nhờ import()
động mà tất cả trình duyệt hiện đại đều hỗ trợ.
Như tôi đã nói trước đó, cỏ dại khá xanh tươi những ngày này.
API Truy cập hệ thống tệp
Giờ đây, khi tôi đã giải quyết vấn đề này, đã đến lúc xem xét cách triển khai thực tế dựa trên API Truy cập hệ thống tệp.
Để nhập hình ảnh, tôi gọi window.chooseFileSystemEntries()
và truyền cho thuộc tính này một thuộc tính accepts
mà tôi muốn có các tệp hình ảnh.
Hỗ trợ cả đuôi tệp và loại MIME.
Thao tác này sẽ tạo ra một handle tệp, từ đó tôi có thể lấy tệp thực tế bằng cách gọi 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);
}
};
Việc xuất hình ảnh cũng gần giống như vậy, nhưng lần này, tôi cần truyền tham số loại 'save-file'
vào phương thức chooseFileSystemEntries()
.
Từ đó, tôi nhận được hộp thoại lưu tệp.
Khi tệp đang mở, bạn không cần làm việc này vì 'open-file'
là mặc định.
Tôi đặt thông số accepts
tương tự như trước, nhưng lần này chỉ giới hạn ở hình ảnh PNG.
Một lần nữa, tôi nhận lại một handle tệp, nhưng thay vì nhận tệp, lần này tôi tạo một luồng có thể ghi bằng cách gọi createWritable()
.
Tiếp theo, tôi ghi blob (là hình ảnh thiệp chúc mừng) vào tệp.
Cuối cùng, tôi đóng luồng có thể ghi.
Mọi thứ đều có thể không thành công: Ổ đĩa có thể hết dung lượng, có thể xảy ra lỗi ghi hoặc đọc, hoặc có thể đơn giản là người dùng huỷ hộp thoại tệp.
Đây là lý do tôi luôn gói các lệnh gọi trong câu lệnh 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);
}
};
Bằng cách sử dụng tính năng cải tiến dần bằng API Truy cập hệ thống tệp, tôi có thể mở tệp như trước. Tệp đã nhập được vẽ ngay trên canvas. Tôi có thể chỉnh sửa và cuối cùng lưu nội dung chỉnh sửa bằng một hộp thoại lưu thực sự, trong đó tôi có thể chọn tên và vị trí lưu trữ của tệp. Giờ đây, tệp đã sẵn sàng để lưu trữ vĩnh viễn.
API Chia sẻ trên web và API Mục tiêu chia sẻ trên web
Ngoài việc lưu trữ mãi mãi, có thể tôi thực sự muốn chia sẻ thiệp chúc mừng của mình. Đây là điều mà Web Share API và Web Share Target API cho phép tôi làm. Gần đây, các hệ điều hành dành cho thiết bị di động và máy tính đã có các cơ chế chia sẻ tích hợp. Ví dụ: bên dưới là trang chia sẻ của Safari trên máy tính chạy macOS được kích hoạt từ một bài viết trên blog của tôi. Khi nhấp vào nút Chia sẻ bài viết, bạn có thể chia sẻ đường liên kết đến bài viết với một người bạn, ví dụ: thông qua ứng dụng Tin nhắn trên macOS.
Mã để thực hiện việc này khá đơn giản. Tôi gọi navigator.share()
và truyền vào đó một title
, text
và url
không bắt buộc trong một đối tượng.
Nhưng nếu tôi muốn đính kèm hình ảnh thì sao? Cấp 1 của API Chia sẻ trên web chưa hỗ trợ tính năng này.
Tin vui là Web Share cấp 2 đã bổ sung các tính năng chia sẻ tệp.
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);
}
Hãy để tôi hướng dẫn bạn cách thực hiện việc này với ứng dụng Thiệp chúc mừng Fugu.
Trước tiên, tôi cần chuẩn bị một đối tượng data
với một mảng files
bao gồm một blob, sau đó là title
và text
. Tiếp theo, theo phương pháp hay nhất, tôi sử dụng phương thức navigator.canShare()
mới. Phương thức này thực hiện đúng như tên gọi: cho tôi biết liệu trình duyệt có thể chia sẻ đối tượng data
mà tôi đang cố gắng chia sẻ hay không.
Nếu navigator.canShare()
cho tôi biết dữ liệu có thể được chia sẻ, tôi đã sẵn sàng gọi navigator.share()
như trước.
Vì mọi thứ đều có thể không thành công, nên tôi sẽ sử dụng lại khối 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);
}
};
Như trước đây, tôi sử dụng tính năng cải tiến tăng dần.
Nếu cả 'share'
và 'canShare'
đều tồn tại trên đối tượng navigator
, thì tôi mới tiếp tục và tải share.mjs
thông qua import()
động.
Trên các trình duyệt như Safari dành cho thiết bị di động chỉ đáp ứng một trong hai điều kiện, tôi sẽ không tải chức năng này.
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
Trong ứng dụng Fugu Greetings, nếu tôi nhấn vào nút Chia sẻ trên một trình duyệt hỗ trợ như Chrome trên Android, thì trang chia sẻ tích hợp sẽ mở ra. Ví dụ: tôi có thể chọn Gmail và tiện ích trình soạn email sẽ bật lên cùng với hình ảnh đính kèm.
API Bộ chọn danh bạ
Tiếp theo, tôi muốn nói về danh bạ, tức là sổ địa chỉ hoặc ứng dụng quản lý danh bạ của thiết bị. Khi bạn viết thiệp chúc mừng, không phải lúc nào bạn cũng có thể viết chính xác tên của một người. Ví dụ: Tôi có một người bạn tên là Sergey. Anh ấy muốn tên mình được viết bằng chữ cái Cyrillic. Tôi đang sử dụng bàn phím QWERTZ của Đức và không biết cách nhập tên của họ. Đây là vấn đề mà Contact Picker API có thể giải quyết. Vì đã lưu bạn bè của mình trong ứng dụng danh bạ trên điện thoại, nên thông qua API bộ chọn danh bạ, tôi có thể truy cập vào danh bạ của mình trên web.
Trước tiên, tôi cần chỉ định danh sách các thuộc tính mà tôi muốn truy cập.
Trong trường hợp này, tôi chỉ muốn tên, nhưng đối với các trường hợp sử dụng khác, tôi có thể quan tâm đến số điện thoại, email, biểu tượng hình đại diện hoặc địa chỉ thực tế.
Tiếp theo, tôi định cấu hình đối tượng options
và đặt multiple
thành true
để có thể chọn nhiều mục nhập.
Cuối cùng, tôi có thể gọi navigator.contacts.select()
để trả về các thuộc tính mong muốn cho những người liên hệ do người dùng chọn.
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);
}
};
Và giờ đây, có thể bạn đã nắm được mẫu: Tôi chỉ tải tệp khi API thực sự được hỗ trợ.
if ('contacts' in navigator) {
import('./contacts.mjs');
}
Trong ứng dụng Fugu Greeting, khi tôi nhấn vào nút Contacts (Danh bạ) và chọn hai người bạn thân nhất của mình là Сергей Михайлович Брин và 劳伦斯·爱德华·"拉里"·佩奇, bạn có thể thấy bộ chọn danh bạ chỉ hiển thị tên của họ chứ không hiển thị địa chỉ email hoặc thông tin khác như số điện thoại. Sau đó, tên của họ được vẽ lên thiệp chúc mừng của tôi.
API Bảng nhớ tạm không đồng bộ
Tiếp theo là thao tác sao chép và dán. Một trong những thao tác mà chúng tôi yêu thích khi làm nhà phát triển phần mềm là sao chép và dán. Là một tác giả thiệp chúc mừng, đôi khi tôi cũng muốn làm như vậy. Tôi có thể muốn dán hình ảnh vào thiệp chúc mừng mà tôi đang làm hoặc sao chép thiệp chúc mừng để có thể tiếp tục chỉnh sửa thiệp từ một nơi khác. API Bảng nhớ tạm không đồng bộ hỗ trợ cả văn bản và hình ảnh. Hãy để tôi hướng dẫn bạn cách thêm tính năng hỗ trợ sao chép và dán vào ứng dụng Fugu Greetings.
Để sao chép nội dung vào bảng nhớ tạm của hệ thống, tôi cần ghi vào bảng nhớ tạm đó.
Phương thức navigator.clipboard.write()
lấy một mảng các mục trên bảng nhớ tạm làm tham số.
Về cơ bản, mỗi mục trên bảng nhớ tạm là một đối tượng có blob làm giá trị và loại của blob làm khoá.
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
Để dán, tôi cần lặp lại các mục trên bảng nhớ tạm mà tôi nhận được bằng cách gọi navigator.clipboard.read()
.
Lý do là nhiều mục trên bảng nhớ tạm có thể nằm trên bảng nhớ tạm ở nhiều dạng.
Mỗi mục trên bảng nhớ tạm có một trường types
cho tôi biết các loại MIME của tài nguyên có sẵn.
Tôi gọi phương thức getType()
của mục bảng nhớ tạm, truyền loại MIME mà tôi đã nhận được trước đó.
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);
}
};
Và giờ đây, bạn gần như không cần phải nói điều này. Tôi chỉ làm việc này trên các trình duyệt hỗ trợ.
if ('clipboard' in navigator && 'write' in navigator.clipboard) {
import('./clipboard.mjs');
}
Vậy trong thực tế, cách này hoạt động như thế nào? Tôi mở một hình ảnh trong ứng dụng Xem trước của macOS và sao chép hình ảnh đó vào bảng nhớ tạm. Khi tôi nhấp vào Dán, ứng dụng Fugu Greetings sẽ hỏi tôi xem tôi có muốn cho phép ứng dụng xem văn bản và hình ảnh trên bảng nhớ tạm hay không.
Cuối cùng, sau khi chấp nhận quyền, hình ảnh sẽ được dán vào ứng dụng. Cách ngược lại cũng hoạt động. Hãy để tôi sao chép một thiệp chúc mừng vào bảng nhớ tạm. Sau đó, khi tôi mở Preview (Xem trước) và nhấp vào File (Tệp) rồi nhấp vào New from Clipboard (Mới từ bảng nhớ tạm), thiệp chúc mừng sẽ được dán vào một hình ảnh mới chưa có tiêu đề.
API Gán huy hiệu
Một API hữu ích khác là API gắn huy hiệu.
Là một PWA có thể cài đặt, Fugu Greetings tất nhiên có một biểu tượng ứng dụng mà người dùng có thể đặt trên thanh dock ứng dụng hoặc màn hình chính.
Một cách thú vị và dễ dàng để minh hoạ API là (lạm) dụng API đó trong ứng dụng Fugu Greetings làm bộ đếm nét bút.
Tôi đã thêm một trình nghe sự kiện để tăng bộ đếm nét bút mỗi khi sự kiện pointerdown
xảy ra, sau đó đặt huy hiệu biểu tượng đã cập nhật.
Bất cứ khi nào canvas bị xoá, bộ đếm sẽ đặt lại và huy hiệu sẽ bị xoá.
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
Tính năng này là một tính năng nâng cao dần, vì vậy, logic tải vẫn như bình thường.
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
Trong ví dụ này, tôi đã vẽ các số từ 1 đến 7, sử dụng một nét bút cho mỗi số. Bộ đếm huy hiệu trên biểu tượng hiện ở mức 7.
API Đồng bộ hoá định kỳ ở chế độ nền
Bạn muốn bắt đầu mỗi ngày bằng những điều mới mẻ? Một tính năng thú vị của ứng dụng Fugu Greetings là có thể truyền cảm hứng cho bạn mỗi sáng bằng một hình nền mới để bắt đầu tạo thiệp chúc mừng. Ứng dụng sử dụng API Đồng bộ hoá định kỳ ở chế độ nền để đạt được điều này.
Bước đầu tiên là đăng ký một sự kiện đồng bộ hoá định kỳ trong quy trình đăng ký worker dịch vụ.
Trình nghe này theo dõi thẻ đồng bộ hoá có tên là 'image-of-the-day'
và có khoảng thời gian tối thiểu là một ngày để người dùng có thể nhận được hình nền mới mỗi 24 giờ.
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);
}
};
Bước thứ hai là nghe sự kiện periodicsync
trong worker dịch vụ.
Nếu thẻ sự kiện là 'image-of-the-day'
, tức là thẻ đã được đăng ký trước đó, thì hình ảnh của ngày đó sẽ được truy xuất thông qua hàm getImageOfTheDay()
và kết quả sẽ được truyền đến tất cả ứng dụng để các ứng dụng này có thể cập nhật canvas và bộ nhớ đệm.
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,
});
});
})()
);
}
});
Xin nhắc lại rằng đây thực sự là một tính năng nâng cao dần dần, vì vậy, mã chỉ được tải khi trình duyệt hỗ trợ API.
Điều này áp dụng cho cả mã ứng dụng và mã worker dịch vụ.
Trên các trình duyệt không hỗ trợ, cả hai đều không được tải.
Lưu ý cách trong trình chạy dịch vụ, thay vì import()
động (chưa được hỗ trợ trong ngữ cảnh trình chạy dịch vụ chưa), tôi sử dụng importScripts()
cổ điển.
// 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');
}
Trong ứng dụng Fugu Greetings, khi nhấn nút Wallpaper (Hình nền), bạn sẽ thấy hình ảnh thiệp chúc mừng trong ngày được cập nhật hằng ngày thông qua API Đồng bộ hoá định kỳ ở chế độ nền.
Notification Triggers API
Đôi khi, ngay cả khi có nhiều cảm hứng, bạn vẫn cần một cú hích để hoàn thành một thiệp chúc mừng đã bắt đầu. Đây là tính năng được bật bằng Notification Triggers API (API Trình kích hoạt thông báo). Là người dùng, tôi có thể nhập thời điểm tôi muốn được nhắc hoàn thành thiệp chúc mừng. Khi đến thời điểm đó, tôi sẽ nhận được thông báo cho biết thiệp chúc mừng của tôi đang chờ xử lý.
Sau khi nhắc thời gian mục tiêu, ứng dụng sẽ lên lịch thông báo bằng showTrigger
.
Đây có thể là TimestampTrigger
có ngày mục tiêu đã chọn trước đó.
Thông báo nhắc sẽ được kích hoạt cục bộ, không cần mạng hoặc phía máy chủ.
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),
});
}
Giống như mọi thứ khác mà tôi đã trình bày cho đến nay, đây là một tính năng nâng cao dần, vì vậy mã chỉ được tải có điều kiện.
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
Khi tôi đánh dấu vào hộp đánh dấu Lời nhắc trong ứng dụng Fugu Greetings, một lời nhắc sẽ hỏi tôi thời điểm tôi muốn được nhắc hoàn tất thiệp chúc mừng.
Khi một thông báo được lên lịch kích hoạt trong ứng dụng Fugu Greetings, thông báo đó sẽ hiển thị giống như mọi thông báo khác, nhưng như tôi đã viết trước đó, thông báo đó không yêu cầu kết nối mạng.
Wake Lock API
Tôi cũng muốn thêm API Khoá chế độ thức. Đôi khi, bạn chỉ cần nhìn chằm chằm vào màn hình cho đến khi nguồn cảm hứng đến với bạn. Điều tồi tệ nhất có thể xảy ra sau đó là màn hình tắt. API khoá chế độ thức có thể ngăn điều này xảy ra.
Bước đầu tiên là lấy khoá chế độ thức bằng navigator.wakelock.request method()
.
Tôi truyền chuỗi 'screen'
vào để lấy khoá chế độ thức của màn hình.
Sau đó, tôi thêm trình nghe sự kiện để được thông báo khi khoá chế độ thức được nhả.
Điều này có thể xảy ra, chẳng hạn như khi chế độ hiển thị thẻ thay đổi.
Nếu điều này xảy ra, tôi có thể lấy lại khoá chế độ thức khi thẻ hiển thị trở lại.
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);
Có, đây là một tính năng nâng cao dần dần, vì vậy, tôi chỉ cần tải tính năng này khi trình duyệt hỗ trợ API.
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
import('./wake_lock.mjs');
}
Trong Fugu Greetings, có một hộp đánh dấu Insomnia (Mất ngủ). Khi bạn chọn hộp này, màn hình sẽ luôn bật.
API Phát hiện trạng thái rảnh
Đôi khi, ngay cả khi bạn nhìn chằm chằm vào màn hình hàng giờ, việc này cũng chẳng có ích gì và bạn không thể nghĩ ra ý tưởng nào cho thiệp chúc mừng. API Phát hiện trạng thái rảnh cho phép ứng dụng phát hiện thời gian người dùng ở trạng thái rảnh. Nếu người dùng không hoạt động quá lâu, ứng dụng sẽ đặt lại về trạng thái ban đầu và xoá canvas. API này hiện được kiểm soát bằng quyền thông báo, vì nhiều trường hợp sử dụng chính thức của tính năng phát hiện trạng thái rảnh liên quan đến thông báo, ví dụ: chỉ gửi thông báo đến một thiết bị mà người dùng hiện đang chủ động sử dụng.
Sau khi đảm bảo rằng quyền thông báo đã được cấp, tôi sẽ tạo bản sao cho trình phát hiện trạng thái rảnh. Tôi đăng ký một trình nghe sự kiện để theo dõi các thay đổi về trạng thái rảnh, bao gồm cả người dùng và trạng thái màn hình. Người dùng có thể đang hoạt động hoặc đang rảnh, đồng thời màn hình có thể đang mở khoá hoặc đang khoá. Nếu người dùng không hoạt động, canvas sẽ bị xoá. Tôi đặt ngưỡng cho trình phát hiện trạng thái rảnh là 60 giây.
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,
});
Và như mọi khi, tôi chỉ tải mã này khi trình duyệt hỗ trợ.
if ('IdleDetector' in window) {
import('./idle_detection.mjs');
}
Trong ứng dụng Fugu Greetings, canvas sẽ xoá khi hộp đánh dấu Ephemeral (Tạm thời) được đánh dấu và người dùng ở trạng thái rảnh quá lâu.
Closing (Đang đóng)
Phù, thật là một chuyến đi. Có rất nhiều API chỉ trong một ứng dụng mẫu. Và hãy nhớ rằng tôi không bao giờ bắt người dùng trả phí tải xuống cho một tính năng mà trình duyệt của họ không hỗ trợ. Bằng cách sử dụng tính năng cải tiến tăng dần, tôi đảm bảo chỉ tải mã có liên quan. Và vì với HTTP/2, các yêu cầu có chi phí thấp, nên mẫu này sẽ hoạt động tốt cho nhiều ứng dụng, mặc dù bạn có thể cân nhắc sử dụng trình đóng gói cho các ứng dụng thực sự lớn.
Ứng dụng có thể trông hơi khác trên mỗi trình duyệt vì không phải nền tảng nào cũng hỗ trợ tất cả tính năng, nhưng chức năng cốt lõi luôn có sẵn và được nâng cao dần theo khả năng của trình duyệt cụ thể. Xin lưu ý rằng các chức năng này có thể thay đổi ngay cả trong cùng một trình duyệt, tuỳ thuộc vào việc ứng dụng đang chạy dưới dạng ứng dụng đã cài đặt hay trong thẻ trình duyệt.
Nếu bạn quan tâm đến ứng dụng Fugu Greetings, hãy tìm và tạo nhánh ứng dụng đó trên GitHub.
Nhóm Chromium đang nỗ lực cải thiện các API Fugu nâng cao. Bằng cách áp dụng tính năng cải tiến dần trong quá trình phát triển ứng dụng, tôi đảm bảo rằng mọi người đều có trải nghiệm cơ sở tốt và vững chắc, nhưng những người sử dụng trình duyệt hỗ trợ nhiều API nền tảng Web hơn sẽ có trải nghiệm tốt hơn nữa. Tôi rất mong được xem những gì bạn làm với tính năng cải tiến dần trong ứng dụng của mình.
Lời cảm ơn
Tôi rất cảm ơn Christian Liebel và
Hemanth HM đã đóng góp cho Fugu Greetings.
Bài viết này đã được Joe Medley và
Kayce Basques xem xét.
Jake Archibald đã giúp tôi tìm ra tình huống với import()
động trong ngữ cảnh của worker dịch vụ.