최신 브라우저를 위해 빌드하고 2003년처럼 점진적으로 개선하기
게시일: 2020년 6월 29일
2003년 3월 Nick Finck와 Steve Champeon은 핵심 웹페이지 콘텐츠를 먼저 로드한 다음 콘텐츠 위에 더 미묘하고 기술적으로 엄격한 프레젠테이션 및 기능 레이어를 점진적으로 추가하는 웹 디자인 전략인 점진적 개선이라는 개념으로 웹 디자인 업계를 놀라게 했습니다. 2003년에는 점진적 개선이 당시의 최신 CSS 기능, 눈에 띄지 않는 JavaScript, 확장 가능한 벡터 그래픽을 사용하는 것이었습니다. 2020년 이후의 점진적 개선은 최신 브라우저 기능을 사용하는 것입니다.

최신 JavaScript
JavaScript에 관해 말하자면 최신 핵심 ES 2015 JavaScript 기능의 브라우저 지원 상황은 매우 좋습니다. 새 표준에는 프로미스, 모듈, 클래스, 템플릿 리터럴, 화살표 함수, let
및 const
, 기본 매개변수, 생성기, 구조 분해 할당, 나머지 및 스프레드, Map
/Set
, WeakMap
/WeakSet
등이 포함됩니다.
모두 지원됨

ES 2017 기능이자 제가 가장 좋아하는 기능 중 하나인 비동기 함수는 모든 주요 브라우저에서 사용할 수 있습니다.
async
및 await
키워드를 사용하면 프라미스 체인을 명시적으로 구성할 필요 없이 더 깔끔한 스타일로 비동기 프라미스 기반 동작을 작성할 수 있습니다.

선택적 체이닝 및 nullish 병합과 같은 최신 ES 2020 언어 추가 기능도 매우 빠르게 지원되었습니다. 핵심 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
이 문서에서는 Fugu Greetings(GitHub)라는 PWA를 사용합니다. 이 앱의 이름은 웹에 Android, iOS, 데스크톱 애플리케이션의 모든 기능을 제공하기 위한 노력인 Project Fugu 🐡에 대한 경의를 표하는 것입니다. 프로젝트에 관한 자세한 내용은 방문 페이지를 참고하세요.
Fugu Greetings는 가상 인사 카드를 만들어 사랑하는 사람에게 보낼 수 있는 그림 앱입니다. PWA의 핵심 개념을 보여줍니다. 안정적이며 완전한 오프라인 지원을 제공하므로 네트워크가 없어도 사용할 수 있습니다. 또한 기기의 홈 화면에 설치할 수 있으며 독립형 애플리케이션으로 운영체제와 원활하게 통합됩니다.

점진적 개선
이제 점진적 개선에 대해 이야기해 보겠습니다. MDN 웹 문서 용어집에서는 이 개념을 다음과 같이 정의합니다.
점진적 개선은 필수 콘텐츠와 기능을 최대한 많은 사용자에게 제공하면서 필요한 코드를 모두 실행할 수 있는 최신 브라우저 사용자에게만 최상의 환경을 제공하는 디자인 철학입니다.
기능 감지는 일반적으로 브라우저가 최신 기능을 처리할 수 있는지 확인하는 데 사용되며 폴리필은 JavaScript로 누락된 기능을 추가하는 데 자주 사용됩니다.
[…]
프로그레시브 개선은 웹 개발자가 최상의 웹사이트를 개발하는 데 집중하면서도 여러 알 수 없는 사용자 에이전트에서 웹사이트가 작동하도록 할 수 있는 유용한 기술입니다. 점진적 저하는 관련이 있지만 동일한 것은 아니며 점진적 개선과 반대되는 방향으로 간주되는 경우가 많습니다. 실제로 두 접근 방식 모두 유효하며 서로 보완할 수 있는 경우가 많습니다.
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
속성과 blob URL을 href
로 사용하여 앵커 링크를 만드는 것입니다.
또한 프로그래매틱 방식으로 '클릭'하여 다운로드를 트리거하고 메모리 누수를 방지하기 위해 blob 객체 URL을 취소하는 것을 잊지 않아야 합니다.
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'),
]);
}
};
파일 시스템 액세스 API 세부정보를 살펴보기 전에 프로그레시브 인핸스먼트 패턴을 간단히 강조하겠습니다. File System Access API를 지원하지 않는 브라우저에서는 기존 스크립트를 로드합니다.


하지만 API를 지원하는 브라우저인 Chrome에서는 새 스크립트만 로드됩니다.
이는 모든 최신 브라우저가 지원하는 동적 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로 점진적 개선을 사용하면 이전과 같이 파일을 열 수 있습니다. 가져온 파일이 캔버스에 바로 그려집니다. 수정 작업을 진행한 후 파일의 이름과 저장 위치를 선택할 수 있는 실제 저장 대화상자를 사용하여 최종적으로 저장할 수 있습니다. 이제 파일을 영원히 보존할 수 있습니다.



Web Share 및 Web Share Target API
영원히 저장하는 것 외에 실제로 인사말 카드를 공유하고 싶을 수도 있습니다. Web Share API와 Web Share Target API를 사용하면 이 작업을 할 수 있습니다. 모바일 운영체제와 최근에는 데스크톱 운영체제에도 기본 공유 메커니즘이 도입되었습니다.
예를 들어 사용자가 내 블로그에서 기사 공유를 클릭하면 macOS의 데스크톱 Safari 공유 시트가 트리거됩니다. macOS 메시지 앱을 사용하여 친구와 도움말 링크를 공유할 수 있습니다.
이렇게 하려면 navigator.share()
를 호출하고 객체에 선택적 title
, text
, url
를 전달합니다.
이미지를 첨부하려면 어떻게 해야 하나요? 웹 공유 API의 레벨 1에서는 아직 지원하지 않습니다.
다행히 Web Share Level 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 인사말 카드 애플리케이션에서 이 기능을 사용하는 방법을 보여드리겠습니다.
먼저 하나의 블롭과 title
, text
로 구성된 files
배열을 사용하여 data
객체를 준비해야 합니다. 다음으로 권장사항에 따라 이름이 나타내는 작업을 실행하는 새 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
객체에 있는 경우에만 앞으로 이동하여 동적 import()
를 사용하여 share.mjs
를 로드합니다.
두 조건 중 하나만 충족하는 모바일 Safari와 같은 브라우저에서는 기능을 로드하지 않습니다.
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
Fugu Greetings에서 Android의 Chrome과 같은 지원 브라우저에서 공유 버튼을 탭하면 기본 공유 시트가 열립니다. 예를 들어 Gmail을 선택하면 이미지가 첨부된 이메일 작성기 위젯이 팝업됩니다.


연락처 선택기 API
다음으로 연락처, 즉 기기의 주소록이나 연락처 관리자 앱에 대해 이야기해 보겠습니다. 인사 카드를 작성할 때 사람의 이름을 올바르게 쓰는 것이 항상 쉬운 것은 아닙니다. 예를 들어 제 친구 세르게이는 자신의 이름이 키릴 문자로 표기되는 것을 선호합니다. 독일어 QWERTZ 키보드를 사용하고 있는데 이름을 입력하는 방법을 모르겠어요. 연락처 선택기 API로 해결할 수 있는 문제입니다. 휴대전화의 연락처 앱에 친구가 저장되어 있으므로 연락처 선택기 API를 사용하여 웹에서 내 연락처를 탭할 수 있습니다.
먼저 액세스할 속성 목록을 지정해야 합니다.
이 경우 이름만 필요하지만 다른 사용 사례에서는 전화번호, 이메일, 아바타 아이콘 또는 실제 주소가 필요할 수 있습니다.
그런 다음 options
객체를 구성하고 multiple
을 true
로 설정하여 항목을 두 개 이상 선택할 수 있습니다.
마지막으로 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에서 연락처 버튼을 탭하고 가장 친한 친구 두 명인 Сергей Михайлович Брин과 劳伦斯·爱德华·"拉里"·佩奇를 선택하면 연락처 선택기가 이름만 표시하고 이메일 주소나 전화번호와 같은 기타 정보는 표시하지 않도록 제한되어 있음을 확인할 수 있습니다. 그런 다음 이름이 내 인사말 카드에 그려집니다.


비동기 클립보드 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()
를 호출하여 가져온 클립보드 항목을 루프 처리해야 합니다.
이는 여러 클립보드 항목이 서로 다른 표현으로 클립보드에 있을 수 있기 때문입니다.
각 클립보드 항목에는 사용 가능한 리소스의 MIME 유형을 알려주는 types
필드가 있습니다.
이전에 가져온 MIME 유형을 전달하여 클립보드 항목의 getType()
메서드를 호출합니다.
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는 배지 API입니다.
설치 가능한 PWA인 Fugu Greetings에는 사용자가 앱 도크나 홈 화면에 배치할 수 있는 앱 아이콘이 있습니다.
API를 재미있게 시연하는 방법은 Fugu Greetings에서 펜 획수 카운터로 사용하는 것입니다.
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');
}
이 예에서는 숫자를 하나씩 펜으로 그렸습니다. 이제 아이콘의 배지 카운터가 7로 표시됩니다.


주기적 백그라운드 동기화 API
매일 새로운 콘텐츠로 하루를 시작하고 싶으신가요? Fugu Greetings 앱의 멋진 기능은 매일 아침 새로운 배경 이미지로 인사말 카드를 시작하도록 영감을 줄 수 있다는 것입니다. 앱은 주기적 백그라운드 동기화 API를 사용하여 이를 달성합니다.
첫 번째 단계는 서비스 워커 등록에서 주기적 동기화 이벤트를 등록하는 것입니다. 'image-of-the-day'
라는 동기화 태그를 수신 대기하고 최소 간격이 하루이므로 사용자는 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);
}
};
두 번째 단계는 서비스 워커에서 periodicsync
이벤트를 수신 대기하는 것입니다.
이벤트 태그가 'image-of-the-day'
(즉, 이전에 등록된 태그)인 경우 당일 이미지가 getImageOfTheDay()
함수를 사용하여 검색되고 결과가 모든 클라이언트에 전파되므로 클라이언트가 캔버스와 캐시를 업데이트할 수 있습니다.
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()
대신 클래식 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에서 Wallpaper 버튼을 누르면 주기적 백그라운드 동기화 API로 매일 업데이트되는 오늘의 인사말 카드 이미지가 표시됩니다.

Notification Triggers API
영감을 많이 받았더라도 시작한 인사말 카드를 완성하려면 약간의 도움이 필요할 때가 있습니다. 이는 알림 트리거 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도 포함하고 싶습니다. 영감이 떠오를 때까지 화면을 오랫동안 응시해야 할 때도 있습니다. 이때 발생할 수 있는 최악의 상황은 화면이 꺼지는 것입니다. 절전 모드 해제 잠금 API를 사용하면 이러한 상황을 방지할 수 있습니다.
첫 번째 단계는 navigator.wakelock.request method()
로 절전 모드 해제 잠금을 획득하는 것입니다.
'screen'
문자열을 전달하여 화면 절전 모드 해제 잠금을 가져옵니다.
그런 다음 웨이크 락이 해제될 때 알림을 받도록 이벤트 리스너를 추가합니다.
예를 들어 탭 표시 상태가 변경될 때 발생할 수 있습니다.
이 경우 탭이 다시 표시되면 절전 모드 해제 잠금을 다시 획득할 수 있습니다.
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
몇 시간 동안 화면을 응시해도 소용이 없고 인사말 카드를 어떻게 해야 할지 전혀 떠오르지 않을 때도 있습니다. 유휴 감지 API를 사용하면 앱이 사용자 유휴 시간을 감지할 수 있습니다. 사용자가 너무 오랫동안 유휴 상태이면 앱이 초기 상태로 재설정되고 캔버스가 지워집니다. 이 API는 알림 권한으로 관리됩니다. 유휴 상태 감지의 많은 프로덕션 사용 사례가 알림과 관련이 있기 때문입니다(예: 사용자가 적극적으로 사용 중인 기기에만 알림을 전송).
알림 권한이 부여되었는지 확인한 후 유휴 감지기를 인스턴스화합니다. 사용자 및 화면 상태를 포함한 유휴 상태 변경을 수신하는 이벤트 리스너를 등록합니다. 사용자는 활성 상태이거나 유휴 상태일 수 있으며 화면은 잠금 해제되어 있거나 잠겨 있을 수 있습니다. 사용자가 유휴 상태이면 캔버스가 지워집니다. 유휴 감지기에 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 앱에서 임시 체크박스를 선택하고 사용자가 너무 오랫동안 유휴 상태이면 캔버스가 지워집니다.

마무리
휴, 정말 힘든 여정이었어요. 하나의 샘플 앱에 이렇게 많은 API가 있습니다. 브라우저에서 지원하지 않는 기능의 다운로드 비용을 사용자에게 청구하지 않는다는 점을 기억하세요. 점진적 개선을 사용하면 관련 코드만 로드됩니다. HTTP/2에서는 요청이 저렴하므로 이 패턴은 많은 애플리케이션에 적합합니다. 하지만 매우 큰 앱의 경우 번들러를 고려하는 것이 좋습니다.

모든 플랫폼에서 모든 기능을 지원하지는 않으므로 브라우저마다 앱이 약간 다르게 보일 수 있지만 핵심 기능은 항상 있습니다. 특정 브라우저의 기능에 따라 점진적으로 향상됩니다. 이러한 기능은 앱이 설치된 앱으로 실행되는지 아니면 브라우저 탭에서 실행되는지에 따라 동일한 브라우저에서도 달라질 수 있습니다.



GitHub에서 Fugu를 포크할 수 있습니다.
Chromium팀은 고급 Fugu API와 관련하여 더 나은 환경을 제공하기 위해 노력하고 있습니다. 앱을 빌드할 때 점진적 개선을 적용하면 모든 사용자가 안정적인 기본 환경을 이용할 수 있지만 더 많은 웹 플랫폼 API를 지원하는 브라우저를 사용하는 사용자는 훨씬 더 나은 환경을 이용할 수 있습니다. 앱에서 점진적 개선을 어떻게 활용하실지 기대됩니다.
감사의 말씀
Fugu Greetings에 기여한 Christian Liebel과 Hemanth HM에게 감사드립니다.
이 문서는 조 메들리와 케이시 바스크가 검토했습니다.
Jake Archibald는 서비스 워커 컨텍스트에서 동적 import()
의 상황을 파악하는 데 도움을 주었습니다.