IntersectionObserver
IntersectionObserver
最近发现一个有趣的API,实际上这个api已经面世好几年了,但最初浏览器兼容性有限,加上此功能使用场景不频繁,且有成熟的替代方案,所以知道的人并不多。
这个接口的功能,简单来说就是:监测页面中的某个元素的可视状态。可以是该元素相对于整个视窗的可见性,也可以是相对于某个父元素的可见性。我们平时使用较多的场景大概有:
举个栗子:
- 无限加载:滑动到底部时开始加载下页
- 吸顶效果:在页面滑动时触发某元素的悬浮效果(如下滑到一定位置,顶栏悬浮固定)
目前我们常用的成熟方案是,监听页面/元素的scroll事件,在scroll事件中通过判断元素的位置,来进行相应的逻辑处理。或者是分别通过touchstart
touchmove
touchend
等触摸事件,来判断滑动方向,滑动距离,再做出相应处理。主流的一些相关功能库基本也是这样的实现原理,如iscroll.js
等。
从微信小程序的一个bug说起说起
这些方案使用上并没有太明显的问题,然而最近在开发小程序时,有一个滑动吸顶的效果,需要根据滑动距离变换顶部的背景,采用的便是上述的方案,监听scroll-view
的滚动事件,根据元素距离顶部的距离实时判断修改。
为了提高性能,多滚动事件采取了节流,在此过程中发现小程序的一个问题,那就是采用节流后,如果滑动过快,小程序bindscroll事件返回的组件距离顶部位置会有错误,比如我从距顶50像素迅速滑到0,此时回调函数返回的任然是50。试验了下跟节流时间关系不大,即便设置几毫秒的节流间隔,已然存在这个问题。而如果不设置节流,在低端机,尤其安卓机上频繁触发,导致页面相当卡顿。
最后搜索半天,在微信开发者社区发现了原因所在。根据官方给出的解决方案,给scroll-view
组件增加一个属性throttle="{{false}}"
(官方文档中并无此属性),原因是官方的组件在配发滑动事件时,默认开启了节流,在滑动过快时,有部分事件可能被节流掉,就出现了上述情况,去掉官方节流,保证事件都能触发,然后自己再实现节流即可。
1 | <scroll-view |
说道半途记起的一个bug,回归正题。
使用监听scrool事件的方法,不是用节流肯定是不行的,节流事件如果太短,那么节流效果不明显,时间太长的话,事件响应又不够及时。经过权衡设置了大概50~100ms的延时,让事件的配发频率和响应延时都能接受,但效果任然不够理想,达不到极致的体验。理想情况下,我们追求:响应及时、性能负担小。
无意间看到微信的一个新的api wx.createIntersectionObserver,简单了解后不明觉厉,然后发现浏览器端也有对应的IntersectionObserver API(“交叉观察器”)。由此引出一种新的更完美的解决方案,即抛弃scroll事件,采用新的“交叉观察器”
scroll事件的一些缺点:
1、响应密集,会造成性能问题
2、为解决1,采用节流后响应不够及时
3、实现某些需求较为复杂:如,当视频滑到可视区域时自动播放等,需要判断列表中某元素的位置,较为复杂
4、统计列表中个元素的曝光量(这个用scroll时间监听的话感觉头都裂了)
IntersectionObserver
MDN doc介绍:
1 | IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。 |
属性:
IntersectionObserver.root
要监听元素的父级窗口,及我要监听的元素,是相对于那个视窗的可见性,是整个全屏的视窗,还是页面中某个容器。不传默认为全局的视窗
IntersectionObserver.rootMargin
相对于视窗的偏移量
IntersectionObserver.thresholds
阈值的列表, 按升序排列。阈值指所监听元素的可视比例,全部不可见为0,全部可见为1,配置此属性后,会在到达对应阈值后触发回调事件,如[0.1, 0.4, 1] 表示在可视比例达到10%、40%、100%时,会分别相应回调事件;如果不设置,默认是在元素全显和全隐时分别触发。
方法:
IntersectionObserver.disconnect()
使IntersectionObserver对象停止监听工作。
IntersectionObserver.observe()
使IntersectionObserver开始监听一个目标元素。
IntersectionObserver.takeRecords()
返回所有观察目标的IntersectionObserverEntry对象数组。
IntersectionObserver.unobserve()
使IntersectionObserver停止监听特定目标元素。
示例:
1 | var intersectionObserver = new IntersectionObserver(function(entries) { |
阮一峰老师有一篇文章写的很简洁易懂,推荐去看看。
微信小程序 wx.createIntersectionObserver
小程序的此API与web端的API如出一辙,使用方法大致相似。
使用步骤:
1、wx.createIntersectionObserver 创建并返回一个IntersectionObserver 对象实例。
2、指定可视窗口参照物
- IntersectionObserver.relativeTo 传入一个父级元素,作为可视窗口
- IntersectionObserver.relativeToViewport 指定页面的整个窗口作为可视参照
3、IntersectionObserver.observe 开始监听
使用步骤大概就是这三步,具体的配置可参考文档。
总结
研究哇这个api后,果断将判断吸顶时用的scroll时间替换为IntersectionObserver,体验就是写法上更简洁,性能更好,能满足更多需求。
所以基本上IntersectionObserver可以完爆scroll了,一些使用广泛的库也开始使用此方案了,so 赶快用起来吧。
IntersectionObserver