B
Background Fetch API (后台缓存API)
MDN
Background Fetch API于2015年9月发布,是一个提供给开发者使用的后台缓存API。它能够在用户断网或者将浏览器关闭时等待或者继续下载资源。因此,如果需要下载很大的资源时,列如电影,游戏关卡等,可以使用这个API
。
Background Fetch API下载的资源是存储在浏览器的,不是存储在系统文件夹中
。
认识Background Fetch API的基础属性和函数
navigator.serviceWorker.ready
该函数是serviceWorker对象的一个属性,返回一个Promise对象,当serviceWorker对象准备就绪时,该Promise对象会被调用。
// 当serviceWorker对象准备就绪时,会调用
navigator.serviceWorker.ready.then(function(registration) {
// 注册成功
console.log('Service Worker 注册成功');
});
registration.backgroundFetch.fetch()
该函数是Background Fetch API的一个方法,用于向设备发起一次后台缓存请求。返回一个Promise对象,如果用户退出或者参数错误,该Promise对象会被拒绝(catch
)。
该函数的参数为
registration.backgroundFetch.fetch('后台fetch唯一id',['需要下载URL1','需要下载URL2','...'],{
// 配置参数,可选
layout: post
title: '在浏览器显示的标题',
icons: [{
src: '图标的URL',
sizes: '图标的尺寸,如:192x192',
type: '图标的类型,如:image/png'
}],
downloadTotal: '需要下载的总文件大小,单位为字节,用于告诉用户总共需要下载多少字节的资源且提供进度信息。如果不提供,则显示未知大小。如果fetch超过了此参数则会被暂停下载。',
})
若该函数成功调用then
会返回一个BackgroundFetchRegistration
对象,该对象属性为:
BackgroundFetchRegistration{
downloadTotal: 229000, // 注册fetch时提供的值
downloaded: 1955, //成功下载的字节数,如果链接断开或无法恢复下载,此值可能较小且浏览器将重新下载。
failureReason: "", // "" 后台下载未失败。"aborted" 后台下载被用户停止或调用了abort()。"bad-status" 其中一个响应不正常如404。"fetch-error" 其中一个下载失败如cors,mix等无效响应。"quota-exceeded" 后台下载超过了设备的存储限制。"download-total-exceeded" 超出提供的downloadTotal
id: "myFetch", // 后台fetch唯一id
onprogress: null
recordsAvailable: true, // 是否可以访问基础请求/响应?一旦这是false,不能使用match/matchAll
result: "", // "" 处于下载中,"success" 下载成功,"failure" 只有完全下载失败才会返回failure
uploadTotal: 0, // 要发送给后台的数据总大小
uploaded: 0, // 成功发送给后台的数据大小
}
若该函数成功调用then
会返回一个BackgroundFetchRegistration
对象,该对象方法为:
- abort(): 停止后台下载,返回一个Promise对象,成功为true,失败为false。
- matchAllmatchAll(request, opts): 获取请求和响应,参数和cache API相同,不带参数将返回所有记录的应许。
- match(request, opts): 和matchAll相同,但只返回第一个记录的响应。
若该函数成功调用会返回一个BackgroundFetchRegistration
对象,该对象事件为: - progress: 当后台下载进度或更改时触发。
uploaded
,downloaded
,result
,failureReason
。Background Fetch API的事件
Background Fetch API提供了以下事件: backgroundfetchsuccess
:当后台缓存请求成功时触发backgroundfetchfail
:当后台缓存请求失败时触发backgroundfetched
:当后台缓存请求完成时触发backgroundfetchabort
:当后台缓存请求被取消时触发backgroundfetchclick
:当后台缓存请求被点击时触发backgroundfetchfailure
:一个或多个读取失败。Background Fetch API的应用
下载素材
注册sw文件
<script> window.addEventListener('load', function () { if('serviceWorker' in navigator){ navigator.serviceWorker.register('sw.js').then(function (registration) { console.log('ServiceWorked成功注册在: ', registration.scope); }).catch(function (err) { console.log('ServiceWorker注册失败: ', err); }); } }); </script>
缓存资源
这里在sw注册完毕后就直接缓存,正确的做法应该是让用户选择是否缓存。
<!-- index.html --> <script> window.addEventListener('load', function () { ... //后台下载缓存资源 navigator.serviceWorker.ready.then(function (registration) { registration.backgroundFetch.fetch('myFetch',['/index.html','/img/1.jpg','/video/test.mp4'],{ layout: post title: '缓存资源中', downloadTotal: 229000, // [巨坑]此参数一定要慎重设置,过小会导致下载失败且不会报错,任务将会保持在后台导至fetch卡死。 }) .then(function (res) { console.log('缓存成功',res); }) .catch(function (err) { console.log('缓存失败',err); }); }); } }); </script>
成功后保存到浏览器储存
//sw.js addEventListener('backgroundfetchsuccess',event=>{ event.waitUntil( event.registration.matchAll() // 拿到所有的触发backgroundfetch的路由 .then(records =>{ caches.open(event.registration.id) // 这里直接拿后台fetch的名字作为缓存名字 .then(cache=>{ return Promise.all( records.map(record=>{ record.responseReady.then(response => { // 将每个路由的response放入缓存 cache.put(record.request, response); }) }) ) }).catch(err=>{ console.log(err) }) }).catch(err=>{ console.log(err) }) ) })
如果不出意外将在浏览器看见缓存文件
实现脱机
// sw.js addEventListener('fetch',event=>{ if (new URL(event.request.url).origin === location.origin) { // 只接受本域的请求 event.respondWith( caches.match(event.request) ) } })
如果没问题,接下来选择脱机也能正常使用。
全部代码
<!-- index.html --> <head> <!-- 新 Bootstrap5 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css"> </head> <div class="container pt-5"> <div class="row" id="movie"> <div class="col"> <h1 class="display-6 text-center" id="network"></h1> <h1 class="display-6 text-center">本地图片</h1> <img src="img/1.jpg" class="img-headimg mx-auto d-block"> <h1 class="display-6 text-center">视频</h1> <video src="video/test.mp4" controls="controls" class="img-headimg mx-auto d-block"></video> </div> </div> </div> <script> window.addEventListener('load', function () { if('serviceWorker' in navigator){ navigator.serviceWorker.register('sw.js').then(function (registration) { console.log('ServiceWorked成功注册在: ', registration.scope); }).catch(function (err) { console.log('ServiceWorker注册失败,建议注销sw重试 ', err); }); //后台下载缓存资源 navigator.serviceWorker.ready.then(function (registration) { registration.backgroundFetch.fetch('myFetch',['/index.html','/img/1.jpg','/video/test.mp4'],{ layout: post title: '缓存资源中', downloadTotal: 23399962, // [巨坑]此参数一定要慎重设置,过小会导致下载失败且不会报错,任务将会保持在后台导至fetch卡死。 }) .then(function (res) { console.log('缓存成功',res); }) .catch(function (err) { console.log('缓存失败',err); }); }); } }); </script> <script> // 素材的js,不需要看。 const network = document.getElementById('network'); network.innerText = navigator.onLine ? '网络已连接' : '网络已断开'; window.addEventListener('online', () => { network.innerText = '网络已连接'; }); window.addEventListener('offline', () => { network.innerText = '网络已断开'; }); </script>
```js
// sw.js
addEventListener(‘backgroundfetchsuccess’,event=>{
event.waitUntil(event.registration.matchAll() // 拿到所有的触发backgroundfetch的路由 .then(records =>{ caches.open(event.registration.id) // 这里直接拿后台fetch的名字作为缓存名字 .then(cache=>{ return Promise.all( records.map(record=>{ record.responseReady.then(response => { // 将每个路由的response放入缓存 cache.put(record.request, response); }) }) ) }).catch(err=>{ console.log(err) }) }).catch(err=>{ console.log(err) })
)
})
addEventListener(‘fetch’,event=>{
if (new URL(event.request.url).origin === location.origin) {
// 只接受本域的请求
event.respondWith(
caches.match(event.request)
)
}
})
### 参考文献
[Google开发者社区](https://developer.chrome.com/blog/background-fetch/)
### 浏览器兼容性
![博客BackgroundFetchAPI浏览器兼容性](https://jsdelivr.007666.xyz/gh/1802024110/GitHub_Oss@main/img/博客BackgroundFetchAPI浏览器兼容性.png)
## Background Tasks API (后台任务API)
[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Background_Tasks_API#%E6%A6%82%E5%BF%B5%E5%92%8C%E7%94%A8%E6%B3%95)
在JavaScript中调用优先级较低的任务是比较麻烦的,此API可以有效的解决此问题。
`Background Tasks API`又称为台任务协作计划 API,由 W3C 给出的函数。`它允许我们计划在页面处于空闲状态时执行的任务。`
### 方法一: `Window.requestIdleCallback()`
#### 概念
`requestIdleCallback()`方法插入一个函数,这个函数将在`浏览器空闲时期被调用`。这使开发者能够在主事件循环上`执行后台和低优先级工作`,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行`超时时间timeout`,则有可能为了在超时前执行函数而打乱执行顺序。
#### 语法
```js
var handle = window.requestIdleCallback(callback[, options])
返回值
一个任务ID,可以传入 Window.cancelIdleCallback()
方法来结束回调。
参数
callback
函数会接收到一个名为IdleDeadline
的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。options
(可选)
方法二: window.cancelIdleCallback()
概念
window.cancelIdleCallback()
方法用于取消之前调用window.requestIdleCallback()
的回调。
语法
window.cancelIdleCallback(handle);
返回值
undefined.
参数
参考文献
Battery Status API(电池状态API)
Battery Status API,更多时候被称之为 Battery API, 提供了有关系统充电的信息并提供了充电状态的改变的事件。 这个可以在设备电量低的时候调整应用的资源使用状态,或者在电池用尽前保存应用中的修改以防数据丢失。
Battery Status Api在window.navigator对象上提供了一个getBattery()方法,返回一个Battery的promise对象。回调函数传递了一个 BatteryManager 对象,并提供了一些新的可以操作电池状态的事件。
以下示例代码如未专门提示,则默认是在回调函数中
navigator.getBattery().then(function(BatteryManager) {
// 示例代码
});
现在xxx时间了
BatteryManager.charging
返回一个布尔值,表示电池是否在充电。console.log("Battery charging? " + (BatteryManager.charging ? "Yes" : "No"));
BatteryManager.level
返回电池的电量百分比console.log("剩余电池电量: " + BatteryManager.level * 100 + "%");
BatteryManager.chargingTime
返回电池充电的时间,以秒为单位console.log("电池充满还差: " + BatteryManager.chargingTime + " seconds");
BatteryManager.dischargingTime
返回电池放电的时间,以秒为单位console.log("电池还能坚挺: " + BatteryManager.dischargingTime + " seconds");
BatteryManager.addEventListener(eventName, function);
电池的事件,可以监听以下事件:
事件名称 | 描述 |
---|---|
chargingchange | 当电池的充电状态发生改变时触发 |
chargingtimechange | 当电池的充电时间发生改变时触发 |
dischargingtimechange | 当电池的放电时间发生改变时触发 |
levelchange | 当电池的电量发生改变时触发 |
BatteryManager.addEventListener("chargingchange", function() {
console.log("电池充电状态改变");
});
浏览器兼容性
.
Beacon API (信标API)
传统的fetch等在页面被关闭时,会被浏览器关闭。而XMLHttpRequest强制发送会导致浏览器卡顿。为了解决这个问题,浏览器提供了Beacon API。
改API接受一个URL和一个数据参数(可选的类型为ArrayBufferView,Blob,DOMString,FormData),并且发送Post请求。如果请求成功进入任务队列,则返回true。改API在页面标签关闭时仍在后台队列中等待发送。
使用场景
- 在页面关闭时,可以通过Beacon API发送数据到后台,以便后台能够接收到数据。
代码示例
```js
/
用node开启的服务器,默认使用80端口。
只开开启了psot请求,地址为http://127.0.0.1/post
需要安装的轮子:
npm install express
npm install connect-multiparty /
var express = require(‘express’);
var app = express();
// 用来解析multipart/form-data
const multipart = require(‘connect-multiparty’);
const multipartyMiddleware = multipart();
app.post(‘/post’, multipartyMiddleware, function (req, res) {
console.log(req.body);
res.end(JSON.stringify(req.body));
})
var server = app.listen(80, function () {
var host = server.address().address
var port = server.address().port
console.log(“应用实例,访问地址为 http://%s:%s“, host, port)
})
```js
function sendBeacon(){
let params = new FormData();
params.append("testMsg","Hello World");
/*
@param url : 需要POST的URL地址
@param params : 需要POST的数据
@return bool : 是否成功加入请求队列
*/
const joinCode = navigator.sendBeacon('http://127.0.0.1/post',params)
console.log(joinCode);
}
浏览器兼容性
Navigator.sendBeacon().Browser_compatibility表说明了该方法具有相对广泛地实现
。但是,WorkerNavigator.sendBeacon().Browser_compatibility数据显示该方法没有被实现。
蓝牙API (蓝牙API)
Broadcast Channel API(广播通道API)
MDN
Broadcast Channel API 可以实现同 源 下浏览器不同窗口,Tab页,frame或者 iframe 下的 浏览器上下文 (通常是同一个网站下不同的页面)之间的简单通讯。也就是说,如果用户打开了同一个网站的的两个标签窗口,如果网站内容发生了变化,那么两个窗口会同时得到更新通知。拿Facebook作为例子,假如你现在已经打开了Facebook的一个窗口,但是你此时还没有登录,此时你又打开另外一个窗口进行登录,那么你就可以通知其他窗口/标签页去告诉它们一个用户已经登录了并请求它们进行相应的页面更新。
实战
创建广播通道
创建一个新的BroadcastChannel API 是一个易如反掌的事情。你需要做的仅仅是把通道名称作为一个参数传给BroadcastChannel的构造函数然后把它的引用保存到一个变量上面而已。相当于创建了一个主题,你可以在这个主题下发布消息,也可以在这个主题下订阅消息。
// 创建广播通道
const channel = new BroadcastChannel('channelName');
发送消息
发送一个消息也是一个非常简单的事情,你只需要引用赋有了BroadcastChannel实例的变量然后调用其postMessage方法就可以了。postMessage方法做的漂亮的地方是你可以用它来发送任何东西。你可以发送一个对象,一个字串,随你便。只要订阅者可以意识到你要发送的是什么事件就行了,好好享受吧。
// 发送消息
channel.postMessage(any);
接收消息
也可以理解为订阅。只需要使用BroadcastChannel对象的onmessage属性,并绑定一个回调函数,就可以接收到消息了。
// 接收消息
channel.onmessage = function(event){
console.log(event.data);
}
关闭广播通道
如果在意性能的话,你可以在不需要的时候关闭广播通道。
// 关闭广播通道
channel.close();
一个简单的文本传输demo
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
text-decoration: none;
color: rgb(0, 0, 0);
}
a:active {
color: rgb(255, 0, 0);
}
.sendBox{
display: none;
}
.recBox{
display: none;
}
#send:checked~.sendBox{
display: block;
}
#rec:checked~.recBox{
display: block;
}
</style>
<div>
<input type="radio" name="rd" id="send" checked>
<label for="send">发送</label>
<input type="radio" name="rd" id="rec">
<label for="rec">接收</label>
<div id="sendBox" class="sendBox">
<input type="text" id="sendName" placeholder="请输入频道名称">
<input type="text" id="sendMessage" placeholder="请输入消息">
<button id="sendMsg">发送</button>
</div>
<div id="recBox" class="recBox">
<input type="text" id="recName" placeholder="请输入频道名称">
<button id="recMsg">接受</button>
<p id="msgBox"></p>
</div>
</div>
<script>
const sendName = document.getElementById('sendName');
const sendMessage = document.getElementById('sendMessage');
const send = document.getElementById('sendMsg');
const recName = document.getElementById('recName');
const rec = document.getElementById('recMsg');
const msgBox = document.getElementById('msgBox');
send.onclick = ()=>{
const bc = new BroadcastChannel(sendName.value);
bc.postMessage(sendMessage.value);
}
rec.onclick = ()=>{
const bc = new BroadcastChannel(recName.value);
bc.onmessage = (e)=>{
msgBox.innerHTML = e.data;
}
}
</script>
浏览器兼容性
C
CSS Counter Styles (自定义有序列表符号样式)
改API主要用于自定义有序列表符号样式
。
简单的栗子
@counter-style规则语法
@counter-style 任意自定义名称 {
system: 计数系统
symbols: 计数符号
additive-symbols: 附加符号
negative: 负数符号
prefix: 前缀
suffix: 后缀
range: 范围
pad: 补全
speak-as: 无障碍,如何阅读
fallback: 备份计数规则
}