跳到主要内容

浏览器-网页状态

Page Visibility API

常用于网页离开监听的有三个事件:

  • pagehide: 从网页离开时执行 js.
  • beforeunload:当前页面卸载(关闭)或刷新时调用, 触发时弹出 confirm 提示框.
  • unload: 当用户点击链接离开时触发本事件.

这三种事件对手机中直接将进程切换到后台是没有帮助的.

Page Visibility API可以监听网页的可见性预判网页的卸载.

用法:

document.visibilityState = 'hidden|visible|prerender';
// 页面不可见|页面至少一部分可以见|页面正在渲染并不可见

第三种只有 Chrome 之类有与渲染能力的浏览器能返回.

返回hidden有四种情况:

  • 浏览器最小化
  • 当前页面切换为背景页
  • 浏览器将要卸载(unload)页面
  • 操作系统触发锁屏

document.visibilityState只针对顶层窗口, 内嵌的iframe即使 display 为 none 也不会触发该属性.

visibilitychange

document.visibilityState发生变化就会触发visibilitychange:

document.addEventListener('visibilitychange', function() {
// 用户离开了当前页面
if (document.visibilityState === 'hidden') {
document.title = '页面不可见';
}

// 用户打开或回到页面
if (document.visibilityState === 'visible') {
document.title = '页面可见';
}
});

Page Lifecycle API

android, ios 以及最新的 windows 可以随时自主停止后台进程, 而无法触发Page Visibility API. 于是 W3C 制定了Page Lifecycle API统一了网页从诞生到卸载的行为模式, 并且定义了新的事件, 允许开发者响应网页状态的各种转换.

除了 Chrome68 以上的版本, 其他浏览器需要一个兼容库来兼容这个 API

网页生命周期

网页的生命周期分成 6 个阶段, 每个时刻只可能处于其中一个阶段

  1. Active: 网页可见, 且拥有输入焦点
  2. Passive: 网页可见, 没有输入焦点, UI 更新执行(桌面有多个窗口)
  3. Hidden: 网页不可见, 尚未冻结, UI 不更新
  4. Terminated: 由于用户主动关闭窗口,或者在同一个窗口前往其他页面,导致当前页面开始被浏览器卸载并从内存中清除。总在 Hidden 之后发生.
  5. Frozen: 如果 Hidden 太久, 网页又没有关闭, 就会 Frozen.
  6. Discarded: Frozen 太久, 页面没有换新, 浏览器会自动卸载网页, 清除网页的内存占用. 自动 Discard 时标签页还在, 切回来会自动重新加载网页.

监听事件

freezeresume时间是新定义的, 其他都是现有的.

网页的生命周期是在所有帧触发, 包含内嵌的iframe.

  • focus: 页面获得焦点时触发
  • blur: 失去焦点时触发
  • visibilitychange: 可见状态变化时触发
  • freeze: 进入 frozen 时触发
  • resume: 离开 frozen 时触发
  • pageshow: 加载网页中触发, 与可见性无关,与 History 有关(包括从缓存中获取页面, 此时 event,persisted 为 true)
  • pagehide: 离开当前网页时触发(History 必须发生变化)
  • beforeunload: 网页即将卸载时触发.
  • unload: 页面正在卸载时触发

获取当前状态

const getState = () => {
if (document.visibilityState === 'hidden') {
return 'hidden';
}
if (document.hasFocus()) {
return 'active';
}
return 'passive';
};

document.wasDiscarded

如果某个选项卡处于 Frozen 阶段,就随时有可能被系统丢弃,进入 Discarded 阶段。如果后来用户再次点击该选项卡,浏览器会重新加载该页面。

开发者通过document.wasDiscarded了解网页是否被丢弃过:

if (document.wasDiscarded) {
// 该网页已经不是原来的状态了,曾经被浏览器丢弃过
// 恢复以前的状态
getPersistedState(self.discardedClientId);
}

同时,window 对象上会新增 window.clientId 和 window.discardedClientId 两个属性,用来恢复丢弃前的状态。

IntersectionObserver API

IntersectionObserver API可以自动的"观察"元素是否是可见的. 由于可见的本质是目标元素与视图产生一个交叉去, 所以这个API叫做 "交叉观察器".

使用非常的简单:

var io = new IntersectionObserver(callback, option);

// 开始观察
io.observe(document.getElementById('example'));

// 停止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();

IntersectionObserver是浏览器原生提供的构造函数, 接受两个参数:

  • callback: 可见性变化时的回调函数
  • option: 配置对象, 可选

这里observe的对象是一个DOM节点, 如果需要监听多个对象, 就需要多次的调用:

io.observe(elementA);
io.observe(elementB);

callback

目标元素的可见性变化的时候, 就会调用观察器的回调函数callback.

callback一般会触发两次. 一次是目标元素刚刚进入视口, 另一次是完全离开视口.

var io = new IntersectionObserver(
entries => {
console.log(entries);
}
);

如果有两个被观察的对象的可见性发生变化, entries就会有两个成员, 并且都是IntersectionObserverEntry对象

IntersectionObserverEntry

IntersectionObserverEntry对象提供目标元素的信息, 一共有6个属性.

{
time: 3893.92, // 可见性发生变化的时间, 是一个高精度的时间戳, 单位毫秒
rootBounds: ClientRect { // 根元素的矩形区域信息, getBoundingClientRect()方法的返回值, 如果没有根元素, 返回null
bottom: 920,
height: 1024,
left: 0,
right: 1024,
top: 0,
width: 920
},
boundingClientRect: ClientRect { // 目标元素的矩形区域信息
// ...
},
intersectionRect: ClientRect { // 目标元素和视口(或者根元素)的交叉区域信息
// ...
},
intersectionRatio: 0.54, // 目标元素的可见比例, 即 intersectionRect 占 boundingClientRect 的比例, 完全可见时为1, 完全不可见时小于等于0
target: element // 被观察的目标元素, 是一个 DOM 节点对象
}

option

可选的配置项

threshold

threshold: 决定什么时候触发回调函数, 是一个数组, 每个成员都是一个门槛值, 默认为0, 即交叉比达到0的时候触发回调函数

new IntersectionObserver(
entries => {/* ... */},
{
threshold: [0, 0.25, 0.5, 0.75, 1]
}
);

比如这里就表示当目标元素在0, 0.25, 0.5, 0.75, 1都会触发回调函数.

root & rootMargin

支持容器内部的滚动, root用于指定目标元素所在的容器节点. 容器节点必须是目标元素的祖先节点.

rootMargin则用来扩展或者缩小rootBounds矩形的大小, 从而影响交叉区域的大小, 顺序与css一致.

var opts = { 
root: document.querySelector('.container'),
rootMargin: "500px 0px"
};

var observer = new IntersectionObserver(
callback,
opts
);

应用

  1. 惰性加载

function query(selector) {
return Array.from(document.querySelectorAll(selector));
}

var observer = new IntersectionObserver(
function(changes) {
changes.forEach(function(change) {
var container = change.target;
var content = container.querySelector('template').content;
container.appendChild(content);
observer.unobserve(container);
});
}
);

query('.lazy-loaded').forEach(function (item) {
observer.observe(item);
});
  1. 无限滚动
var intersectionObserver = new IntersectionObserver(
function (entries) {
// 如果不可见,就返回
if (entries[0].intersectionRatio <= 0) return;
loadItems(10);
console.log('Loaded new items');
});

// 开始观察
intersectionObserver.observe(
document.querySelector('.scrollerFooter')
);

注意

该API是异步的, 只会在线程空闲的时候(requestIdleCallback)执行观察器, 这意味着该观察器的优先级非常的低

整理来源