프로그레시브 웹 앱을 점진적으로 개선하기

최신 브라우저에 맞게 빌드하고 2003년처럼 점진적으로 개선

2003년 3월에 닉 핑크Steve Champeon은 웹 디자인 업계를 놀라게 합니다. Kubernetes의 점진적 개선 핵심 웹 페이지 콘텐츠를 먼저 로드하는 것에 중점을 둔 웹 디자인 전략입니다. 그런 다음 점점 더 섬세한 표현이 그리고 기술적으로 엄격한 프레젠테이션 및 기능을 기반으로 해야 합니다. 2003년에 점진적인 발전은 당시에는 CSS 기능, 눈에 거슬리지 않는 JavaScript 및 확장 가능한 벡터 그래픽만 지원합니다. 2020년 이후의 점진적인 발전은 최신 브라우저 기능입니다.

<ph type="x-smartling-placeholder">
</ph> 점진적으로 개선하여 미래를 위한 포용적인 웹 디자인 Finck와 Champeon의 원본 프레젠테이션에서 발췌한 제목 슬라이드입니다. <ph type="x-smartling-placeholder">
</ph> 슬라이드: 점진적으로 개선하여 미래를 위한 포용적인 웹 디자인. (출처)

최신 JavaScript

JavaScript에 관해서는, 최신 핵심 ES 2015 JavaScript에 대한 브라우저 지원 상황 좋습니다. 새로운 표준에는 프로미스, 모듈, 클래스, 템플릿 리터럴, 화살표 함수, let, const가 포함됩니다. 기본 매개변수, 생성기, 디스트럭처링 할당, 나머지 및 확산, Map/Set, WeakMap/WeakSet모두 지원됩니다.

<ph type="x-smartling-placeholder">
</ph> 모든 주요 브라우저 지원에 관한 ES6 기능 관련 CanIUse 지원 표 <ph type="x-smartling-placeholder">
</ph> ECMAScript 2015 (ES6) 브라우저 지원 표 (출처)

비동기 함수, ES 2017 기능 그리고 개인적으로 가장 좋아하는 것 중 하나인 사용하여 사용할 수 있습니다. asyncawait 키워드는 프라미스 기반 비동기 동작을 지원합니다. 프라미스 체인을 명시적으로 구성할 필요가 없으므로 더 깔끔한 스타일로 작성됩니다.

<ph type="x-smartling-placeholder">
</ph> 모든 주요 브라우저에서 지원되는 비동기 함수에 관한 CanIUse 지원 표 <ph type="x-smartling-placeholder">
</ph> 비동기 함수 브라우저 지원 표 (출처)

또한 선택적 체이닝무효 병합 지원팀에 매우 빠르게 전달했습니다. 아래에서 코드 샘플을 확인할 수 있습니다. 자바스크립트 핵심 기능이 잔디밭보다 오늘입니다.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
드림 <ph type="x-smartling-placeholder">
</ph> 상징적인 Windows XP 녹색 잔디 배경 이미지입니다.
핵심 JavaScript 기능은 녹색입니다. ( 권한이 있는지 확인합니다.)

샘플 앱: Fugu Greetings

이 글에서는 PWA라는 간단한 PWA를 사용하여 작업합니다. 복구 인사말 (GitHub). 이 앱의 이름은 웹의 모든 것을 제공하기 위한 노력의 일환인 Project Fugu 🐡의 후원입니다. 강력한 Android/iOS/데스크톱 애플리케이션입니다. 프로젝트에 대한 자세한 내용은 방문 페이지입니다.

Fugu Greetings는 가상 인사말 카드를 만들고 소중한 사람들에게 보내세요. 데이터 레이크는 PWA의 핵심 개념 그것은 안정적이고 완전히 오프라인으로 사용할 수 있으므로 그래도 사용할 수 있습니다. 또한 설치 가능합니다. 운영체제와 원활하게 통합되며 사용할 수 있습니다

<ph type="x-smartling-placeholder">
</ph> Fugu Greetings PWA(PWA 커뮤니티 로고와 유사한 그림) <ph type="x-smartling-placeholder">
</ph> Fugu Greetings 샘플 앱

점진적 개선

이제 점진적 개선에 대해 이야기해야 합니다. MDN Web Docs 용어집의 정의 개념은 다음과 같습니다.

점진적 개선은 일종의 디자인 철학으로 핵심 콘텐츠와 기능을 최대한 많은 사용자에게 제공하고 최신 기술을 보유한 사용자에게만 최상의 경험 제공 브라우저를 만들 수 있습니다.

특성 감지 는 일반적으로 브라우저가 최신 기능을 처리할 수 있는지 여부를 결정하는 데 사용됩니다. 폴리필은 는 주로 JavaScript로 누락된 기능을 추가하는 데 사용됩니다.

[…]

점진적 개선은 웹 개발자가 모델 학습에 집중하고 최고의 웹사이트를 개발하는 동시에 최적의 웹사이트를 여러 알 수 없는 사용자 에이전트에 적용됩니다. 단계적 성능 저하 는 관련이 있지만 같은 것은 아니며 반대 방향으로 가는 것으로 보이는 경우가 많습니다. 점진적 향상에 이르게 됩니다. 실제로 두 접근 방식 모두 유효하며 종종 서로를 보완할 수 있습니다.

MDN 참여자

각 인사말 카드를 처음부터 시작하는 것은 매우 번거로울 수 있습니다. 그렇다면 사용자가 이미지를 가져와 거기서부터 시작할 수 있는 기능은 없을까요? 전통적인 접근 방식에서는 <input type=file> 드림 요소를 포함해야 합니다. 먼저 요소를 만들고 type'file'로 설정하고 accept 속성에 MIME 유형을 추가합니다. 프로그래매틱 방식으로 '클릭' 변경사항을 수신 대기합니다 이미지를 선택하면 캔버스로 바로 가져옵니다.

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 포함 속성을 사용하고 href로 blob URL을 사용합니다. 프로그래매틱 방식으로 '클릭'합니다. 다운로드를 트리거하고 메모리 누수를 방지하기 위해 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'),
    ]);
  }
};

그러나 File System Access API에 대해 자세히 알아보기 전에 점진적 개선 패턴을 빠르게 강조하겠습니다. 현재 File System Access API를 지원하지 않는 브라우저에서는 기존 스크립트를 로드합니다. 아래에서 Firefox와 Safari의 네트워크 탭을 확인할 수 있습니다.

<ph type="x-smartling-placeholder">
</ph> 기존 파일이 로드되는 모습을 보여주는 Safari Web Inspector <ph type="x-smartling-placeholder">
</ph> Safari Web Inspector 네트워크 탭
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> 레거시 파일이 로드되는 것을 보여주는 Firefox 개발자 도구 <ph type="x-smartling-placeholder">
</ph> Firefox 개발자 도구 네트워크 탭.

하지만 이 API를 지원하는 브라우저인 Chrome에서는 새 스크립트만 로드됩니다. 이 작업은 동적 import()를 사용하며, 이는 모든 최신 브라우저에서 지원을 제공합니다. 앞서 말했듯이 요즘 잔디는 꽤 초록색이야.

<ph type="x-smartling-placeholder">
</ph> 최신 파일이 로드되는 모습을 보여주는 Chrome DevTools <ph type="x-smartling-placeholder">
</ph> Chrome DevTools 네트워크 탭

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를 통해 점진적 개선을 통해 이전처럼 파일을 열 수 있습니다. 가져온 파일이 캔버스에 바로 그려집니다. 수정한 다음 실제 저장 대화상자를 사용하여 저장할 수 있습니다. 여기서 파일의 이름과 저장 위치를 선택할 수 있습니다. 이제 파일을 영원히 보존할 준비가 되었습니다.

<ph type="x-smartling-placeholder">
</ph> 파일 열기 대화상자가 있는 Fugu Greetings 앱 <ph type="x-smartling-placeholder">
</ph> 파일 열기 대화상자
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> 이제 가져온 이미지가 포함된 Fugu Greetings 앱 <ph type="x-smartling-placeholder">
</ph> 가져온 이미지입니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> 수정된 이미지가 있는 Fugu Greetings 앱 <ph type="x-smartling-placeholder">
</ph> 수정된 이미지를 새 파일에 저장

Web Share 및 Web Share Target API

영원히 보관해 두는 게 아니라 인사말 카드를 공유하고 싶을지 모릅니다. 이는 Web Share APIWeb Share Target API를 사용하면 가능합니다. 모바일, 그리고 최근에는 데스크톱 운영체제에 공유 기능이 내장되어 있습니다. 메커니즘을 제공합니다 예를 들어 아래는 내 블로그. 기사 공유 버튼을 클릭하면 기사 링크를 친구와 공유할 수 있습니다. macOS 메시지 앱을 통해 앱을 다시 배포할 수 있습니다.

<ph type="x-smartling-placeholder">
</ph> macOS에서 기사의 공유 버튼으로 트리거되는 데스크톱 Safari의 공유 시트 <ph type="x-smartling-placeholder">
</ph> macOS의 데스크톱 Safari에서 Web Share API

이를 위한 코드는 매우 간단합니다. 저는 navigator.share()에 전화하고 객체의 선택적 title, text, url를 전달합니다. 이미지를 첨부하려면 어떻게 해야 하나요? Web Share 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 인사말 카드 애플리케이션으로 이 작업을 수행하는 방법을 보여드리겠습니다. 먼저 하나의 blob으로 구성된 files 배열이 있는 data 객체를 준비해야 합니다. titletext입니다. 다음으로, 새 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);
  }
};

이전과 마찬가지로 점진적 개선을 사용합니다. navigator 객체에 'share''canShare'가 모두 있는 경우에만 계속 진행합니다. 동적 import()를 통해 share.mjs를 로드합니다. 두 가지 조건 중 하나만 충족하는 모바일 Safari와 같은 브라우저에서 기능을 살펴봤습니다

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

Fugu Greetings에 Android의 Chrome과 같은 지원 브라우저에서 Share 버튼을 탭하면 기본 제공되는 공유 시트가 열립니다. 예를 들어 Gmail을 선택하면 이메일 작성 도구 위젯에 이미지가 첨부되었습니다.

<ph type="x-smartling-placeholder">
</ph> 이미지를 공유할 다양한 앱이 표시된 OS 수준 공유 시트입니다. <ph type="x-smartling-placeholder">
</ph> 파일을 공유할 앱 선택
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> 이미지가 첨부된 Gmail 이메일 작성 위젯 <ph type="x-smartling-placeholder">
</ph> 파일은 Gmail 편지쓰기의 새 이메일에 첨부됩니다.

연락처 선택도구 API

다음으로 기기의 주소록, 즉 연락처에 관해 이야기하겠습니다. 연락처 관리자 앱에 액세스할 수 있습니다. 카드를 만들 때 제대로 작성하기 어려울 수 있습니다 알게 될 수 있습니다. 예를 들어, 세르게이라는 친구가 키릴 문자로 그의 이름을 쓰고 싶어 해요. 나는 독일어 QWERTZ 키보드를 사용하고 이름을 입력하는 방법을 모를 것입니다. 이는 Contact Picker API로 해결할 수 있는 문제입니다. 내 휴대전화의 연락처 앱에 친구가 저장되어 있기 때문에 주소록 선택도구 API를 사용하면 웹에서 연락처를 사용할 수 있습니다.

먼저 액세스하려는 속성 목록을 지정해야 합니다. 이 경우 이름은 다른 사용 사례의 경우 전화번호, 이메일, 아바타, 아이콘, 실제 주소 등입니다. 다음으로 options 객체를 구성하고 multipletrue로 설정하여 더 많이 선택할 수 있습니다. 하나 이상 입력합니다. 마지막으로 원하는 속성을 반환하는 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 인사말에서 Contacts(연락처) 버튼을 탭하고 가장 친한 친구 두 명을 선택하면 серукей лимайлович 애드센스에서рин劳伦·爱德 보내드립니다·"拉里"·佩奇, Kubernetes에서 연락처 선택도구는 이름만 표시하도록 제한되어 있기 때문에 이메일 주소나 전화번호와 같은 다른 정보는 제외하세요. 그런 다음 상대방의 이름이 내 연하장에 그려져 있습니다.

<ph type="x-smartling-placeholder">
</ph> 주소록에 있는 두 연락처의 이름을 보여주는 연락처 선택도구 <ph type="x-smartling-placeholder">
</ph> 주소록에서 연락처 선택 도구로 두 개의 이름을 선택합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> 이전에 선택한 두 연락처의 이름이 인사말 카드에 그려집니다. <ph type="x-smartling-placeholder">
</ph> 그러면 두 이름이 인사 카드에 그려집니다.

비동기 클립보드 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 리소스를 배포합니다 클립보드 항목의 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 앱에서 앱이 클립보드의 텍스트와 이미지를 볼 수 있도록 허용할지 여부입니다.

<ph type="x-smartling-placeholder">
</ph> 클립보드 권한 메시지를 보여주는 Fugu Greetings 앱 <ph type="x-smartling-placeholder">
</ph> 클립보드 권한 메시지

마지막으로, 권한을 수락하고 나면 이미지를 애플리케이션에 붙여넣습니다. 다른 방법도 있습니다. 인사말 카드를 클립보드에 복사하겠습니다. 그런 다음 미리보기를 열고 File(파일), New from Clipboard(클립보드에서 새로 만들기)를 차례로 클릭합니다. 인사말 카드를 제목 없는 새 이미지에 붙여넣습니다.

<ph type="x-smartling-placeholder">
</ph> 제목 없이 방금 붙여넣은 이미지가 있는 macOS 미리보기 앱 <ph type="x-smartling-placeholder">
</ph> macOS Preview 앱에 붙여넣은 이미지

Badging API

또 다른 유용한 API는 Badging API입니다. Fugu Greetings는 설치 가능한 PWA로 앱 아이콘을 앱 도크나 홈 화면에 배치할 수 있습니다. API를 쉽고 재미있게 설명하는 방법은 Fugu Greetings에서 (ab)사용하는 것입니다. 펜 획 카운터로 표현할 수 있습니다. 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까지 숫자를 그렸습니다. 있습니다. 아이콘의 배지 카운터가 이제 7입니다.

<ph type="x-smartling-placeholder">
</ph> 펜 획 한 번으로 1부터 7까지의 숫자가 인사말 카드에 그려져 있습니다. <ph type="x-smartling-placeholder">
</ph> 일곱 가지 펜을 사용하여 1부터 7까지 숫자를 그립니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> Fugu Greetings 앱의 숫자 7을 보여주는 배지 아이콘 <ph type="x-smartling-placeholder">
</ph> 앱 아이콘 배지 형태의 펜 획 카운터

Periodic Background Sync API

새로운 일로 하루를 상쾌하게 시작하고 싶으신가요? Fugu Greetings 앱의 멋진 기능 중 하나는 매일 아침 영감을 준다는 것입니다. 을(를) 새 배경 이미지로 바꿉니다. 앱이 Periodic Background Sync API를 사용합니다. 이러한 목표를 달성할 수 있습니다

첫 번째 단계는 서비스 워커 등록에서 주기적 동기화 이벤트를 등록하는 것입니다. '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);
  }
};

두 번째 단계는 서비스 워커에서 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 인사말에서 배경화면 버튼을 누르면 오늘의 인사말 카드 이미지가 표시됩니다. Periodic Background Sync API를 통해 매일 업데이트됩니다.

<ph type="x-smartling-placeholder">
</ph> 오늘의 새 인사말 이미지가 있는 Fugu Greetings 앱 <ph type="x-smartling-placeholder">
</ph> 배경화면 버튼을 누르면 오늘의 이미지가 표시됩니다.

알림 트리거 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에서 Notifications(알림) 체크박스를 선택하면 알림을 받고 싶을 때 알림을 받습니다.

<ph type="x-smartling-placeholder">
</ph> 사용자에게 인사말 카드를 마무리하라는 알림을 받을 시간을 묻는 메시지가 표시된 Fugu Greetings 앱 <ph type="x-smartling-placeholder">
</ph> 인사말 카드를 마무리하라는 알림을 받도록 로컬 알림을 예약합니다.

Fugu 인사말에서 예약된 알림이 트리거되면 다른 알림과 마찬가지로 표시되지만 아까 말씀드렸듯이 네트워크 연결이 필요하지 않았습니다

<ph type="x-smartling-placeholder">
</ph> Fugu 인사말에서 트리거된 알림을 보여주는 macOS 알림 센터 <ph type="x-smartling-placeholder">
</ph> 트리거된 알림은 macOS 알림 센터에 표시됩니다.

Wake Lock API

Wake Lock API도 포함하고 싶습니다. 무언가 영감이 될 때까지 화면을 충분히 오래 봐야 하는 경우도 있습니다. 키스합니다. 이때 최악의 상황은 화면이 꺼지는 것입니다. Wake Lock API를 사용하면 이러한 상황을 방지할 수 있습니다.

첫 번째 단계는 navigator.wakelock.request method()로 wake lock을 획득하는 것입니다. 화면 wake lock을 획득하기 위해 'screen' 문자열을 전달합니다. 그런 다음 wake lock이 해제되면 알리는 이벤트 리스너를 추가합니다. 예를 들어 탭 공개 상태가 변경될 때 이 문제가 발생할 수 있습니다. 이 경우 탭이 다시 표시되면 wake lock을 다시 설정할 수 있습니다.

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

예, 점진적으로 개선한 것이므로 브라우저가 실행될 때만 로드해야 합니다. API를 지원합니다

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

Fugu Greetings에는 불면증 체크박스가 있는데, 이 체크박스를 선택하면 절전 모드 해제

<ph type="x-smartling-placeholder">
</ph> 불면증 체크박스를 선택하면 화면이 깨어 있는 상태로 유지됩니다. <ph type="x-smartling-placeholder">
</ph> 불면증 체크박스를 선택하면 앱이 켜진 상태로 유지됩니다.

Idle Detection API

가끔은 화면을 몇 시간 동안 봐도 그냥 쓸모없고 연하장으로 뭘 해야 할지 알 수 없습니다. Idle Detection 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 앱에서 임시 체크박스를 선택하면 캔버스가 지워집니다. 사용자가 너무 오랫동안 유휴 상태인 경우

<ph type="x-smartling-placeholder">
</ph> Fugu Greetings 앱의 화면은 사용자가 너무 오랫동안 자리를 비웠을 때 캔버스가 지워진 모습입니다. <ph type="x-smartling-placeholder">
</ph> 임시 체크박스가 선택되어 있고 사용자가 너무 오랫동안 유휴 상태인 경우 캔버스가 지워집니다.

마무리

정말 즐겁습니다. 단 하나의 샘플 앱에 많은 API가 있습니다. 그리고 사용자가 다운로드 비용을 지불하게 하지 않습니다. 특정 기능을 사용하는 경우 점진적 개선을 사용하여 관련 코드만 로드되도록 합니다. 그리고 HTTP/2를 사용하면 요청이 저렴하기 때문에 이 패턴은 애플리케이션, 하지만 매우 큰 앱에는 번들러를 사용하는 것이 좋습니다.

<ph type="x-smartling-placeholder">
</ph> 현재 브라우저에서 지원하는 코드가 포함된 파일 요청만 보여주는 Chrome DevTools Network 패널 <ph type="x-smartling-placeholder">
</ph> 현재 브라우저에서 지원하는 코드가 포함된 파일 요청만 보여주는 Chrome DevTools Network 탭

모든 플랫폼이 모든 기능을 지원하는 것은 아니기 때문에 앱은 브라우저마다 조금씩 다르게 보일 수 있습니다. 핵심 기능은 항상 존재하며 특정 브라우저의 기능에 따라 점차 개선됩니다. 이러한 기능은 하나의 동일한 브라우저에서도 변경될 수 있지만 앱이 설치된 앱으로 실행 중인지 또는 브라우저 탭에서 실행 중인지에 따라 다릅니다.

<ph type="x-smartling-placeholder">
</ph> Android Chrome에서 실행되는 Fugu 인사말로, 사용 가능한 여러 기능을 보여줍니다. <ph type="x-smartling-placeholder">
</ph> Android Chrome에서 실행되는 Fugu 인사말
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> Fugu 인사말이 데스크톱 Safari에서 실행되고, 사용 가능한 기능이 더 적게 표시됩니다. <ph type="x-smartling-placeholder">
</ph> 데스크톱 Safari에서 실행되는 Fugu 인사말
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> 데스크톱 Chrome에서 실행되는 Fugu 인사말로, 사용 가능한 여러 기능을 보여줍니다. <ph type="x-smartling-placeholder">
</ph> 데스크톱 Chrome에서 실행되는 Fugu 인사말

Fugu Greetings 앱에 관심이 있다면 GitHub에서 포크합니다.

<ph type="x-smartling-placeholder">
</ph> GitHub의 Fugu Greetings 저장소 <ph type="x-smartling-placeholder">
</ph> GitHub의 Fugu Greetings

Chromium팀은 고급 Fugu API와 관련하여 잔디를 더 푸르게 만들기 위해 노력하고 있습니다. 앱 개발에 점진적인 개선을 적용함으로써 모두가 좋은 기본 환경을 누리도록 하고 더 많은 웹 플랫폼 API를 지원하는 브라우저를 사용하는 사람들은 훨씬 더 나은 경험을 얻습니다. 여러분께서 앱을 점진적으로 개선하여 어떤 성과를 거두실지 기대하고 있습니다.

감사의 말씀

Christian Liebel에게 감사하고 Hemanth HM은 모두 Fugu Greetings에 기고했습니다. 이 도움말은 Joe Medley가 검토했으며 케이스 바스케스 제이크 아치볼드가 상황을 파악하는 데 도움을 주었습니다. 서비스 워커 컨텍스트에서 동적 import()를 사용합니다.