在实际开发工作中,判断页面是否可见来执行相关逻辑,能提升用户的体验性,比如页面不可见时,主动暂定正在播放的视频,并在页面可见时,主动播放视频等。
IntersectionObserver:特定的内容是否显示在了视口内,即用户是否看见了,可实际用于用户必须阅读完条文后,才能点击相关按钮或特定的内容添加相关埋点等等
Page Visibility API 页面可见性
某些场景下,开发者需要知道用户是否正在浏览网页内容。
Page Visibility API 可以监听页面的可见性发生变化。Document.visibilityState(只读属性),返回 document 的可见性。
属性值:字符串
-
hidden: 页面彻底不可见 所有浏览器必须支持触发场景:
-
浏览器最小化
-
浏览器没有最小化,但是当前页面切换成了背景页
-
操作系统触发锁屏屏幕
-
-
visible:页面至少一部分可见 所有浏览器必须支持 -
prerender:页面即将或正在渲染,处于不可见状态 只在支持"预渲染"的浏览器上才会出现
visibilityChange 事件
只要 document.visibilityState 属性发生变化,就会触发 visibilityChange 事件
document.addEventListener('visibilitychange', () => {
console.log( document.visibilityState )
// 页面可见性发生改变后,执行响应逻辑
})
通过监听网页的可见性,可以用来节省资源,当用户不看网页时,可以暂停对服务器的轮询、暂停网页动画、暂停播放音频或视频等操作
IntersectionObserver API 交叉观察器
IntersectionObserver 接口提供了一种异步观察目标元素与其祖先元素或视口(viewport)交叉状态的方法。祖先元素或视口(viewport)被称为根(root)
API 使用
1、创建观察者(观察者一旦被创建,其配置则无法更改)
// callback 参数是目标元素与root交叉变化时的回调函数
// option 参数是配置对象
let observer = new IntersectionObserver(callback, option)
2、开始观察
observer.observe(dom)
3、停止观察指定元素或关闭观察器
// 停止观察指定元素
observe.unobserve(dom)
// 关闭观察器
observe.disconnect()
callback 回调函数 参数
let observer = new IntersectionObserver((entries) => {
console.log(entries)
})
未在 option 中配置 threshold 值时,callback 针对一个目标元素,一般会触发三次。
-
开始观察目标元素时,会主动触发一次回调
-
目标元素刚刚和
root交叉 -
目标元素和
root从交叉到完全不交叉
callback 函数的入参是一个数组,数组成员为 IntersectionObserverEntry 对象
IntersectionObserverEntry 对象: 只读
boundingClientRect: 目标元素的DOMRect信息intersectionRatio: 目标元素与root的交叉区域占目标元素的比例intersectionRect: 交叉区域的DOMRect信息isIntersection: 目标元素与root是否交叉rootBounds:root的DOMRect信息target:目标元素DOM节点对象
option 配置对象
-
root:监听元素的具体祖先元素,默认为视口viewport -
thresholds:比如:数组[0.2, 0.8]。数组内数据升序排列。当intersectionRatio超过数组内值时,触发回调,默认为 0 -
rootMargin:计算交叉时添加到根(root)边界盒bounding box的矩形偏移量,可以有效的缩小或扩大根的判定范围从而满足计算要求。所有偏移量均可用像素或者百分比来表达,默认值为"0px 0px 0px 0px"
Vue 自定义指令:IntersectionObserver 配合 Velocity 动画库
// 代码可引入全局自定义指令
/**
TODO:
当浏览器不支持交叉观察器时,使用 getBoundingClientRect 配置 scroll 事件来兼容,代码中固定了 scroll container 为 "#app"
可根据实际需求添加自定义指令的绑定值或者使用 mixin 方式改用局部注册方式实现
*/
import Vue from 'vue'
import Velocity from 'velocity-animate'
import { throttle } from 'lodash'
// 是否支持交叉观察器api
const isIntersectionObserver = 'IntersectionObserver' in window
// viewport 宽高
let vHeight
let vWidth
window.addEventListener('resize', calcWindowInfo)
calcWindowInfo()
// vue 插件
const globalDirective = {
install (Vue) {
Vue.directive('observe', {
bind (el) {
el.style.opacity = 0
},
inserted (el) {
if (isIntersectionObserver) {
// 使用交叉观察器
observe(el)
} else {
// el是绑定指令的dom对象,可自由添加属性
el.$handler = throttleFun(() => {
calcIntersecting(el, el.$handler)
}, 300)
// 监听scroll事件,并结合 getBoundingClientRect 来判断是否显示在viewport
listenerScroll(el)
}
},
unbind (el) {
if (el.$handler) {
document.getElementById('app').removeEventListener('scroll', el.$handler)
}
}
})
}
}
// 使用交叉观察器
function observe (el) {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
// 取消观测
observer.unobserve(el)
animVelocity(el)
}
})
// 观测
observer.observe(el)
}
const throttleFun = (func, wait = 50) => throttle(func, wait)
// 监听scroll事件,并通过DomRect判断是否交叉
function listenerScroll (el) {
// 根据实际情况,给特定的dom添加scroll事件监听
document.getElementById('app').addEventListener('scroll', el.$handler)
setTimeout(() => {
el.$handler()
}, 60)
}
function calcIntersecting (el, handler) {
const { top, right, bottom, left } = el.getBoundingClientRect()
// 在viewport内
if (!(bottom < 0 || left > vWidth || top > vHeight || right < 0)) {
// 移除监听
document.getElementById('app').removeEventListener('scroll', handler)
animVelocity(el)
}
}
// 执行dom显示动画
function animVelocity (el) {
Velocity(el, {
/* translateX 初始值永远为-80px 动画结束值为0 */
translateX: [0, -80],
translateY: [0, 50],
scale: [1, 0.8],
/* opacity 初始值永远为0 动画结束值为1 缓动效果为"easeInSine" */
opacity: [1, 'easeInSine', 0]
})
}
// viewport 宽高信息
function calcWindowInfo () {
vHeight = document.documentElement.clientHeight || document.body.clientHeight
vWidth = document.documentElement.clientWidth || document.body.clientWidth
}
Vue.use(globalDirective)
export default globalDirective
参考文章: