From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../progressive_web_apps/app_structure/index.html | 274 ++++++++++++++ files/zh-cn/web/progressive_web_apps/index.html | 81 ++++ .../installable_pwas/index.html | 124 ++++++ .../progressive_web_apps/introduction/index.html | 90 +++++ .../network_independent/index.html | 94 +++++ .../offline_service_workers/index.html | 199 ++++++++++ .../progressive_web_apps/re-engageable/index.html | 78 ++++ .../re-engageable_notifications_push/index.html | 242 ++++++++++++ .../web/progressive_web_apps/responsive/index.html | 77 ++++ .../responsive_design_building_blocks/index.html | 421 +++++++++++++++++++++ .../\344\274\230\345\212\277/index.html" | 57 +++ .../\345\212\240\350\275\275/index.html" | 152 ++++++++ .../index.html" | 218 +++++++++++ 13 files changed, 2107 insertions(+) create mode 100644 files/zh-cn/web/progressive_web_apps/app_structure/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/installable_pwas/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/introduction/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/network_independent/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/offline_service_workers/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/re-engageable/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/re-engageable_notifications_push/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/responsive/index.html create mode 100644 files/zh-cn/web/progressive_web_apps/responsive/responsive_design_building_blocks/index.html create mode 100644 "files/zh-cn/web/progressive_web_apps/\344\274\230\345\212\277/index.html" create mode 100644 "files/zh-cn/web/progressive_web_apps/\345\212\240\350\275\275/index.html" create mode 100644 "files/zh-cn/web/progressive_web_apps/\346\267\273\345\212\240\345\210\260\344\270\273\345\261\217\345\271\225/index.html" (limited to 'files/zh-cn/web/progressive_web_apps') diff --git a/files/zh-cn/web/progressive_web_apps/app_structure/index.html b/files/zh-cn/web/progressive_web_apps/app_structure/index.html new file mode 100644 index 0000000000..2ffb7591e0 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/app_structure/index.html @@ -0,0 +1,274 @@ +--- +title: PWA 结构 +slug: Web/Progressive_web_apps/App_structure +translation_of: Web/Progressive_web_apps/App_structure +--- +
{{PreviousMenuNext("Web/Apps/Progressive/Introduction", "Web/Progressive_web_apps/Offline_Service_workers", "Web/Progressive_web_apps")}}
+ +

现在,我们已经知道了 PWAs 背后的原理, 让我们来看一个推荐的 PWA 结构,这来自一个真实的应用 。我们从分析 js13kPWA 这个应用开始:为什么它要这样构建?这样做有什么好处?

+ +

应用架构

+ +

渲染网站主要有两种方法 - 在服务器上或在客户端上。它们都有其优点和缺点,你可以适当地混合使用这两种方法

+ + + +

将SSR与CSR混合可以获得最佳结果 - 您可以在服务器上渲染网站,缓存其内容,然后在客户端需要时更新渲染。 由于SSR,第一页加载很快,并且页面之间的导航是平滑的,因为客户端可以仅使用已更改的部分重新渲染页面。

+ +

PWA可以使用您喜欢的任何方法构建,但有些方法会更适合。最流行的方法是“app shell”概念,它完全按照上述方式混合SSR和CSR,此外还遵循“离线优先”方法,我们将在后续文章中详细解释并在我们的示例应用程序中使用。还有一种涉及Streams API的新方法,我们将简要提及。

+ +

App shell(可以理解为程序的外壳)

+ +

App shell意图尽快加载最小的用户界面,然后缓存它,以便在后续访问时可以离线使用,然后加载应用程序的所有内容。这样,下次有人从设备访问应用程序时,UI立即从缓存加载,并从服务器请求新内容(如果它已在缓存中不可用)。

+ +

这种结构很快,并且当用户立即看到内容而不是加载动画或空白页时也感觉很快。如果网络连接不可用,它还允许离线访问网站。

+ +

我们可以通过service worker控制从服务器请求的内容以及从缓存中检索的内容,这将在下一篇文章中详细解释 - 现在让我们关注结构本身。

+ +

我为什么要用它?

+ +

这种架构允许网站从所有 PWA 功能中获益最多 - 它可以缓存应用 app shell 并以极大地提高性能的方式管理动态内容。 除了基本 shell 之外,您还可以添加其他功能,例如添加到主屏幕推送通知,你可以放心如果用户的浏览器不支持应用程序仍然可以正常工作 - 这是渐进增强的美妙之处。

+ +

该网站感觉就像一个原生应用,具有即时交互和可靠的性能,同时保留了网络的所有好处。

+ +

可链接、渐进式和响应式

+ +

记住 PWA 的优点并在设计应用程序时牢记这一点非常重要。 app shell 方法允许网站:

+ + + +

不同的概念:流

+ +

使用 Streams API 可以实现完全不同的服务器端或客户端渲染方法。在service worker帮助下,流可以极大改进我们解析内容的方式。 

+ +

应用程序外壳模型要求在网站开始呈现之前可以使用所有资源。 它与HTML不同,因为浏览器实际上已经流式传输数据,您可以看到元素在网站上加载和呈现的时间。 但是,为了使JavaScript“可操作”,必须完整地下载它。

+ +

Streams API允许开发人员直接访问来自服务器的数据流 - 如果您想对数据执行操作(例如,向视频添加过滤器),则不再需要等待所有数据流 下载并转换为blob(或其他) - 您可以立即开始。 它提供细粒度控制 - 流可以启动,与另一个流链接,取消,检查错误等等。

+ +

从理论上讲,流媒体是一种更好的模型,但它也更复杂,在撰写本文时(2018年3月),Streams API仍然是一项正在进行的工作,并且尚未在任何主流浏览器中完全可用。 当它可用时,它将是提供内容的最快方式 - 在性能方面的好处将是巨大的。

+ +

有关工作示例和更多信息,请参阅 Streams API documentation.

+ +

示例应用的结构

+ +

js13kPWA 这个网站的结构比较简单: 他包含一个HTML页面(index.heml),一个CSS样式表(style.css),一些图片,js脚本和字体,它的文件结构如下所示:

+ +

Folder structure of js13kPWA.

+ +

HTML页面

+ +

从HTML页面的角度,有一个id为content的标签,这个标签外部的所有内容,都是app shell(程序外壳)

+ +
<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<title>js13kGames A-Frame entries</title>
+	<meta name="description" content="A list of A-Frame entries submitted to the js13kGames 2017 competition, used as an example for the MDN articles about Progressive Web Apps.">
+	<meta name="author" content="end3r">
+	<meta name="theme-color" content="#B12A34">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<meta property="og:image" content="icons/icon-512.png">
+	<link rel="shortcut icon" href="favicon.ico">
+	<link rel="stylesheet" href="style.css">
+	<link rel="manifest" href="js13kpwa.webmanifest">
+	<script src="data/games.js" defer></script>
+	<script src="app.js" defer></script>
+</head>
+<body>
+<header>
+	<p><a class="logo" href="http://js13kgames.com"><img src="img/js13kgames.png" alt="js13kGames"></a></p>
+</header>
+<main>
+	<h1>js13kGames A-Frame entries</h1>
+	<p class="description">List of games submitted to the <a href="http://js13kgames.com/aframe">A-Frame category</a> in the <a href="http://2017.js13kgames.com">js13kGames 2017</a> competition. You can <a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa">fork js13kPWA on GitHub</a> to check its source code.</p>
+	<button id="notifications">Request dummy notifications</button>
+	<section id="content">
+		// Content inserted in here
+	</section>
+</main>
+<footer>
+	<p>© js13kGames 2012-2018, created and maintained by <a href="http://end3r.com">Andrzej Mazur</a> from <a href="http://enclavegames.com">Enclave Games</a>.</p>
+</footer>
+</body>
+</html>
+ +

head标签包含了一些基本的内容,例如标题,网页描述,CSS引用,web manifest文件,game.js和app.js,整个程序的初始化工作,会在app.js中完成。body标签中包括header,main和footer三个部分,header中包含了一张图片,main标签包含了网页标题、描述和一个区域,用来放置网页的主体,我们称之为content,footer内部则放置作者信息和链接(原文:copy and links)

+ +

这个应用的唯一工作就是列出js13kGames 2017年比赛中的A-Frame(一个用来构建虚拟现实(VR)应用的网页开发框架,译者注) 项目列表。如你所见,这是一个很普通的单页应用,目的是用一个简单的东西来展示PWA的真实功能。

+ +

CSS部分

+ +

CSS部分也是尽可能的简单:运用@font-face来加载和使用自定义字体以及给HTML元素提供简单的样式,总体的目标是让页面在移动端和桌面设备上运行(通过使用响应式布局)

+ +

主要的JavaScript代码

+ +

app.js所做的一些工作我们会在下一篇文章中详细分析,首先它用下面的模板生成了content中的内容

+ +
var template = "<article>\n\
+    <img src='data/img/SLUG.jpg' alt='NAME'>\n\
+    <h3>#POS. NAME</h3>\n\
+    <ul>\n\
+    <li><span>Author:</span> <strong>AUTHOR</strong></li>\n\
+    <li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li>\n\
+    <li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li>\n\
+    <li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li>\n\
+    <li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li>\n\
+    </ul>\n\
+</article>";
+var content = '';
+for(var i=0; i<games.length; i++) {
+    var entry = template.replace(/POS/g,(i+1))
+        .replace(/SLUG/g,games[i].slug)
+        .replace(/NAME/g,games[i].name)
+        .replace(/AUTHOR/g,games[i].author)
+        .replace(/TWITTER/g,games[i].twitter)
+        .replace(/WEBSITE/g,games[i].website)
+        .replace(/GITHUB/g,games[i].github);
+    entry = entry.replace('<a href=\'http:///\'></a>','-');
+    content += entry;
+};
+document.getElementById('content').innerHTML = content;
+ +

接着,它注册了一个service worker:

+ +
if('serviceWorker' in navigator) {
+    navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js');
+};
+ +

下面这部分代码实现了一个功能:点击按钮时请求用户授权,用来向用户推送通知

+ +
var button = document.getElementById("notifications");
+button.addEventListener('click', function(e) {
+    Notification.requestPermission().then(function(result) {
+        if(result === 'granted') {
+            randomNotification();
+        }
+    });
+});
+ +

最后这部分是创建通知的代码,它会随机展示游戏列表中的一个项目

+ +
function randomNotification() {
+    var randomItem = Math.floor(Math.random()*games.length);
+    var notifTitle = games[randomItem].name;
+    var notifBody = 'Created by '+games[randomItem].author+'.';
+    var notifImg = 'data/img/'+games[randomItem].slug+'.jpg';
+    var options = {
+        body: notifBody,
+        icon: notifImg
+    }
+    var notif = new Notification(notifTitle, options);
+    setTimeout(randomNotification, 30000);
+}
+ +

service worker

+ +

最后我们来快速浏览一下 service worker相关的文件sw.js,他首先映入game.js这个文件file:

+ +
self.importScripts('data/games.js');
+ +

接着,程序会对app shell和主体内容(content)里面的数据创建一个缓存列表

+ +
var cacheName = 'js13kPWA-v1';
+var appShellFiles = [
+  '/pwa-examples/js13kpwa/',
+  '/pwa-examples/js13kpwa/index.html',
+  '/pwa-examples/js13kpwa/app.js',
+  '/pwa-examples/js13kpwa/style.css',
+  '/pwa-examples/js13kpwa/fonts/graduate.eot',
+  '/pwa-examples/js13kpwa/fonts/graduate.ttf',
+  '/pwa-examples/js13kpwa/fonts/graduate.woff',
+  '/pwa-examples/js13kpwa/favicon.ico',
+  '/pwa-examples/js13kpwa/img/js13kgames.png',
+  '/pwa-examples/js13kpwa/img/bg.png',
+  '/pwa-examples/js13kpwa/icons/icon-32.png',
+  '/pwa-examples/js13kpwa/icons/icon-64.png',
+  '/pwa-examples/js13kpwa/icons/icon-96.png',
+  '/pwa-examples/js13kpwa/icons/icon-128.png',
+  '/pwa-examples/js13kpwa/icons/icon-168.png',
+  '/pwa-examples/js13kpwa/icons/icon-192.png',
+  '/pwa-examples/js13kpwa/icons/icon-256.png',
+  '/pwa-examples/js13kpwa/icons/icon-512.png'
+];
+var gamesImages = [];
+for(var i=0; i<games.length; i++) {
+  gamesImages.push('data/img/'+games[i].slug+'.jpg');
+}
+var contentToCache = appShellFiles.concat(gamesImages);
+ +

下面的代码用来配置service worker,缓存上述列表的工作就在这里发生

+ +
self.addEventListener('install', function(e) {
+  console.log('[Service Worker] Install');
+  e.waitUntil(
+    caches.open(cacheName).then(function(cache) {
+      console.log('[Service Worker] Caching all: app shell and content');
+      return cache.addAll(contentToCache);
+    })
+  );
+});
+ +

最后,如果条件允许,service worker将从缓存中请求content中所需的数据,从而提供离线应用功能

+ +
self.addEventListener('fetch', function(e) {
+  e.respondWith(
+    caches.match(e.request).then(function(r) {
+      console.log('[Service Worker] Fetching resource: '+e.request.url);
+      return r || fetch(e.request).then(function(response) {
+        return caches.open(cacheName).then(function(cache) {
+          console.log('[Service Worker] Caching new resource: '+e.request.url);
+          cache.put(e.request, response.clone());
+          return response;
+        });
+      });
+    })
+  );
+});
+ +

JavaScript 数据

+ +

项目中所用的游戏数据放置在data文件夹下面,以JavaScript对象的形式提供(games.js):

+ +
var games = [
+    {
+        slug: 'lost-in-cyberspace',
+        name: 'Lost in Cyberspace',
+        author: 'Zosia and Bartek',
+        twitter: 'bartaz',
+        website: '',
+        github: 'github.com/bartaz/lost-in-cyberspace'
+    },
+    {
+        slug: 'vernissage',
+        name: 'Vernissage',
+        author: 'Platane',
+        twitter: 'platane_',
+        website: 'github.com/Platane',
+        github: 'github.com/Platane/js13k-2017'
+    },
+// ...
+    {
+        slug: 'emma-3d',
+        name: 'Emma-3D',
+        author: 'Prateek Roushan',
+        twitter: '',
+        website: '',
+        github: 'github.com/coderprateek/Emma-3D'
+    }
+];
+ +

每一个入口在data/img文件夹下面都有属于它自己的图片。这些就是我们的内容数据,我们通过js将这些数据加载到主体内容中

+ +

下一步

+ +

下一篇文章我们会探讨更多的细节,关于service worker.如何帮助我们缓存app shell 和内容,从而让我们实现离线功能

+ +

{{PreviousMenuNext("Web/Apps/Progressive/Introduction", "Web/Apps/Progressive/Offline_Service_workers", "Web/Apps/Progressive")}}

diff --git a/files/zh-cn/web/progressive_web_apps/index.html b/files/zh-cn/web/progressive_web_apps/index.html new file mode 100644 index 0000000000..0b020d08ad --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/index.html @@ -0,0 +1,81 @@ +--- +title: 渐进式 Web 应用(PWA) +slug: Web/Progressive_web_apps +tags: + - Apps + - Modern web apps + - PWA + - Progressive web apps + - web app manifest + - 渐进式增强 + - 渐进式增强的Web应用程序 +translation_of: Web/Progressive_web_apps +--- +

PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。这些应用无处不在、功能丰富,使其具有与原生应用相同的用户体验优势。 这组文档和指南告诉您有关 PWA 的所有信息。

+ +

PWA 的优势

+ +

PWA 是可被发现、易安装、可链接、独立于网络、渐进式、可重用、响应性和安全的。关于这些含义的细节,请参阅 PWA的优势。关于如何实施PWA,请参阅下面列出的指南。

+ +

核心 PWA 指南

+ +

以下指南通过简单的示例和工作原理,展示了实施PWA需要做什么。

+ +
    +
  1. PWA 介绍
  2. +
  3. PWA 结构
  4. +
  5. 通过 Service workers 让 PWA 离线工作
  6. +
  7. 让 PWA 易于安装
  8. +
  9. 通过通知推送让 PWA 可重用
  10. +
  11. 渐进式加载
  12. +
+ +
+
+

技术指南

+ + +
+ +
+

工具

+ +
    +
  • localForage — a nice simple JavaScript library for making client-side data storage really simple; it uses IndexedDB by default, and falls back to Web SQL/Web Storage if necessary.
  • +
  • ServiceWorkerWare — an Express-like microframework for easy Service Worker development.
  • +
  • oghliner — not only a template but a tool for deploying Offline Web Apps to GitHub Pages.
  • +
  • sw-precache — a node module to generate service worker code that will precache specific resources.
  • +
  • workbox — spiritual successor to sw-precache with more advanced caching strategies and easy precaching.
  • +
  • upup — a tiny script that makes sure your site is always there for your users.
  • +
  • The service worker cookbook — A series of excellent service worker/push recipes, showing how to implement an offline app, but also much more.
  • +
+
+
+ +

相关链接

+ + + +
{{QuickLinksWithSubpages("/zh-CN/docs/Web/Apps/Progressive/")}}
diff --git a/files/zh-cn/web/progressive_web_apps/installable_pwas/index.html b/files/zh-cn/web/progressive_web_apps/installable_pwas/index.html new file mode 100644 index 0000000000..87c70deba5 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/installable_pwas/index.html @@ -0,0 +1,124 @@ +--- +title: 让 PWA 易于安装 +slug: Web/Progressive_web_apps/Installable_PWAs +tags: + - PWAs + - a2hs + - js13kGames + - 可安装 + - 添加至主屏幕 + - 渐进式应用 +translation_of: Web/Progressive_web_apps/Installable_PWAs +--- +
{{PreviousMenuNext("Web/Apps/Progressive/Offline_Service_workers", "Web/Apps/Progressive/Re-engageable_Notifications_Push", "Web/Apps/Progressive")}}
+ +

在上一篇文章,我们了解了如何通过service workerjs13kPWA 离线工作,我们可以更进一步,让用户如同本地应用一样在支持的移动浏览器上安装web应用。这篇文章讲述如何通过网页清单和添加到主屏特性来做到这一点。

+ +

这些技术允许应用从设备主屏直接启动,而不是启动浏览器键入URL。你的web应用可以作为一等公民和本地应用肩并肩。这样更容易访问,而且你可以指定应用全屏运行,没有浏览器界面,这样看起来更像一个本地应用。

+ +

要求

+ +

为了成为可安装网站,需要下列事情就位:

+ + + +

清单文件

+ +

关键元素是一份网页清单,它通过JSON形式列举了网站的所有信息。

+ +

它通常位于网页应用的根目录。包含一些有用的信息,比如应用的标题,在一个移动OS上显示的代表该应用的不同大小的图标(例如,主屏图标)的路径,和用于加载或启动画面的背景颜色。这些信息是浏览器在安装web应用时和在主屏上显示应用需要的。

+ +

js13kPWA web应用的js13kpwa.webmanifest文件包含在index.html文件的{{htmlelement("head")}}段,如下行所示:

+ +
<link rel="manifest" href="js13kpwa.webmanifest">
+ +
+

注意:过去有一些常用的扩展名用于清单:manifest.webapp 在Firefox OS应用清单中很流行,许多人使用manifest.json作为网页清单因为内容是JSON格式的。但是,.webmanifest 扩展名是在W3C清单规范 中显示指定的,所有应该坚持这种做法。

+
+ +

文件的内容是这个样子的:

+ +
{
+    "name": "js13kGames Progressive Web App",
+    "short_name": "js13kPWA",
+    "description": "Progressive Web App that lists games submitted to the A-Frame category in the js13kGames 2017 competition.",
+    "icons": [
+        {
+            "src": "icons/icon-32.png",
+            "sizes": "32x32",
+            "type": "image/png"
+        },
+        // ...
+        {
+            "src": "icons/icon-512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        }
+    ],
+    "start_url": "/pwa-examples/js13kpwa/index.html",
+    "display": "fullscreen",
+    "theme_color": "#B12A34",
+    "background_color": "#B12A34"
+}
+ +

大部分字段无需解释,但是让我们分解一下文档并且详细解释这些字段:

+ + + +

一份网页清单最少需要name和一个图标 (带有 src, size 和 type)。description, short_name, 和start_url最好要提供。还有在上述列表之外的字段你可以使用 — 请查看 网页应用清单参考获得详细情况。

+ +

添加到主屏

+ +

"添加到主屏" (或者英语短语a2hs) 是移动浏览器实现的一个特性,它利用网页清单中的信息来在设备主屏上显示应用图标和文字。应用必须满足上述必备条件才可以正常工作。

+ +

当用户使用一个支持的移动浏览器访问一个PWA时,他应该显示一个横幅信息表示可以安装这个应用。

+ +

Add to Home screen popup of js13kPWA.

+ +

在用户单击这个横幅后,安装横幅信息会显示,它是浏览器基于网页清单信息创建的,名字和图标可以在提示中看到。

+ +

Install banner of js13kPWA.

+ +

如果用户单击安装到主屏按钮,会显示这样应用的图标的样子,让用户确认是否安装这个应用。

+ +

Add to Home screen popup of js13kPWA.

+ +

确认之后,应用就安装到主屏上了。

+ +

+ +

之后,用户可以立刻启动并使用应用。注意,PWA有时(取决于浏览器和移动操作系统)会在图标右下角显示一个浏览器图片用于告诉用户这是一个网页应用。

+ +

启动画面

+ +

在一些浏览器中,可以通过清单信息产生一个启动画面,当PWA启动时显示。

+ +

+ +

图标、主题和背景色用于创建这个启动画面。

+ +

总结

+ +

在本文中,我们学习了如何使用网页清单和安装到主屏特性让PWA可安装。

+ +

为了获得添加到主屏的更多信息,请阅读添加到主屏指南。 浏览器支持当前限于安卓火狐58+,移动Chrome和安卓Webview 31+, 安卓Opera32+, 但是这些限制在不久的将来会改善的。.

+ +

现在让我们转移到PWA的最后一个困惑 — 通过推送通知再次启动(re-engagement)。

+ +

{{PreviousMenuNext("Web/Apps/Progressive/Offline_Service_workers", "Web/Apps/Progressive/Re-engageable_Notifications_Push", "Web/Apps/Progressive")}}

+ +

{{QuickLinksWithSubpages("/en-US/docs/Web/Progressive_web_apps/")}}

diff --git a/files/zh-cn/web/progressive_web_apps/introduction/index.html b/files/zh-cn/web/progressive_web_apps/introduction/index.html new file mode 100644 index 0000000000..f455dddb09 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/introduction/index.html @@ -0,0 +1,90 @@ +--- +title: 渐进式 web 应用介绍 +slug: Web/Progressive_web_apps/Introduction +translation_of: Web/Progressive_web_apps/Introduction +--- +
{{NextMenu("Web/Apps/Progressive/App_structure", "Web/Apps/Progressive")}}
+ +

这篇文章将会为你介绍渐进式web应用(PWA),讨论一下它们到底是什么,相比于常规的web应用,它又带来了哪些优势。

+ +

什么是渐进式web应用?

+ +

PWA应用是指那些使用指定技术和标准模式来开发的web应用,这将同时赋予它们web应用和原生应用的特性。

+ +

例如,web应用更加易于发现——相比于安装应用,访问一个网站显然更加容易和迅速,并且你可以通过一个链接来分享web应用。

+ +

在另一方面,原生应用与操作系统可以更加完美的整合,也因此为用户提供了无缝的用户体验。你可以通过安装应用使得它在离线的状态下也可以运行,并且相较于使用浏览器访问,用户也更喜欢通过点击主页上的图标来访问它们喜爱的应用。

+ +

PWA赋予了我们创建同时拥有以上两种优势的应用的能力。

+ +

这并不是一个新概念——这样的想法在过去已经在web平台上通过许多方法出现了多次。渐进式增强和响应式设计已经可以让我们构建对移动端友好的网站。在多年以前的Firefox OS的生态系统中离线运行和安装web应用已经成为了可能。

+ +

PWAs, 不但如此,更是提供了所有的甚至是更多的特性,来让web更加优秀。

+ +

什么使应用成为PWA?

+ +

正如前文所述,PWA不是使用一种技术创建的。它们代表了构建Web应用程序的新理念,涉及一些特定的模式,API和其他功能。如果一个Web App从一开始就是PWA,那就不那么明显了。当应用程序满足某些要求时,可以将其视为PWA,或者实现一组给定的功能:离线工作,可安装,易于同步,可以发送推送通知等。

+ +

此外,还有一些工具可以按百分比衡量应用的完整性。(Lighthouse目前是最受欢迎的工具)通过实施各种技术优势,我们可以使应用程序更加渐进式,从而最终获得更高的Lighthouse 得分。但这只是一个粗略的指标。

+ +

这里有一些关键的原则来辨别一个web应用是否是一个PWA应用。它应该具有以下特点:

+ + + +

这么做值得吗?

+ +

当然值得!只需要相对较小的代价就可以实现PWA的核心特性,这样的优势是巨大的。例如:

+ + + +

有许多知名的成功企业正在尝试PWA的方式,选择一个增强的网站体验而不是一个原生应用。事实上它们也确实从中获得了显而易见的益处。  PWA Stats 这个网站上分享了许多案例研究,这都证明了以上这些优势。

+ +

最著名的案例可能是Flipkart Lite--印度最大的电子商务网站,在2015年重建为渐进式网络应用程序,转化率提高了70%。AliExpress PWA也看到了比web app或native app更好的结果,新用户的转换率提高了104%。鉴于其利润增长以及转换为PWA所需的工作量相对较少,优势显而易见。

+ +

couponmoto 这样的早期新兴创业公司也开始使用渐进式网络应用来推动更多的消费者参与,这表明渐进式网络应用可以帮助小公司和大公司更有效地(重新)吸引用户。

+ +

您可以查看 pwa.rocks 上的列表以获取更多示例。 特别值得一提的是hnpwa.com,它列出了 Hacker News 网站的示例实现(而不是通常的 TodoMVC 应用程序),您可以在其中看到各种前端框架的使用。

+ +

您甚至可以使用 PWABuilder 网站在线生成 PWA。

+ +

对于 service worker 和推送特定信息,请务必查看 Service Worker 手册,一个在现代站点中使用 service worker 的方法集合。

+ +

PWA是非常值得尝试的,您可以自己查看它是否适用于您的应用程序。

+ +

浏览器支持

+ +

如前所述,PWA 不依赖于单个 API ,而是使用各种技术来实现提供最佳 Web 体验的目标。

+ +

PWA 所需的关键要素是 service worker 支持。 值得庆幸的是,桌面和移动设备上的所有主流浏览器都支持 service worker

+ +

其他功能,如 Web App ManifestPushNotifications Add to Home Screen 功能也得到了广泛的支持。 目前,Safari 对 Web App Manifest Add to Home Screen 的支持有限,并且不支持 Web 推送通知。 但是,其他主流浏览器支持所有这些功能。

+ +

其中一些 API 是实验性的,文档仍在草稿中,但是看到像 Flipkart 和 AliExpress 这样的成功案例应该说服您尝试在 Web 应用程序中实现一些 PWA 功能。

+ +

最重要的是,您应该遵循渐进增强规则 - 仅在支持它们的情况下使用提供此类增强功能的技术,但如果不支持,则仍然提供应用程序的基本功能。 这样每个人都可以使用它,但那些使用现代浏览器的人将更多地受益于 PWA 功能。

+ +

一个示例应用程序

+ +

在本系列文章中,我们将研究一个超级简单网站的源代码,该网站列出了  js13kGames 2017 竞赛中提交给 A-Frame category 的游戏的相关信息。 您不必考虑网站上的实际内容 - 主要是学习如何在您自己的项目中使用PWA功能。

+ +

您可以在 mdn.github.io/pwa-examples/js13kpwa 找到在线版本(另请参阅源代码),我们将在接下来的几篇文章中对其进行详细解释。

+ +

现在,让我们转到本系列的第二部分,我们将看看我们的示例应用程序的结构。

+ +

{{NextMenu("Web/Apps/Progressive/App_structure", "Web/Apps/Progressive")}}

+ +
{{QuickLinksWithSubpages("/en-US/docs/Web/Apps/Progressive/")}}
diff --git a/files/zh-cn/web/progressive_web_apps/network_independent/index.html b/files/zh-cn/web/progressive_web_apps/network_independent/index.html new file mode 100644 index 0000000000..003ea1f351 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/network_independent/index.html @@ -0,0 +1,94 @@ +--- +title: 网络独立 +slug: Web/Progressive_web_apps/Network_independent +tags: + - Application Shell + - IndexedDB + - PWA + - Progressive web apps + - Service Workers + - Web Storage + - localStorage +translation_of: Web/Progressive_web_apps +--- +
+
当网络不可靠,甚至不存在时,现代网络应用程序仍可以工作。没有更多的空白连接错误页面或恐龙穿过沙漠。除了离线高速缓存和服务工作者之外,UI和内容之间的一个明确的分隔可让您存储应用程序的数据和核心资产,以备将来使用。
+ +
+
+ +

The basic ideas behind network independence are to be able to:

+ + + +

核心指南

+ +
+
Using service workers
+
A simple guide for those new to the Service Worker API.
+
Using IndexedDB
+
The basics of IndexedDB, explained in detail.
+
Using the Web Storage API
+
The Web storage API made simple.
+
Instant Loading Web Apps with An Application Shell Architecture
+
A guide to using the App Shell coding pattern to create apps that load quickly.
+
+ +

技术

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
技术描述支持概览最新规范
Service workersJavaScript running in a special worker context that is run by the browser under certain circumstances such as fetch or push events. These allow the service worker to intercept responses and customise them in any way you want, for example caching assets for offline use before they are served.Experimental: Chrome and Firefox (more detail){{SpecName('Service Workers')}}
IndexedDBA transactional database system that allows complex client-side data storage to be controlled via JavaScript.Widespread across modern browsers (more detail){{SpecName('IndexedDB')}}
Web StorageA simple API for storing name-value pairs on the client-side.Widespread (more detail){{SpecName('Web Storage')}}
+ +

工具

+ +
+
localForage
+
A nice simple JavaScript library for making client-side data storage really simple; it uses IndexedDB by default, and falls back to Web SQL/Web Storage if necessary.
+
ServiceWorkerWare
+
An Express-like microframework for easy Service Worker development.
+
oghliner
+
Not only a template but a tool for deploying Offline Web Apps to GitHub Pages.
+
sw-precache
+
A node module to generate service worker code that will precache specific resources.
+
upup
+
A tiny script that makes sure your site is always there for your users.
+
+ +

参见

+ +
+
The service worker cookbook
+
A series of excellent service worker recipes, showing how to implement an offline app, but also much more.
+
diff --git a/files/zh-cn/web/progressive_web_apps/offline_service_workers/index.html b/files/zh-cn/web/progressive_web_apps/offline_service_workers/index.html new file mode 100644 index 0000000000..fddc3ead25 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/offline_service_workers/index.html @@ -0,0 +1,199 @@ +--- +title: 通过 Service workers 让 PWA 离线工作 +slug: Web/Progressive_web_apps/Offline_Service_workers +translation_of: Web/Progressive_web_apps/Offline_Service_workers +--- +
{{PreviousMenuNext("Web/Apps/Progressive/App_structure", "Web/Apps/Progressive/Installable_PWAs", "Web/Apps/Progressive")}}
+ +

我们已经看到了 js13kPWA 的结构,并且看到了基本的 shell 启动和运行,那么让我们看看如何使用 Service Worker 实现离线功能。 在本文中,我们将看看它是如何在 js13kPWA example 中使用的(另请参阅源代码)。 我们将研究如何添加脱机功能。

+ +

Service workers 解释

+ +

Service Workers是浏览器和网络之间的虚拟代理。 他们最终解决了前端开发人员多年来一直在努力解决的问题 - 最值得注意的是解决了如何正确缓存网站资源并使其在用户设备离线时可用。

+ +

它们的运行在一个与我们页面的 JavaScript 主线程独立的线程上,并且没有对 DOM 结构的任何访问权限。 这引入了与传统 Web 编程不同的方法 - API 是非阻塞的,并且可以在不同的上下文之间发送和接收信息。 您可分配给 Service Worker 一些任务,并在使用基于 Promise 的方法当任务完成时收到结果。

+ +

他们不仅仅提供离线功能,还提供包括处理通知,在单独的线程上执行繁重的计算等。Service workers 非常强大,因为他们可以控制网络请求,修改网络请求,返回缓存的自定义响应,或合成响应。

+ +

安全

+ +

因为它们非常强大,所以 Service Workers 只能在安全的上下文中执行(即 HTTPS )。 如果您想在将代码推送到生产环境之前先进行实验,则可以始终在本地主机上进行测试或设置 GitHub 页面 - 两者都支持HTTPS。

+ +

离线优先

+ +

“离线优先”或“缓存优先”模式是向用户提供内容的最流行策略。 如果资源已缓存且可脱机使用,请在尝试从服务器下载资源之前先将其返回。 如果它已经不在缓存中,请下载并缓存以备将来使用。

+ +

PWA 渐进增强

+ +

当作为渐进增强功能正确实施时,service workers 可以通过提供离线支持使支持 Service​Worker API 的现代浏览器的用户受益,但不会为使用旧版浏览器的用户破坏任何内容。

+ +

js13kPWA应用程序中的 Service workers

+ +

理论足够了,让我们看一些源代码!

+ +

注册 Service Worker

+ +

首先在 app.js 文件中查看注册新 Service Worker 的代码:

+ +
if('serviceWorker' in navigator) {
+    navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js');
+};
+ +

如果浏览器支持 service worker API,则使用 ServiceWorkerContainer.register() 方法对该站点进行注册。 其内容在 sw.js 文件中,可以在注册成功后执行。 它是 app.js 文件中唯一的Service Worker代码; 其他关于 Service Worker 的内容都写在 sw.js 文件中。

+ +

Service Worker 的生命周期

+ +

注册完成后,sw.js 文件会自动下载,然后安装,最后激活。

+ +

安装

+ +

API 允许我们为我们感兴趣的关键事件添加事件监听器 - 第一个是 install 事件:

+ +
self.addEventListener('install', function(e) {
+    console.log('[Service Worker] Install');
+});
+ +

install 的监听函数中, 我们可以初始化缓存以及添加离线应用时所需的文件,我们的 js13kPWA 应用确实也是这么做的。

+ +

首先,创建一个作为缓存的名字的变量,app shell所需的文件被记录在一个数组上

+ +
var cacheName = 'js13kPWA-v1';
+var appShellFiles = [
+  '/pwa-examples/js13kpwa/',
+  '/pwa-examples/js13kpwa/index.html',
+  '/pwa-examples/js13kpwa/app.js',
+  '/pwa-examples/js13kpwa/style.css',
+  '/pwa-examples/js13kpwa/fonts/graduate.eot',
+  '/pwa-examples/js13kpwa/fonts/graduate.ttf',
+  '/pwa-examples/js13kpwa/fonts/graduate.woff',
+  '/pwa-examples/js13kpwa/favicon.ico',
+  '/pwa-examples/js13kpwa/img/js13kgames.png',
+  '/pwa-examples/js13kpwa/img/bg.png',
+  '/pwa-examples/js13kpwa/icons/icon-32.png',
+  '/pwa-examples/js13kpwa/icons/icon-64.png',
+  '/pwa-examples/js13kpwa/icons/icon-96.png',
+  '/pwa-examples/js13kpwa/icons/icon-128.png',
+  '/pwa-examples/js13kpwa/icons/icon-168.png',
+  '/pwa-examples/js13kpwa/icons/icon-192.png',
+  '/pwa-examples/js13kpwa/icons/icon-256.png',
+  '/pwa-examples/js13kpwa/icons/icon-512.png'
+];
+ +

接下来,从data/games.js的内容中解析出来的图片链接被赋值到另一个数组上,之后,两个数组会用{{jsxref("Array.prototype.concat()")}}方法合并起来。

+ +
var gamesImages = [];
+for(var i=0; i<games.length; i++) {
+  gamesImages.push('data/img/'+games[i].slug+'.jpg');
+}
+var contentToCache = appShellFiles.concat(gamesImages);
+ +

接着我们可以监听install事件:

+ +
self.addEventListener('install', function(e) {
+  console.log('[Service Worker] Install');
+  e.waitUntil(
+    caches.open(cacheName).then(function(cache) {
+          console.log('[Service Worker] Caching all: app shell and content');
+      return cache.addAll(contentToCache);
+    })
+  );
+});
+ +

有两件事需要在这里解释一下:{{domxref("ExtendableEvent.waitUntil")}}做了什么 ?{{domxref("Caches","caches")}} 对象是什么东西?

+ +

service worker会等到 waitUntil 里面的代码执行完毕之后才开始安装。它返回一个promise —— 这个过程时必须的,因为安装过程需要一些时间,我们必须等待它完成 。

+ +

caches 是一个特殊的 {{domxref("CacheStorage")}} 对象,它能在Service Worker指定的范围内提供数据存储的能力(service worker在注册时,第二个参数是选填的,可以被用来指定你想让 service worker 控制的内容的子目录,译者注),在service worker中使用web storage 将不会有效果,因为web storage的执行是同步的(此处理解为web storage并不返回一个promise,译者注),所以我们使用Cache API作为替代。

+ +

这里,我们使用给定的名字开启了一个缓存,并且将我们的应用所需要缓存的文件全部添加进去,当我们再次加载这些资源时,对应的缓存就是可用的(通过请求的url确定缓存是否命中)。

+ +

Activation

+ +

还有一个activate 事件,它的用法跟install 事件相同。这个事件通常用来删除那些我们已经不需要的文件或者做一些清理工作。因为在我们的app里面没有使用到,这里我们暂时跳过它。

+ +

响应请求

+ +

每次当我们的应用发起一个http请求时,我们还有一个fetch 事件可以使用。这个事件对我们来说非常有用,它允许我们拦截请求并对请求作出自定义的响应,下面是一个简单的例子

+ +
self.addEventListener('fetch', function(e) {
+    console.log('[Service Worker] Fetched resource '+e.request.url);
+});
+ +

请求的响应可以是任何我们想要的东西:请求过的文件缓存下来的副本,或者一段做了具体操作的JavaScript代码,拥有无限的可能。

+ +

在我们的示例代码中,当缓存存在时,我们使用缓存来提供服务而不是重新请求数据。不管当前应用是在线还是离线,我们都这么做。当请求的文件不在缓存中时,我们会在响应之前将数据添加到缓存中。

+ +
self.addEventListener('fetch', function(e) {
+  e.respondWith(
+    caches.match(e.request).then(function(r) {
+          console.log('[Service Worker] Fetching resource: '+e.request.url);
+      return r || fetch(e.request).then(function(response) {
+                return caches.open(cacheName).then(function(cache) {
+          console.log('[Service Worker] Caching new resource: '+e.request.url);
+          cache.put(e.request, response.clone());
+          return response;
+        });
+      });
+    })
+  );
+});
+ +

上述代码中,对于请求我们首先会在缓存中查找资源是否被缓存,如果有,将会返回缓存的资源,如果不存在,会转而从网络中请求数据,然后将它缓存起来,这样下次有相同的请求发生时,我们就可以直接使用缓存。

+ +

{{domxref("FetchEvent.respondWith")}} 方法将会接管响应控制,它会作为服务器和应用之间的代理服务。它允许我们对每一个请求作出我们想要的任何响应:Service Worker会处理这一切,从缓存中获取这些数据,并在需要的情况下对它们进行修改。

+ +

就是这样,我们的应用会在install触发时缓存资源,并且在fetch事件触发时返回缓存中的资源,这就是为什么它甚至在离线状态下也能使用的原因。当我们添加新的内容时,他也会随时被缓存下来。

+ +

更新

+ +

还有一点需要考虑:当我们的应用有了一个新版本,并且它包含了一些可用的新资源 时,我们应该如何去更新它的Service Worker?我们存放在缓存名称中的版本号是这个问题的关键:

+ +
var cacheName = 'js13kPWA-v1';
+ +

当我们把版本号更新到v2,service worker会将我们所有的文件(包括那些新的文件)添加到一个新的缓存中。

+ +
contentToCache.push('/pwa-examples/js13kpwa/icons/icon-32.png');
+
+// ...
+
+self.addEventListener('install', function(e) {
+  e.waitUntil(
+    caches.open('js13kPWA-v2').then(function(cache) {
+      return cache.addAll(contentToCache);
+    })
+  );
+});
+ +

这个时候一个新的service worker会在后台被安装,而旧的service worker仍然会正确的运行,直到没有任何页面使用到它为止,这时候新的service worker将会被激活,然后接管所有的页面。

+ +

缓存的清理

+ +

还记得我们前面跳过的那个activate 事件吗?它可以用来清理那些我们不再需要的缓存:

+ +
self.addEventListener('activate', function(e) {
+  e.waitUntil(
+    caches.keys().then(function(keyList) {
+      return Promise.all(keyList.map(function(key) {
+        if(cacheName.indexOf(key) === -1) {
+          return caches.delete(key);
+        }
+      }));
+    })
+  );
+});
+ +

这样能确保只有那些我们需要的文件会保留在缓存中,我们不需要留下任何的垃圾,毕竟浏览器的缓存空间是有限的,手动清理掉这些不需要的缓存是一个不错的主意。

+ +

其他用途

+ +

从缓存中加载资源并不是Service Worker的唯一能力,如果你有比较耗时的计算,你可以把它们从主线程中抽离出来,在Service Worker中进行计算,最后在它们计算完毕的时候从Service Worker中取得计算结果。性能方面,你可以在Service Worker中对即将使用到的资源进行预加载,这样当你使用到这些资源的时候,应用的加载速度会更快。

+ +

总结

+ +

在这篇文章中我们简单的了解了如何使用service workers让你的PWA实现离线功能。如果你想要学习更多关于Service Worker API的概念以及使用service worker方面的相关细节,你可以进一步查阅我们的文档。

+ +

Service Workers在处理消息推送 的时候也会经常被用到,这一部分我们将会在后面的章节进行讨论。

+ +

{{PreviousMenuNext("Web/Apps/Progressive/App_structure", "Web/Apps/Progressive/Installable_PWAs", "Web/Apps/Progressive")}}

+ +
{{QuickLinksWithSubpages("/en-US/docs/Web/Apps/Progressive/")}}
diff --git a/files/zh-cn/web/progressive_web_apps/re-engageable/index.html b/files/zh-cn/web/progressive_web_apps/re-engageable/index.html new file mode 100644 index 0000000000..4fb4a7475f --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/re-engageable/index.html @@ -0,0 +1,78 @@ +--- +title: Re-engageable +slug: Web/Progressive_web_apps/Re-engageable +tags: + - Modern web apps + - Notifications API + - Progressive web apps + - Push API + - Service Workers +translation_of: Web/Progressive_web_apps +--- +
+
原生平台一个主要优势是,用户可以轻松通过更新或加载新内容,即使用户没有正在查看应用程序或者使用他们的设备。现在的Web应用程序现在也可以使用Web Push API等技术实现这样的功能。
+ +
+
+ +

核心指南

+ +
+
Using service workers
+
A simple guide for those new to the Service Worker API.
+
Using the Push API
+
Learn the essentials behind the Web Push API.
+
Using the Notifications API
+
Web notifications in a nutshell.
+
+ +

技术

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
技术描述浏览器支持最新规范
Service workersJavaScript running in a special worker context that is run by the browser under certain circumstances such as fetch or push events. These allow the service worker to intercept responses and customise them in any way you want, for example caching assets for offline use before they are served.Experimental: Chrome and Firefox (more detail){{SpecName('Service Workers')}}
Push APIWhen subscribed to, the push service provides an endpoint that can be used by a server to send a push message to a web app under the control of a particular service worker.Experimental: chrome and Firefox (more detail){{SpecName("Push API")}}
Notifications APIFires system notifications directly from web applications.Widespreadin modern browsers (more detail){{SpecName('Web Notifications')}}
+ +

工具

+ +
+
ServiceWorkerWare
+
An Express-like microframework for easy Service Worker development.
+
oghliner
+
Not only a template but a tool for deploying Offline Web Apps to GitHub Pages.
+
sw-precache
+
A node module to generate service worker code that will precache specific resources.
+
+ +

参见

+ +
+
The service worker cookbook
+
A series of excellent service worker recipes, showing how to implement an offline app, but also much more.
+
diff --git a/files/zh-cn/web/progressive_web_apps/re-engageable_notifications_push/index.html b/files/zh-cn/web/progressive_web_apps/re-engageable_notifications_push/index.html new file mode 100644 index 0000000000..bb214c16a4 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/re-engageable_notifications_push/index.html @@ -0,0 +1,242 @@ +--- +title: 通过通知推送让 PWA 可重用 +slug: Web/Progressive_web_apps/Re-engageable_Notifications_Push +translation_of: Web/Progressive_web_apps/Re-engageable_Notifications_Push +--- +
{{PreviousMenuNext("Web/Apps/Progressive/Installable_PWAs", "Web/Apps/Progressive/Loading", "Web/Apps/Progressive")}}
+ +

拥有本地缓存能力来实现离线应用是一个强大的特性。允许用户在主屏幕上安装Web应用程序则显得更了不起。但与其单纯依赖用户手动打开应用,我们可以做得更多的东西,我们可以利用消息和通知推送来自动重启应用并随时提供新的内容。

+ +

两个API,一个目标

+ +

推送 API通知 API是两个相互独立的API,但当你的应用想实现一些有趣实用的功能时他们可以配合得很好。推送API可以实现从服务端推送新的内容而不需要客户端发起请求,它是由应用的service worker来实现的。通知功能则可以通过service worker来向用户展示一些新的信息,或者至少提醒用户应用已经更新了某些功能。

+ +

这些工作是在浏览器外部实现的,跟service worker一样,所以即使应用被隐藏到后台甚至应用已经被关闭了,我们仍然能够推送更新或者推送通知给用户。

+ +

通知

+ +

让我们先从通知API开始,它能够脱离推送API单独工作,但两者结合起来会非常的有用,我们先单独看看它能做什么。

+ +

请求授权

+ +

为了能够显示通知,我们需要先请求用户的授权。最佳的实践经验告诉我们,我们应该引导用户点击一个按钮的时候弹出一个授权窗口,而不是立刻显示通知(授权之前无法显示通知,译者注):

+ +
var button = document.getElementById("notifications");
+button.addEventListener('click', function(e) {
+    Notification.requestPermission().then(function(result) {
+        if(result === 'granted') {
+            randomNotification();
+        }
+    });
+});
+ +

下面的图片展示了通过系统的通知服务显示一个授权窗口:

+ +

Notification of js13kPWA.

+ +

当用户确定接收通知,我们得到应用就可以获得推送通知的功能。用户的授权的结果有三种,default,granted 或者denied,当用户没有做出选择的时候,授权结果会返回defalut,另外两种结果分别是用户选择了授权或者拒绝授权。

+ +

一旦用户选择授权,这个授权结果对通知API和推送API两者都有效

+ +

创建一个通知

+ +

我们的示例应用通过可用的数据来创建通知 —— 随机选择一个游戏数据来填充通知的主体:用游戏的名称来作为通知的标题,通知的主体会提及游戏的作者,用游戏的图片来作为通知的图标:

+ +
function randomNotification() {
+    var randomItem = Math.floor(Math.random()*games.length);
+    var notifTitle = games[randomItem].name;
+    var notifBody = 'Created by '+games[randomItem].author+'.';
+    var notifImg = 'data/img/'+games[randomItem].slug+'.jpg';
+    var options = {
+        body: notifBody,
+        icon: notifImg
+    }
+    var notif = new Notification(notifTitle, options);
+    setTimeout(randomNotification, 30000);
+}
+ +

上述代码每隔三十秒会创建一个通知,直到用户开始觉得通知有点烦人并手动关闭掉它为止。(对于一个真正的app,这种通知的频率必须进行严格的控制,它的内容也要更加有用才行)通知API的优势在于它使用了操作系统的通知功能。这意味着即使用户当前并没有在使用我们的应用,我们的通知也能够展示,他看起来跟一个原生应用发出的通知差不多。

+ +

推送

+ +

推送比通知要复杂一些,我们需要从服务端订阅一个服务,之后服务端会推送数据到客户端应用。应用的Service Worker将会接收到从服务端推送的数据,这些数据可以用来做通知推送,或者实现其他的需求。

+ +

这个技术还处在非常初级的阶段,一些运行中的例子使用了谷歌云的消息推送平台,但他们正在被重写以支持 VAPID (Voluntary Application Identification),它为你的应用提供了一层额外的安全防护。你可以检查一下这个例子Service Workers Cookbook examples,尝试用Firebase,配置一个消息推送服务,或者构建自己的推送服务(例如使用Node.js).

+ +

之前提到,为了接收到推送的消息,你需要有一个service worker,这部分的基础知识我们已经在文章 通过 Service workers 让 PWA 离线工作 里面解释过。在service worker 内部,存在一个消息推送服务订阅机制。

+ +
registration.pushManager.getSubscription() .then( /* ... */ );
+ +

一旦用户订阅服务,他们就能接收到服务器推送的通知。

+ +

从服务端的角度来看,出于安全的目的,这整个过程必须使用非对称加密技术进行加密 —— 允许用户不安全的使用应用程序来发送推送消息会是一个很糟糕的主意。你可以通过阅读Web推送数据加密测试页里面的详细信息来保护你的服务器。当用户订阅服务时,服务器会储存所有的接收到的信息以便在后续需要的时候能将信息推送出去。

+ +

为了能够接收到推送的消息,我们需要在Service Worker文件里面监听{{event("push")}}事件:

+ +
self.addEventListener('push', function(e) { /* ... */ });
+ +

这些数据将会被接收后通过通知的方式立刻展现给用户。举个例子,这个功能可以用来提醒用户一些事情,或者让他们知道我们的应用更新了一些新的内容并且已经可用。

+ +

推送例子

+ +

推送需要服务端参与工作,但我们没办法将服务端的例子包含进 js13kPWA这个应用里面,因为应用托管在github上,而github只提供静态页面托管。Service Worker Cookbook 这个页面上有详细的说明,demo请看 Push Payload Demo.

+ +

这个demo包含三个文件:

+ + + +

让我们来一起探索一下这些代码

+ +

index.js

+ +

index.js以注册service worker开始:

+ +
navigator.serviceWorker.register('service-worker.js')
+.then(function(registration) {
+  return registration.pushManager.getSubscription()
+  .then(async function(subscription) {
+      // 注册部分
+  });
+})
+.then(function(subscription) {
+    // 订阅部分
+});
+ +

这个service worker比我们在js13kPWA demo看到的要稍微复杂一些,在这部分代码里,注册完成之后,我们使用registration对象来发起订阅,然后使用subscription对象来结束这整个流程。

+ +

注册部分的代码看起来大致是这个样子:

+ +
if(subscription) {
+    return subscription;
+}
+ +

如果用户已经完成订阅,我们直接返回subscription对象并且跳转到订阅部分。如果没有,我们会初始化一个新的subscription对象:

+ +
const response = await fetch('./vapidPublicKey');
+const vapidPublicKey = await response.text();
+const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
+ +

app会从服务器请求一个公钥并且把响应结果转化成文本,最后它还需要转化成一个Uint8Array(为了兼容chome)。如果你想学习更多关于VAPID的内容可以阅读这篇博客 Sending VAPID identified WebPush Notifications via Mozilla’s Push Service

+ +

app现在可以使用{{domxref("PushManager")}} 来订阅新用户。这个方法支持传递两个参数 ——第一个是userVisibleOnly: true  ,意思是发送给用户的所有通知对他们都是可见的,第二个是 applicationServerKey ,这个参数包含我们之前从服务端取得并转化的VAPID key。

+ +
return registration.pushManager.subscribe({
+    userVisibleOnly: true,
+    applicationServerKey: convertedVapidKey
+});
+ +

现在我们把注意力转移到订阅部分 —— app首先使用Fetch subscription以json的方式发送给服务器。

+ +
fetch('./register', {
+    method: 'post',
+    headers: {
+        'Content-type': 'application/json'
+    },
+    body: JSON.stringify({
+        subscription: subscription
+    }),
+});
+ +

接着注册按钮绑定了一个函数

+ +
document.getElementById('doIt').onclick = function() {
+    const payload = document.getElementById('notification-payload').value;
+    const delay = document.getElementById('notification-delay').value;
+    const ttl = document.getElementById('notification-ttl').value;
+
+    fetch('./sendNotification', {
+        method: 'post',
+        headers: {
+            'Content-type': 'application/json'
+        },
+        body: JSON.stringify({
+            subscription: subscription,
+            payload: payload,
+            delay: delay,
+            ttl: ttl,
+        }),
+    });
+};
+ +

当按钮被点击的时候, fetch 会请求服务器根据给定的参数发送通知, payload 参数包含通知里面要显示的文本,delay 参数定义了通知将会延迟展示的秒数,ttl 是 time-to-live 的缩写,用来设置通知在服务器上的存活时间,依然是以秒为单位。

+ +

接着,我们进入下一个JavaScript文件

+ +

server.js

+ +

服务端的代码是用Node.js编写的,它需要被托管在合适的地方。这部分内容已经可以用一个完全独立的主体再写一篇文章了。所以这里我们只提供一个简单的概览。

+ +

web-push 模块被用来配置 VAPID 密钥,如果它尚未可用,你可以选择性的配置他们。

+ +
const webPush = require('web-push');
+
+if (!process.env.VAPID_PUBLIC_KEY || !process.env.VAPID_PRIVATE_KEY) {
+  console.log("You must set the VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY "+
+    "environment variables. You can use the following ones:");
+  console.log(webPush.generateVAPIDKeys());
+  return;
+}
+
+webPush.setVapidDetails(
+  'https://serviceworke.rs/',
+  process.env.VAPID_PUBLIC_KEY,
+  process.env.VAPID_PRIVATE_KEY
+);
+
+ +

接着,一个模块定义并导出了所有应用需要处理的路由:获取 VAPID 公钥,注册,发送通知等等。你可以看到来自 index.js 的变量在这里被用到:payload, delayttl

+ +
module.exports = function(app, route) {
+  app.get(route + 'vapidPublicKey', function(req, res) {
+    res.send(process.env.VAPID_PUBLIC_KEY);
+  });
+
+  app.post(route + 'register', function(req, res) {
+
+    res.sendStatus(201);
+  });
+
+  app.post(route + 'sendNotification', function(req, res) {
+    const subscription = req.body.subscription;
+    const payload = req.body.payload;
+    const options = {
+      TTL: req.body.ttl
+    };
+
+    setTimeout(function() {
+      webPush.sendNotification(subscription, payload, options)
+      .then(function() {
+        res.sendStatus(201);
+      })
+      .catch(function(error) {
+        console.log(error);
+        res.sendStatus(500);
+      });
+    }, req.body.delay * 1000);
+  });
+};
+ +

service-worker.js

+ +

最后我们要看得文件是service worker。

+ +
self.addEventListener('push', function(event) {
+    const payload = event.data ? event.data.text() : 'no payload';
+    event.waitUntil(
+        self.registration.showNotification('ServiceWorker Cookbook', {
+            body: payload,
+        })
+    );
+});
+ +

它做的所有事就是监听 {{event("push")}} 事件,创建payload 变量,这个变量包含了来自event.data的文本(如果data是空的,则会新建一个‘no payload’字符串),然后一直等到通知推送给用户为止。

+ +

如果你想知道他们具体是怎么处理的,请随意探索Service Worker Cookbook 里面剩下的例子—— Github上面提供了完整的源代码。那里有大量的示例展示了各种用法,包括web推送,缓存策略,性能,离线运行等等 。

+ +

{{PreviousMenuNext("Web/Apps/Progressive/Installable_PWAs", "Web/Apps/Progressive/Loading", "Web/Apps/Progressive")}}

+ +
{{QuickLinksWithSubpages("/en-US/docs/Web/Progressive_web_apps/")}}
diff --git a/files/zh-cn/web/progressive_web_apps/responsive/index.html b/files/zh-cn/web/progressive_web_apps/responsive/index.html new file mode 100644 index 0000000000..f357c6f81b --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/responsive/index.html @@ -0,0 +1,77 @@ +--- +title: Responsive design +slug: Web/Progressive_web_apps/Responsive +tags: + - Media Queries + - PWA + - Progressive web apps + - Responsive web design + - viewport +translation_of: Web/Progressive_web_apps/Responsive/responsive_design_building_blocks +--- +
+
响应式Web应用使用媒体查询和viewport等技术,以确保它们的页面适配任何设备,比如:桌面、移动手机、平板,或者其他新的设备。
+ +
+
+ +

核心指南

+ +
+
The building blocks of responsive design
+
Learn the basics of responsive design, an essential topic for modern app layout.
+
Mobile first
+
Often when creating responsive application layouts, it makes sense to create the mobile layout as the default, and build wider layouts on top.
+
+ +

技术

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TechnologyDescriptionSupport summaryLatest spec
Media queriesDefines expressions allowing styling to be selectively applied to content depending on viewport size, resolution, orientation, etc.Widespread across modern browsers (more detail)Media Queries Level 4
@viewport/viewport meta tagControls viewport settings, primarily on mobile devices.@viewport: Experimental (more detail)
+ Viewport meta tag: Widespread across modern mobile devices
CSS Device Adaptation Module Level 1
FlexboxA very useful CSS feature for creating flexible, responsive layouts.Widespread across modern browsers (more detail)CSS Flexible Box Layout Module Level 1
+ +

工具

+ +
+
Modernizr
+
A feature detection library for applying different CSS and JS to your page depending on whether certain CSS/JS features are supported.
+
css3-mediaqueries-js
+
A JavaScript polyfill to add Media Query support to old versions of IE (5+.)
+
+ +

参见

+ +
+
Graphics for responsive sites
+
Ideas to keep in mind when designing graphics for responsive sites and applications.
+
Responsive navigation patterns
+
How do you make a UI that looks and works as great on a smartphone as it does on the desktop? Learn how to design UIs that change to fit your user's screen.
+
diff --git a/files/zh-cn/web/progressive_web_apps/responsive/responsive_design_building_blocks/index.html b/files/zh-cn/web/progressive_web_apps/responsive/responsive_design_building_blocks/index.html new file mode 100644 index 0000000000..0c644f8724 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/responsive/responsive_design_building_blocks/index.html @@ -0,0 +1,421 @@ +--- +title: 响应式设计的基础 +slug: Web/Progressive_web_apps/Responsive/responsive_design_building_blocks +translation_of: Web/Progressive_web_apps/Responsive/responsive_design_building_blocks +--- +
+

在本文中,我们将讨论响应式设计的主要基本组成部分,并在必要时提供一些指向更多信息的链接。

+
+ +

For Web developers, it is now fairly common to be called upon to create a Web site or app that changes its user interface depending on the browser or device accessing the site to provide an optimized experience. One approach to this is to create different versions of your site/app for different platforms or browsers and serve them appropriately after detecting which browser or platform is looking at your site. But this is increasingly inefficient: browser sniffing is inherently error prone, and maintaining multiple copies of your code can turn out to be a nightmare.

+ +

It is usually much better to create a single version of your code which doesn't care about what browser or platform is accessing the site, but instead uses feature tests to find out what code features the browser supports or what the values of certain browser features are, and then adjusts the code appropriately. This tends to be termed responsive design or adaptive design, two related but different approaches. For a discussion on the differences between the two, read Responsive design versus adaptive design.

+ +

This is much more reliable, more maintainable, and more future proof. You don't get caught in the situation of having to bring out more new site versions as more new browsers and platforms come out, and adjust code as feature support in existing browsers changes.

+ +

There are disadvantages to this approach as well. If the content, layout, and functionality need to change greatly for different devices, it may not be such a good approach. Also, taking an existing site and adding responsiveness to it, to make it mobile/tablet friendly, can be a lot more effort than just creating a separate mobile site or app, especially if it is a sprawling enterprise site. Read more about responsive design advantages and disadvantages.

+ +
+

You can also read our discussion on the basics of responsive design, if you need some more background information and basics.

+
+ +

Fluid grids

+ +

The best place to start is with fluid measurements for our application layout — essentially, this means using a combination of percentages and ems/rems to size your containers and text, not fixed widths such as pixels. This has a lot of advantages in that the layout will adapt to different viewport dimensions. Let's look at an example.

+ +

We've written a simple-but-fun prototype for an application called Snapshot, which takes a video stream from your webcam (using {{domxref("navigator.getUserMedia", "getUserMedia()")}}) then allows you to capture stills from that video stream (using HTML5 {{HTMLElement("canvas")}}), and save them to a gallery. You can then view previously-captured images and delete them. Other articles will discuss the functionality in more detail, but here we're interested in the layout.

+ +
+

Note: You can find the Snapshot app on Github; check out the code and help improve it. You can also see Snapshot running live. Note that getUserMedia() is an experimental technology, which currently only works in Google Chrome and Firefox desktop. More functionality and a clean up of the styling of Snapshot are planned for a future date.

+
+ +

Our desktop layout for Snapshot is three columns, containing the camera viewer, image capture view, and gallery, respectively.

+ +

+ +

The markup is very simple:

+ +
<x-deck selected-index="0">
+  <x-card>
+    …
+  </x-card>
+  <x-card>
+    …
+  </x-card>
+  <x-card>
+    …
+  </x-card>
+</x-deck>
+ +
+

Note: These weird x- elements may be unfamiliar; they are part of Brick, Mozilla's UI element library for mobile web apps. We have used Brick to create the mobile layout for Snapshot, which you will read more about below.

+
+ +

To get these sitting side-by-side we have used the following rules:

+ +
x-card {
+  width: 100%;
+}
+
+x-card:nth-child(1), x-card:nth-child(2) {
+  width: 30%;
+  float: left;
+  padding: 2rem;
+}
+
+x-card:nth-child(3) {
+  width: 40%;
+  float: left;
+  height: 100%;
+  overflow: auto;
+  padding: 2rem;
+}
+ +

So we're giving the first two columns a {{cssxref("width")}} of 30%, and the third a width of 40%, floating the columns all left. This way they end up side-by-side, and their proportions remain the same as the browser window size varies. This is just a simple grid example, but you can apply this principle to more complex grid layouts as required.

+ +

border-box sizing

+ +

The padding does not affect the overall width and height of the containers because we have set the {{cssxref("box-sizing")}} of all elements to border-box:

+ +
*, *:before, *:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+ +

This basically means that {{cssxref("width")}} and {{cssxref("height")}} will now set the dimensions of an element all the way up to and including the border, not just the content. So if you set width: 40%, the box width will always be 40% of its parent, and any {{cssxref("padding")}} and {{cssxref("border")}} widths set on the box will be subtracted from the content width, not added to it. Very useful! Read more about this at * { Box-sizing: Border-box } FTW, by Paul Irish.

+ +

Flexible replaced elements

+ +

Things are working fairly well now, but there are still some issues just waiting to present themselves. For a start, let's have a look at what happens when we include the {{HTMLElement("video")}} and {{HTMLElement("img")}} elements inside our first two columns, naked and unstyled.

+ +

+ +

Because the size of replaced elements is dictated by the size of the media inserted into them, and the media is a fixed size, they explode out of their containing elements and make a mess of the layout. This is pretty horrible, but generally this kind of problem is easily fixed with some simple CSS:

+ +
img, video {
+  max-width: 100%;
+}
+ +

This tells the replaced elements to remain constrained inside their container's widths, no matter what. However, if they aren't as wide as their containers, they will not stretch to fill them. In the snapshot example, we ended up with slightly different code:

+ +
x-card:nth-child(1) video, x-card:nth-child(2) img {
+  width: 100%;
+    …
+}
+ +

This is because in our case, we do in fact want the video and image to stretch to always fill their containers no matter what — a subtle but important difference from {{cssxref("max-width")}} — and therefore always be the same size. The video always resizes dynamically, but the screen captures taken from it do not, so upon resizing the screen it was possible to end up with a messy layout with different sized elements when using max-width: 100%, such as:

+ +

+ +

Media queries

+ +

Fluid grids are a great start, but you'll notice that at certain points (known as breakpoints) the layout starts to break down. At these points you'll want to change the layout to rectify the layout problem, and this can be done using media queries.

+ +
+

Note: Media queries are a CSS3 feature that allow you to selectively apply CSS depending on the results of media feature tests — for more on the basics, read Media queries.

+
+ +

Typical desktop layout

+ +

In our example, we have a desktop layout, as we've already seen. This is created using the CSS rules included at the top of the stylesheet, before any media queries are encountered.
+
+

+ +

Mid-width layout

+ +

We also have a mid-width layout, which is aimed at working well on tablets and narrow laptop screens. This is created by all of the CSS inside the first media query:

+ +
@media all and (max-width: 1024px) {
+  x-card:nth-child(1), x-card:nth-child(2) {
+    width: 50%;
+  }
+
+  x-card:nth-child(3) {
+    width: 100%;
+    clear: left;
+  }
+
+  x-card:nth-child(3) img {
+    width: 20%;
+  }
+}
+ +

So here we're altering the widths of the columns and removing the float of the third column (and adding clearing to guard against any float funny business). We've also altered the width of the images inside the third container (no longer a column — this is the gallery) so that now you get five per line (it was previously three per line).

+ +

+ +

Narrow screen/mobile layout

+ +

We then have a narrow screen layout, designed to fit the bill for a mobile app/open Web app experience. This is created in multiple parts. First of all, as expected, there is a media query in our main CSS, which is quite weighty, so we'll go through it in parts:

+ +
@media all and (max-width: 480px) {
+  x-card:nth-child(1), x-card:nth-child(2), x-card:nth-child(3) {
+    width: 100%;
+    float: none;
+    padding: 0;
+  }
+
+  button {
+    margin-top: 0;
+    border-radius: 0;
+  }
+
+  x-card:nth-child(1) video, x-card:nth-child(2) img {
+    border-radius: 0px;
+    border: none;
+    padding: 0;
+    background-color: 0;
+  }
+ +

This first block resets a number of different things from the widescreen layouts that were't required for the mobile app.

+ +
  x-card:nth-child(1) video, x-card:nth-child(2) img, x-card:nth-child(3) {
+    margin-top: 17.5vw;
+  }
+
+  x-card:nth-child(1) button, x-card:nth-child(2) button {
+    position: absolute;
+    bottom: 0;
+  }
+
+  x-card:nth-child(2) button:nth-of-type(2) {
+    bottom: 5.9rem;
+  }
+
+  x-card:nth-child(1) button {
+    font-size: 7vw;
+  }
+
+  x-card:nth-child(2) button {
+    font-size: 7vw;
+  }
+ +

The next rules do some sizing on the buttons inside the first two cards, and give all card contents a top margin so that their content won't be lost under the navigation buttons (see below). This was necessary because Mozilla Brick (also see below) forces its components to be 100% of the screen width and height. We have used vw (viewport width) units for these — 1vw is equivalent to  1% of the viewport width. This makes the dimensions scale up and down nicely along with the viewport width. Last for this section, we absolutely positioned all buttons at the bottom of the cards they are in, so the layout looks OK at different viewport size variations. We then add a rule that positions the second button in any card a button's width higher up the card. When you click on an image in the gallery it brings up options to delete or cancel deletion of the card, and you don't want two buttons on top of one another.

+ +
x-card:nth-child(3) img {
+  width: 50%;
+}
+ +

This rule simply changes the width of the gallery images so now there are two per line.

+ +
  nav {
+    width: 100%;
+    position: absolute;
+    z-index: 1000;
+
+    display: -webkit-flex;
+    display: -moz-flex;
+    display: -ms-flexbox;
+    display: flex;
+  }
+
+  nav button {
+    font-size: 6.8vw;
+
+    -webkit-flex: 1;
+    -moz-flex: 1;
+    -ms-flex: 1;
+    flex: 1;
+
+    border-left: 1px solid rgba(100,100,100,0.4);
+  }
+
+  nav button:first-child {
+    border-left: 0;
+  }
+}
+ +

In this last set of rules, we change the display value of the {{HTMLElement("nav")}} to flex to make it show (it was set to none in the default CSS at the top of the stylesheet, as it wasn't needed for the other views.) We then use absolute positioning and {{cssxref("z-index")}} to make it take up no space in the document flow,  and sit on top of the x-cards (this is why we gave the x-cards that top-margin earlier).

+ +

Next up, the font-size of the buttons is set to 6.8vw. Why? Because the top-margin of the x-cards was set to 17vw earlier on. All buttons in the app have been set to have a line-height of 2.5, in the default CSS at the top of the stylesheet (check if you don't believe me.) And 6.8 x 2.5 = 17.

+ +

Last, we have used flex: 1; to make the buttons always take up the same proportion of space on the line. Let's have a look at the mobile layout, in the below image.

+ +

single column layout for mobile app view, with three buttons to navigate between cards, an image viewer, and a Save Picture button at the button.But there are more tricks up our sleeves for this mobile app layout! As mentioned above, we used Mozilla Brick, a collection of ready-rolled mobile UI components, in the making of the mobile app layout. In particular, we used the deck component for the nice transition effect between cards when the buttons are pressed. For more on using Brick, read Mozilla Brick: ready made UI components.

+ +

What's more relevant to this article is that we didn't want the Brick CSS and JavaScript files being applied to the markup unless we were looking at the mobile app view. To achieve this, we applied the Brick CSS to the page using a separate {{HTMLElement("link")}} element with a media attribute:

+ +
<link href="dist/brick.css" type="text/css" rel="stylesheet" media="all and (max-width: 480px)">
+ +

This says that the whole stylesheet will not be linked to the HTML unless the viewport width is 480px or less. Moving on to the JavaScript, {{HTMLElement("script")}} elements don't accept media attributes, so I had to do this a different way. Fortunately there is a JavaScript construct called {{domxref("window.matchMedia()")}}, which can conditionally run JavaScript constructs depending on whether a media query returns true or not. We opened up the brick.js file and wrapped the whole lot in the following:

+ +
if (window.matchMedia("(max-width: 480px)").matches) {
+  // The whole of brick.js goes here!
+}
+ +

This causes nothing inside the brick.js file to be run unless the viewport width is 480px or less. Problem solved.

+ +

Really wide screens

+ +

One thing you might notice is that when the viewport gets very wide (such as on a cinema display), the layout stops getting wider, and just centers in the space available. This is pretty simple to achieve. You could use a min-width media query to fix the {{HTMLElement("body")}} width at a certain point:

+ +
@media all and (min-width: 1400px) {
+  body {
+    width: 1400px;
+    margin: 0 auto;
+  }
+}
+ +

But it's actually easier to just set the following rule instead, and get rid of the media query altogether:

+ +
body {
+  max-width: 1400px;
+  margin: 0 auto;
+}
+ +

Orientation fail

+ +

We also came across some problems with orientation: the mobile-app layout of our example app is designed for portrait orientation, and looks terrible when viewed on a device in landscape orientation. To fix this, we added in a media query that only applies its contents to the markup when device is viewed in landscape orientation:

+ +
@media all and (max-width: 480px) and (orientation: landscape) {
+  nav {
+    width: auto;
+
+    -webkit-flex-direction: column;
+    -moz-flex-direction: column;
+    -ms-flex-direction: column;
+    flex-direction: column;
+  }
+
+  nav button {
+    font-size: 6.8vh;
+  }
+
+  nav button {
+    border-left: 0;
+  }
+
+  x-card:nth-child(1) video, x-card:nth-child(2) img, x-card:nth-child(3) {
+    margin-top: 0;
+  }
+
+  x-card:nth-child(1) button, x-card:nth-child(2) button {
+    font-size: 2rem;
+  }
+}
+ +

This does the following:

+ + + +

This results in the following layout:

+ +

+ +
+

Note: Another solution with respect to orientation might be to just lock the orientation of your app, to portrait or landscape. If you are working on an installed app, you can easily do this with the orientation manifest field. If you want a solution that works across general web apps, you could use the Screen orientation API, and/or provide a message asking the user to rotate their screen if they are using the wrong orientation (for example, if window.innerWidth is larger than window.innerHeight, assume the
+ game is landscape mode and show a "please rotate" message.)

+
+ +

Viewport

+ +

One last problem to mention for our example app is concerned with mobile browsers and media queries. If we viewed my example in a mobile browser in its current state, we wouldn't see our nice mobile layout. Instead, we'd see the below image.

+ +

I'm sure you'll agree that this really isn't what we wanted — why is this happening? In short, mobile browsers lie. They don't render web pages at their true viewport width. Instead, they render pages at a higher assumed viewport width (something approaching a laptop screen), and then shrink the result down to fit inside the mobile screen. This is a sensible defensive mechanism — most old school sites that don't have media queries would look terrible when rendered at say, 320px or 480px wide. But this doesn't help us responsible web developers, who have written small screen layouts into our CSS using media queries and want mobile devices to display those!

+ +

There is a way to override this mobile rendering behavior — viewport, which is inserted into our HTML pages in the form of a {{HTMLElement("meta")}} tag. In my example, let's add the following into our HTML {{HTMLElement("head")}}:

+ +
<meta name="viewport" content="width=480">
+ +

This causes our browser to render our mobile app layout properly — width=480 tells the browser "render this markup at 480 pixels wide", hence the media queries kick in appropriately. There are many more options available in the viewport meta tag, which you can read about in Using the viewport meta tag to control layout on mobile browsers.

+ +
+

Note: There is a spec called device adaptation, which defines the same functionality but in CSS, using a @viewport at-rule. This is probably a more logical place to put such information, but the spec is not as well supported as the viewport meta tag, therefore you should stick with that for now.

+
+ +

Responsive images/video

+ +

Another problem that comes up more and more these days is making image/video weight (size in KB) responsive as well as the dimensions of the image on screen. Yes, you want the images to be contained inside the app UI whether you are using it on desktop or mobile, but you should also consider that mobile apps have much smaller viewport dimensions available than desktop apps, so you should try to give mobile devices a smaller image to download. Mobiles in general (more commonly in some parts of the world than others) are on lower bandwidth connections and have less memory available than desktop devices, so yes, those extra kilobytes really do count.

+ +

Another challenge is dealing with high resolution screens — raster graphics designed for low resolutions are in danger of appearing tiny when displayed on a high resolution screen, so devices often apply a default zoom factor to rendered pages to avoid this. The trouble with this, then, is that raster images are zoomed in and as a result can start to look pixellated.

+ +

CSS background images

+ +

For CSS background images this is a fairly easy problem to solve. If you use the mobile first methodology, you will be creating your mobile layout inside your default CSS, before any media queries have been applied. The media queries then supply CSS that is only applied to the markup when the viewport is above a certain width. Let's look at a quick example:

+ +
header {
+  height: 300px;
+  width: 100%;
+  background: url(images/small-header.jpg) center;
+}
+
+@media all and (min-width: 480px) {
+  header {
+    background: url(images/large-header.jpg) center;
+  }
+}
+ +

This means that mobile browsers only download the mobile background image asset — not the desktop mobile assets — because they fail the media query tests and therefore ignore the media queries. You can also serve a larger graphic to a higher resolution device using a resolution media query, like so:

+ +
button {
+  background: url(images/low-res-header.jpg) 1rem center ;
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 2),
+       only screen and ( min-resolution: 192dpi),
+       only screen and ( min-resolution: 2dppx) {
+  button {
+    background: url(images/high-res-header.jpg) 1rem center ;
+  } 
+}
+ +

This looks rather complicated, but really it's not — we are providing a number of media query options, as at this time different browsers support different resolution media query types and even units. Brett Jankord has a good explanation at Cross Browser Retina/High Resolution Media Queries.

+ +

<video>

+ +

HTML5 video is fairly well catered for in terms of responsive capabilities. If you wish, you can point to multiple video files via {{HTMLElement("source")}} attributes, each with their own source and MIME type:

+ +
<video controls>
+  <source src="videos/720/crystal720.mp4" type="video/mp4">
+  <source src="videos/720/crystal720.webm" type="video/webm">
+</video>
+ +

But you can go one step further. You can include media attributes on the <source> element containing media queries — the video loaded in the browser will depend on both the format the browser supports, and the results of the media tests. So for example:

+ +
<video controls>
+  <source src="videos/320/crystal320.mp4" type="video/mp4" media="all and (max-width: 480px)">
+  <source src="videos/320/crystal320.webm" type="video/webm" media="all and (max-width: 480px)">
+  <source src="videos/720/crystal720.mp4" type="video/mp4" media="all and (min-width: 481px)">
+  <source src="videos/720/crystal720.webm" type="video/webm" media="all and (min-width: 481px)">
+</video>
+ +

This allows your site to serve different video files based on the available space, in order to optimize the user's experience.

+ +

<img>

+ +

HTML images are a more difficult proposition. There is no mechanism inherent in HTML images for serving different image files dependent on viewport size, and, due to a number of irksome browser behavior realities, solutions are more difficult to hack together than you would imagine. There are currently some standards proposals in the works that would provide this — the W3C responsive images community group discussed this problem for ages and arrived at the <picture> element, which provides a similar markup structure to {{HTMLElement("video")}}, with {{HTMLElement("source")}} alternatives selectable via media query results. Another proposal, srcset, was put forward by Apple and takes a slightly different approach, instead providing a new srcset attribute for {{HTMLElement("img")}} inside which image references are placed along with "hints" that the browser can use to work out which image is most suitable to display given its viewport size, resolution, etc. These are not intended to be mutually exclusive.

+ +

This all sounds good. But those solutions are definitely not ready for production yet — both are in a very early stage of standardization, and have no support across browsers. Currently we have to rely on various polyfills and other solutions, none of which are perfect for all situations, so you need to decide which one is right for your particular situation. Some available solutions are as follows:

+ +
+
HiSRC
+
A jQuery plugin that allows you to create small, medium, and large versions of an image, and then serves the appropriate one according to the browser's resolution and available network speed.
+
Mobify.js capturing
+
A very clever technique from Mozilla that allows you to capture the source of the page before it's parsed. This way, you can swap out image src values with JavaScript depending on browser features, circumventing browser preloading issues. This is promising, but doesn't work very well across older browsers.
+
Picturefill
+
A JavaScript-based polyfill for <picture>, which works nicely, but it does require a lot of custom markup.
+
Adaptive images
+
A server-side solution, which records the viewport size in a cookie, then resizes images via a combination of PHP and .htaccess to a more appropriate size, if appropriate. This doesn't require markup or scripting, but has a number of limitations.
+
+ +

SVG and other vector graphics

+ +

For some image requirements (not photographs, but icons and user interface elements are a good fit), a good solution is to use vector graphics. Because vector images are calculated based on mathematical algorithms rather than containing separate data on every pixel in the image, they tend to be smaller in file size, and are infinitely scalable when zoomed or viewed on high resolution devices (at least, in theory). Some ideas follow, which also help to keep the number of HTTP requests down — another key factor in mobile app performance:

+ + + +

See also

+ + diff --git "a/files/zh-cn/web/progressive_web_apps/\344\274\230\345\212\277/index.html" "b/files/zh-cn/web/progressive_web_apps/\344\274\230\345\212\277/index.html" new file mode 100644 index 0000000000..815ccbea1b --- /dev/null +++ "b/files/zh-cn/web/progressive_web_apps/\344\274\230\345\212\277/index.html" @@ -0,0 +1,57 @@ +--- +title: 渐进式webApp优势 +slug: Web/Progressive_web_apps/优势 +translation_of: Web/Progressive_web_apps/Introduction#Advantages_of_web_applications +--- +

以下是渐进式webApp所有的优势清单

+ +

Discoverable

+ +

The eventual aim is that web apps should have better representation in search engines, be easier to expose, catalog and rank, and have metadata usable by browsers to give them special capabilities.

+ +

Some of the capabilities have already been enabled on certain web-based platforms by proprietary technologies like Open Graph, which provides a format for specifying similar metadata in the HTML <head> using meta tags.

+ +

The relevant web standard here is the Web app manifest, which defines features of an app such as name, icon, splash screen, and theme colors in a JSON-formatted manifest file. This is for use in contexts such as app listings and device home screens.

+ + + +

Installable

+ +

A core part of the apps experience is for users to have app icons on their home screen, and be able to tap to open apps into their own native container that feels nicely integrated with the underlying platform.

+ +

Modern web apps can have this native app feel via properties set inside the Web app manifest, and via a feature available in modern smartphone browsers called Add to home screen.

+ +

Linkable

+ +

One of the most powerful features of the Web is to be able to link to an app at a specific URL — no app store needed, no complex installation process. This is how it has always been.

+ +

Network independent

+ +

Modern web apps can work when the network is unreliable, or even non-existent. The basic ideas behind network independence are to be able to:

+ + + +

This is achieved by a combination of technologies — Service Workers to control page requests (for example storing them offline), the Cache API for storing responses to network requests offline (very useful for storing site assets), and client-side data storage technologies such as Web Storage and IndexedDB to store application data offline.

+ +

Progressive

+ +

Modern web apps can be developed to provide a super cool experience to fully capable browsers, and an acceptable (although not quite as shiny) experience to less capable browsers. We've been doing this for years with best practices such as progressive enhancement.

+ +

Re-engageable

+ +

One major advantage of native platforms is the ease with which users can be re-engaged by updates and new content, even when they aren't looking at the app or using their devices. Modern web apps can now do this too, using new technologies such as Service Workers for controlling pages, the Web Push API for sending updates straight from server to app via a service worker, and the Notifications API for generating system notifications to help engage users when they're not in the browser.

+ +

Responsive

+ +

Responsive web apps use technologies like media queries and viewport to make sure that their UIs will fit any form factor: desktop, mobile, tablet, or whatever comes next.

+ +

Safe

+ +

The web platform provides a secure delivery mechanism that prevents snooping and ensures content hasn’t been tampered with — as long as you take advantage of HTTPS and develop your apps with security in mind. In addition, you can verify the true nature of a PWA by confirming that it is at the correct URL, whereas apps in apps stores can often look like one thing, but be another (example).

+ +

 

diff --git "a/files/zh-cn/web/progressive_web_apps/\345\212\240\350\275\275/index.html" "b/files/zh-cn/web/progressive_web_apps/\345\212\240\350\275\275/index.html" new file mode 100644 index 0000000000..7f45a3c278 --- /dev/null +++ "b/files/zh-cn/web/progressive_web_apps/\345\212\240\350\275\275/index.html" @@ -0,0 +1,152 @@ +--- +title: 渐进式加载 +slug: Web/Progressive_web_apps/加载 +tags: + - PWA + - 渐进式加载 +translation_of: Web/Progressive_web_apps/Loading +--- +
{{PreviousMenu("Web/Apps/Progressive/Re-engageable_Notifications_Push", "Web/Apps/Progressive")}}
+ +

在前面的文章我们介绍了很多api,例如:Service Workers, Web Manifests, Notifications and Push,它让我们的示例应用 js13kPWA 成为一个渐进式web应用。在这篇文章里我们将会走得更远,我们会使用资源渐进式加载来提高整个应用的性能。

+ +

首次有效渲染

+ +

尽快把有效的信息输送给用户是一件非常重要的事情 —— 等待页面加载的时间越长,用户在页面加载完成之前离开的概率就越大。为了达到这个目的,网页加载完成前,我们应该用占位符在最终资源将会加载的地方展示最起码的视图骨架。

+ +

这个功能可以用渐进式加载来实现 —— 它也被称为 懒加载。它的做法是延迟加载尽可能多的资源(HTML, CSS, JavaScript),只有在用户第一次使用到它的时候,它才会被立刻加载。

+ +

打包还是拆分

+ +

大部分的用户不会用到一个网站的所有页面,但我们通常的做法却是把我们所有的功能都打包进一个很大的文件里面。一个 bundle.js 文件的大小可能会有几个M,一个打包后的style.css 会包含一切东西,从CSS结构定义到各个版本的网站的样式:移动端,平板,桌面,打印等等。

+ +

通常来说只加载一个打包后大的文件会比加载很多个小文件要快一些,但如果用户并不是一开始就需要所有的资源,我们就可以首先加载那些关键的资源,其他的等到我们需要的时候,我们再去加载它。

+ +

导致页面阻塞的资源(Render-blocking resources)

+ +

将所有文件打包是一种不好的做法,因为浏览器把计算结果渲染到屏幕之前,需要先把HTML,CSS和JavaScript下载下来。在页面被打开到页面加载完成的这段时间里,用户将会看到一个空白的页面,这无疑是一个非常糟糕的体验。

+ +

为了解决这个问题,举个例子,我们可以在script标签上面加上一个 defer

+ +
<script src="app.js" defer></script>
+
+ +

他们会等到文档解析完成之后再开始下载和执行,所以他们不会阻塞HTML页面的渲染。我们还可以拆分css文件并给它们加上media属性:

+ +
<link rel="stylesheet" href="style.css">
+<link rel="stylesheet" href="print.css" media="print">
+
+ +

 这种做法告诉浏览器只有在条件满足的情况下才加载这些资源(例如指定了print,则在打印环境下才会加载这些资源,译者注)。

+ +

在我们这个js13kPWA应用里面,由于CSS非常的简单,因此所有样式都被放到一个文件里面,并没有具体的规则来指导它如何加载css。但我们可以做得更好,例如把 style.css 里面的所有内容移动到一个 <style> 标签里面,并把它放到 index.html  的 <head> 的里面。这样做可以进一步提高应用的性能,但为了使代码更具可读性,我们并没有选择这么做。

+ +

图片

+ +

除了JavaScript和CSS,网站通常还会包含大量的图片。当你把{{htmlelement("img")}}元素添加到网站里面时,对应的所有图片资源都会在页面初始化时被下载下来。在网站就绪之前下载几个兆的图片资源是一种比较常见的状况,但它会再次给人一种性能不好的印象。我们并不需要在一打开网站的时候就以最高的画质呈现所有的图片。

+ +

这是可以优化的。首先,你可以使用类似TinyPNG这类工具或者服务,它可以在不过分降低画质的情况下压缩文件的大小。如果你已经做到了这一点,那你可以考虑一下如何通过JavaScript来优化图片的下载了,我们将会在下面的篇幅提到这些内容。

+ +

图片占位符

+ +

我们可以通过使用JavaScript有选择的加载图片,而不是把所有的游戏截图都放到 <img> 标签的 src 属性里面,因为那样浏览器会自动下载所有的图片。 js13kPWA示例在图片最终加载之前会将图片的最终路径存放到 data-src 中,在这个阶段,应用会使用图片占位符来代替真正的图片,它体积更小更轻量级(加载也更快,译者注)。

+ +
<img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'>
+
+ +

这些图片会在网站构建完HTML主体框架之后通过JavaScript进行加载。图片占位符被缩放到和真正的图片一样大小,所以它会占据同样的空间,并且在真正的图片完成加载后不会导致页面重绘。

+ +

通过JavaScript进行加载

+ +

app.js 这个文件处理 data-src 属性的过程如下所示:

+ +
let imagesToLoad = document.querySelectorAll('img[data-src]');
+const loadImages = (image) => {
+  image.setAttribute('src', image.getAttribute('data-src'));
+  image.onload = () => {
+    image.removeAttribute('data-src');
+  };
+};
+ +

当函数 loadImages 把地址从  data-src 移动到 src 上时,  imagesToLoad 变量包含了所有图片的链接。当每个图片都已经加载完成时,我们会把data-src 属性移除掉,因为这个时候已经没有任何用处了。我们对遍历了所有的图片来加载这些图片:

+ +
imagesToLoad.forEach((img) => {
+  loadImages(img);
+});
+ +

用CSS制造模糊

+ +

为了让整个过程在视觉上更加吸引人,图片占位符的样式用CSS做了模糊处理.

+ +

Screenshot of placeholder images in the js13kPWA app.

+ +

我们在开始时用模糊来渲染图像,因此可以实现一个从模糊到清晰图像的过渡效果:

+ +
article img[data-src] {
+  filter: blur(0.2em);
+}
+
+article img {
+  filter: blur(0em);
+  transition: filter 0.5s;
+}
+ +

这个样式会在半秒钟内移除模糊效果,它会让“加载”效果看起来更好看:

+ +

按需加载

+ +

上面讨论的图片加载机制工作得还不错——它在HTML文档加载完成之后再开始加载图片,并且在加载过程中还提供了一个很漂亮的过度效果。问题是它仍然一次性加载了所有的图片,即使网站加载完成后用户有可能只看前两张或者三张图片。

+ +

这个问题可以用新的 Intersection Observer API 来解决 —— 通过这个api我们可以确保只有当图片出现在可见区域时,它才会被加载。

+ +

Intersection Observer

+ +

这是对上面那个可以正常工作的例子提供的一个渐进增强功能 —— Intersection Observer 只有在用户向下滚动页面并且显示在屏幕上时,才会开始加载目标图片。

+ +

这里有相关的代码展示:

+ +
if('IntersectionObserver' in window) {
+  const observer = new IntersectionObserver((items, observer) => {
+    items.forEach((item) => {
+      if(item.isIntersecting) {
+        loadImages(item.target);
+        observer.unobserve(item.target);
+      }
+    });
+  });
+  imagesToLoad.forEach((img) => {
+    observer.observe(img);
+  });
+} else {
+  imagesToLoad.forEach((img) => {
+    loadImages(img);
+  });
+}
+ +

如果 {{domxref("IntersectionObserver")}}对象被浏览器所支持,app会对它新建一个实例。当一个或多个item(指的是监听的对象,例如一个img,译者注)跟观察者发生交互时(图片出现在视图中时),作为参数传递的函数可以用来处理一些回调事务(例如图片加载,译者注)。我们可以对每一个项目做一个迭代并对它们做出相应的处理——当图片可见时,我们开始加载真正的图片并且停止监听这张图片,因为图片加载完成之后,我们已经没有很必要再知道它的状态了。

+ +

让我们来重申一下我们之前提到的渐进增强 —— 代码这么写的好处在于,不管 Intersection Observer是否被当前浏览器支持,app都能够正常的工作。如果 Intersection Observer不被支持,我们会用上面提到的基础方法来实现图片的加载。

+ +

一些改进

+ +

记住,有很多的方法可以用来优化我们的加载时间,这个示例只探讨了其中的一种方法。你可以尝试让你的app变的更加健壮,通过让它在没有JavaScript的情况下也能工作 —— 通过使用 {{htmlelement("noscript")}} 标签来展示已经分配了最终 src 路径的图片,或者在 <img> 外面套上一个 {{htmlelement("a")}} 标签并指向对应的图片资源,当用户想要想要看的时候他们可以点击图片并访问他们。

+ +

我们并没有这么做因为这个app本身依赖于JavaScript —— 没有JavaScript游戏列表就无法加载,Service Worker相关的代码也将无法执行。

+ +

我们可以重写整个加载过程,让它不止加载图片,而是加载整个列表项,包括详细介绍和链接。工作起来它像一个无限滚动的页面——当用户往下滚动的时候开始加载新的项目。通过这个方法HTML页面体积会达到最小,加载时间可以更短,我们也可以从中获取到更大的性能优势。

+ +

结论

+ +

初始化时加载更少的文件,分拆成更小的模块,使用占位符以及按需加载更多的内容 —— 这会让我们获得更短的首次加载时间,它既能让app开发者受益,也能给用户提供更加丝滑的体验。

+ +

记住关于渐进增强的内容 —— 不管在任何硬件或平台都提供一个可用的产品,但在现代浏览器上面确保能提供更好的用户体验。

+ +

最后的思考

+ +

这就是这个系列的所有内容了 —— 我们通过  js13kPWA 示例应用的源码 学习了渐进式web 应用的的用法,包括了PWA介绍, PWA 结构, 通过Service Workers让PWA离线工作, 让PWA易于安装,以及最后的通知功能。在Service Worker Cookbook的帮助下我们还解释了推送的原理。而在本篇文章中,我们探讨了渐进式加载的概念,包括一个使用了 Intersection Observer API的有趣例子。

+ +

请随意试验这些代码,通过使用PWA的特性来让你现有的应用更加健壮,或者自己创建一些全新的东西。相对于常规的web应用,PWA存在巨大的优势。

+ +

{{PreviousMenu("Web/Apps/Progressive/Re-engageable_Notifications_Push", "Web/Apps/Progressive")}}

+ +

{{QuickLinksWithSubpages("/en-US/docs/Web/Progressive_web_apps/")}}

diff --git "a/files/zh-cn/web/progressive_web_apps/\346\267\273\345\212\240\345\210\260\344\270\273\345\261\217\345\271\225/index.html" "b/files/zh-cn/web/progressive_web_apps/\346\267\273\345\212\240\345\210\260\344\270\273\345\261\217\345\271\225/index.html" new file mode 100644 index 0000000000..a0915ea9d2 --- /dev/null +++ "b/files/zh-cn/web/progressive_web_apps/\346\267\273\345\212\240\345\210\260\344\270\273\345\261\217\345\271\225/index.html" @@ -0,0 +1,218 @@ +--- +title: 添加到主屏幕 +slug: Web/Progressive_web_apps/添加到主屏幕 +tags: + - PWA + - 图标 + - 服务进程 + - 添加到主屏幕 + - 清单 + - 渐进式Web应用 +translation_of: Web/Progressive_web_apps/Add_to_home_screen +--- +

添加到主屏幕(A2HS)添加到主屏幕(简称A2HS)是现代智能手机浏览器中的一项功能,使开发人员可以轻松便捷地将自己喜欢的Web应用程序(或网站)的快捷方式添加到主屏幕中,以便他们随后可以通过单点访问它。本指南说明了A2HS的使用方式,以及作为开发人员要使您的用户利用A2HS所需做的事情。

+ +

为什么选择A2HS?

+ +

A2HS被认为是渐进式Web应用程序哲学的一部分—为Web应用程序提供与原生应用程序相同的用户体验优势,因此它们可以在当今的生态系统战争中竞争。这部分是通过访问主屏幕上的应用程序图标来访问应用程序,然后将其整齐地显示在自己的窗口中的简单手势。A2HS使这成为可能。

+ +

哪些浏览器支持A2HS?

+ +

Mobile Chrome / Android Webview 从31版开始支持A2HS,Opera for Android从32版开始支持,Firefox for Android从58版开始支持。

+ +

如何使用?

+ +

我们已经编写了一个非常简单的示例网站(观看我们的在线演示,并查看源代码),该网站虽然功能不多,但是开发时使用了必要的代码,也可以将其添加到主屏幕中,并且service worker使其可以脱机使用。这个示例展示了一系列的狐狸图片。

+ +

如果您有适用于Android的Firefox,使用它导航到我们的示例:https://mdn.github.io/pwa-examples/a2hs/。你将会看到狐狸图片,但更重要的是,你将会看到一个带有加号(+)的“主页”图标—这是为具有必要功能的任何站点显示的“添加到主屏幕”图标。

+ +

+ +

点击此按钮将显示一个确认横幅—按下大“+ 添加到主屏幕”按钮即可完成操作,将应用添加到主屏幕。(注意:在Android 8及更高版本中,将首先显示系统级的“添加到主屏幕”权限对话框。)

+ +

+ +

如果您可以使用Mobile Chrome,则体验会略有不同;加载我们的网站后,您会看到一个弹出安装横幅,询问您是否要将此应用添加到主屏幕。

+ +

+ +
+

注意:您可以在“Web App安装横幅”一文中找到有关Chrome安装横幅的更多信息。

+
+ +

如果您选择不将其添加到主屏幕,则可以稍后使用Chrome主菜单中的添加到主屏幕图标添加。

+ +

无论使用哪种浏览器,当您选择将应用程序添加到主屏幕时,您都会看到它与短标题一起出现,就像原生应用程序一样。

+ +

+ +

点按此图标可以将其打开,但是作为全屏应用程序,您将不再看到其周围的浏览器用户界面。

+ +

如何使应用程序支持A2HS?

+ +

要将您的应用添加到主屏幕,它需要满足以下条件:

+ + + +

Manifest

+ +

web manifest 以标准JSON格式编写,应放置在应用程序目录中的某个位置(最好是在根目录中),名称为somefilename.webmanifest(我们选择 manifest.webmanifest)。它包含多个字段,这些字段定义有关Web应用程序及其行为的某些信息。

+ +
+

注意:.webmanifest扩展名是在规范的“媒体类型注册”部分中指定的,但通常浏览器将支持带有其他适当扩展名的清单,例如:.json。

+
+ +

A2HS所需的字段如下:

+ + + +

我们的示例应用程序的manifest如下所示:

+ +
{
+  "background_color": "purple",
+  "description": "Shows random fox pictures. Hey, at least it isn't cats.",
+  "display": "fullscreen",
+  "icons": [
+    {
+      "src": "icon/fox-icon.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "name": "Awesome fox pictures",
+  "short_name": "Foxes",
+  "start_url": "/pwa-examples/a2hs/index.html"
+}
+ +

合适的图标

+ +

如以上manifest所示,我们包括一个192 x 192像素的图标,供我们的应用使用。您可以根据需要添加更多尺寸;Android将为每个不同的用例选择最合适的尺寸。您还可以决定添加不同类型的图标,以便设备可以使用他们能够使用的最佳图标(例如,Chrome已经支持WebP格式).

+ +

请注意,每个图标对象中的 type 成员都指定了该图标的mimetype,因此浏览器可以快速读取该图标的类型,然后将其忽略,并在不支持该图标时采用其他图标。

+ +

在设计图标方面,您应该遵循与任何Android图标相同的最佳做法(请参阅Android图标设计指南)。

+ +

将HTML链接到manifest

+ +

要完成manifest的设置,您需要从应用程序主页的 HTML 中引用它:

+ +
<link rel="manifest" href="manifest.webmanifest">
+ +

一旦支持 manifest,支持 A2HS 的浏览器就会知道在哪里查找它。

+ +

A2HS没有给你什么?

+ +

请记住,将应用程序添加到主屏幕时,它只会使该应用程序易于访问—不会将应用程序的资产和数据下载到您的设备上,也不会使该应用程序脱机使用或类似的操作。 为了使你的应用离线运行,你必须使用Service Worker API来离线存储资源,如果需要,还可以使用 Web storage 或 IndexedDB 来存储其数据。

+ +

在示例应用程序中,我们仅使用了一个service worker来存储所有必需的文件。service worker使用index.js 文件中的最终的代码块在网站上注册。然后,我们使用 Cache API 缓存网站的所有资产,并使用 sw.js 文件中的代码从缓存而不是网络中为它们提供服务。

+ +

桌面上的A2HS

+ +

虽然最初旨在改善移动操作系统上的用户体验,但人们也提出了使PWA也可以安装在桌面平台上的趋势。

+ +
+

注意:在撰写本文时,仅在较新版本的Chrome(在Windows中默认情况下,在macOS上的#enable-desktop-pwas标志开启后)中支持以下功能。

+
+ +

添加安装按钮

+ +

为了使PWA可在桌面上安装,我们首先在文档中添加了一个按钮,以允许用户进行安装—桌面应用程序不会自动提供此按钮,并且需要通过用户手势来触发安装:

+ +
<button class="add-button">Add to home screen</button>
+ +

然后,我们给它一些简单的样式:

+ +
.add-button {
+  position: absolute;
+  top: 1px;
+  left: 1px;
+}
+ +

用于处理安装的JavaScript

+ +

index.js文件的底部,我们添加了一些JavaScript来处理安装。首先,我们声明一个deferredPrompt变量(我们将在后面解释),获得对安装按钮的引用,并初始设置为display: none

+ +
let deferredPrompt;
+const addBtn = document.querySelector('.add-button');
+addBtn.style.display = 'none';
+ +

我们最初隐藏该按钮是因为PWA必须遵循A2HS标准才能安装。发生这种情况时,支持的浏览器将触发beforeinstallprompt事件。 然后,我们可以使用以下处理程序来处理安装:

+ +
window.addEventListener('beforeinstallprompt', (e) => {
+  // Prevent Chrome 67 and earlier from automatically showing the prompt
+  e.preventDefault();
+  // Stash the event so it can be triggered later.
+  deferredPrompt = e;
+  // Update UI to notify the user they can add to home screen
+  addBtn.style.display = 'block';
+
+  addBtn.addEventListener('click', (e) => {
+    // hide our user interface that shows our A2HS button
+    addBtn.style.display = 'none';
+    // Show the prompt
+    deferredPrompt.prompt();
+    // Wait for the user to respond to the prompt
+    deferredPrompt.userChoice.then((choiceResult) => {
+        if (choiceResult.outcome === 'accepted') {
+          console.log('User accepted the A2HS prompt');
+        } else {
+          console.log('User dismissed the A2HS prompt');
+        }
+        deferredPrompt = null;
+      });
+  });
+});
+ +

所以我们在这里:

+ + + +

点击处理程序包含以下步骤:

+ + + +

So when the button is clicked, the install prompt appears.

+ +

+ +

如果用户选择安装,则将安装该应用程序(可作为独立的桌面应用程序使用),并且不再显示“安装”按钮(如果已经安装了该应用程序,则将不再触发onbeforeinstallprompt事件)。当您打开应用程序时,它将显示在其自己的窗口中:

+ +

+ +

如果用户选择“取消”,则应用程序的状态将返回到单击按钮之前的状态。

+ +
+

注意:本部分的代码主要来自Pete LaPage的应用安装横幅/添加到主屏幕

+
+ +

其他

+ + + +
{{QuickLinksWithSubpages("/en-US/docs/Web/Progressive_web_apps/")}}
-- cgit v1.2.3-54-g00ecf