webapp用户体验差(不能离线访问),用户粘性低(无法保存入口),pwa就是为了解决这一系列问题(Progressive Web Apps),让webapp具有快速,可靠,安全等特点
将网站添加到桌面、更类似native的体验
<link rel="manifest" href="/manifest.json">
{
"name":"珠峰课堂", // 应用名称
"short_name":"课堂", // 桌面应用的名称 ✓
"display":"standalone", // fullScreen (standalone) minimal-ui browser ✓
"start_url":"", // 打开时的网址 ✓
"icons":[], // 设置桌面图片 icon图标 修改图标需要重新添加到桌面icons:[{src,sizes,type}]
"background_color":"#aaa", // 启动画面颜色
"theme_color":"#aaa" // 状态栏的颜色
}
图标icon
<link rel="apple-touch-icon" href="apple-touch-icon-iphone.png"/>
添加到主屏后的标题 和 short_name一致
<meta name="apple-mobile-web-app-title" content="标题">
隐藏safari地址栏 standalone模式下默认隐藏
<meta name="apple-mobile-web-app-capable" content="yes" />
设置状态栏颜色
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
横幅安装:用户在浏览器中访问至少两次,两次访问间隔至少时间为五分钟(safari不支持横幅)
为了提升用户体验
Service Worker特点:
实现浏览器离线缓存的功能
注册缓存
window.addEventListener('load',function(){
// 页面加载完成后 注册serviceWorker
if('serviceWorker' in navigator){
navigator.serviceWorker.register('./sw.js').then(registeration=>{
console.log(registeration.scope);
});
navigator.serviceWorker.addEventListener('controllerchange',()=>{
console.log('change')
})
}
if(!navigator.onLine){
window.addEventListener('online',()=>{
console.log('更新');
})
}
});
离线缓存 应用cache缓存请求
// self 当前线程中的this
// 拦截用户发送的所有请求
let CACHE_NAME = `cache_version_` + 81;
let CACHAE_LIST = [
'/',
'/index.css',
'/index.html',
'main.js',
'/getImage'
];
// 独立的线程 可以使用fetch 但是不能使用ajax
function fetchAndSave(req){
return fetch(req).then(res=>{ // res 是流
// 做缓存操作
let r = res.clone();
caches.open(CACHE_NAME).then(cache=>cache.put(req,r));
return res;
})
}
self.addEventListener('fetch', e => {
// 用相应来替换 如果获取不到才用缓存
let url = new URL(e.request.url);
if(url.origin !== self.origin){
return;
}
if(e.request.url.includes('/getImage')){ // 调用了接口
// 如果遇到了接口 更新缓存
e.respondWith(
fetchAndSave(e.request).catch(err=>{
// 如果没网 在缓存中 匹配结果 返回请求
return caches.match(e.request);
})
)
return;
}
e.respondWith(
fetch(e.request).catch(err=>{
// 如果没网 在缓存中 匹配结果 返回请求
return caches.match(e.request);
})
)
}); // 用缓存替换
// serviceWorker安装的阶段
function preCache() {
return caches.open(CACHE_NAME).then(cache => {
return cache.addAll(CACHAE_LIST);
})
}
self.addEventListener('install', (e) => {
// 安装的过程中需要缓存
e.waitUntil(
preCache().then(skipWaiting)
)
});
function clearCache() {
return caches.keys().then(keys => {
return Promise.all(keys.map(key => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
}))
})
}
self.addEventListener('activate', (e) => {
e.waitUntil(
Promise.all([
clearCache(),
self.clients.claim() // 立即使serviceWorker生效
])
)
})
https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
vue create pwa-project
npm run build // 才具备pwa效果
vue-cli3.0配置pwa
在public目录下可以更改manifest配置文件
module.exports = {
pwa: {
name: 'My App',
themeColor: '#f2f2f2',
msTileColor: '#aaaaa',
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
workboxPluginMode: 'InjectManifest',
workboxOptions: {
// swSrc is required in InjectManifest mode.
swSrc: 'dev/sw.js',
}
}
}
需要更改registerServiceWorker文件 更改为sw.js
// 设置缓存前缀
workbox.core.setCacheNameDetails({prefix: "pwa-project"});
// 设置预缓存列表
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
// 增加缓存列表策略
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
基于workbox 缓存工具包 https://developers.google.com/web/tools/workbox
增加缓存策略
workbox.routing.registerRoute(
function(obj){
// 包涵api的就缓存下来
return obj.url.href.includes('/user')
},
workbox.strategies.staleWhileRevalidate()
);
配置webpack插件 vue-skeleton-webpack-plugin
单页骨架屏幕
import Vue from 'vue';
import Skeleton from './Skeleton.vue';
export default new Vue({
components: {
Skeleton:Skeleton
},
template: `
<Skeleton></Skeleton>
`
});
plugins: [
new SkeletonWebpackPlugin({
webpackConfig: {
entry: {
app: resolve('./src/entry-skeleton.js')
}
}
})
]
带路由的骨架屏,编写skeleton.js文件
import Vue from 'vue';
import Skeleton1 from './Skeleton1';
import Skeleton2 from './Skeleton2';
export default new Vue({
components: {
Skeleton1,
Skeleton2
},
template: `
<div>
<skeleton1 id="skeleton1" style="display:none"/>
<skeleton2 id="skeleton2" style="display:none"/>
</div>
`
});
new SkeletonWebpackPlugin({
webpackConfig: {
entry: {
app: path.join(__dirname, './src/skeleton.js'),
},
},
router: {
mode: 'history',
routes: [
{
path: '/',
skeletonId: 'skeleton1'
},
{
path: '/about',
skeletonId: 'skeleton2'
},
]
},
minimize: true,
quiet: true,
})
实现骨架屏插件
class MyPlugin {
apply(compiler) {
compiler.plugin('compilation', (compilation) => {
compilation.plugin(
'html-webpack-plugin-before-html-processing',
(data) => {
data.html = data.html.replace(`<div id="app"></div>`, `
<div id="app">
<div id="home" style="display:none">首页 骨架屏</div>
<div id="about" style="display:none">about页面骨架屏</div>
</div>
<script>
if(window.hash == '#/about' || location.pathname=='/about'){
document.getElementById('about').style.display="block"
}else{
document.getElementById('home').style.display="block"
}
</script>
`);
return data;
}
)
});
}
}
npm install prerender-spa-plugin
const PrerenderSPAPlugin = require('prerender-spa-plugin')
plugins: [
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about',],
})
]
...