用 Intersection Observer 来自己造个图片懒加载的轮子₍₍ (ง ˙ω˙)ว ⁾⁾
✨小透明・宸✨
2020-03-26 14:33:08

封面图:Pixiv ID: 80105763 「アッカリーン」 by cedar

Intersection Observer 这东西之前我是一直没听说过的,至于为什么发现了它……这个过程就有点奇怪了(〃′▽`)

  1. 寒假的时候尝试着用 VueMDUI 搞了点大新闻。
  2. 在搜索其他 Material Design 风格的界面框架时,发现了 Vuetify 这个 Vue 组件库。
  3. 翻了 Vuetify 的文档,它的功能比 MDUI 丰富了不少,居然还有个懒加载功能!
  4. 懒加载的实现靠的是 Intersection Observer,这是……哎?这么神奇的东西原来浏览器就自带啊!(。•́ - •̀。)

基本的食用方法

Intersection Observer 可以用于检测网页上两个元素的交集的变化,并且在这个“交集”改变到一定程度时自动调用一个函数。

下面是一个实例,虽然是一片空白,但是可以试着滑啊滑~当两个一组的色块组合开始显示/移出、显示/移出了一半、完全显示/移出时下面的输出都会更新。

首先要创建一个 Intersection Observer 对象,并设定好要自动调用的函数以及根元素:

let callback = entries => {console.log(entries)};

let observer = new IntersectionServer(callback, {
    root: document.getElementById('root'),
    rootMargin: '1px 2px 4px 8px',
    threshold: [0, .5, 1],
});
  • callback 是调用的函数,稍后再说明~
  • root根元素的 DOM,不设定的话就是浏览器窗口。
  • rootMargin 是根元素的边距(相当于将根元素的范围扩大了),写法和 CSS 的 margin 是一样的。
  • threshold 是其他元素和根元素的交集的阈值,交集为 0 代表该元素在根元素之外,1 代表该元素完全与根元素完全重合。阈值可以设为一个或多个值(数组),不设定的话就是 0

这样就相当于有了一个属于上面的“根元素”的“观察器”,接下来就可以设定它要观察哪些元素与根元素的交集变化了:

// 开始观察某个元素
observer.observe(document.getElementById('target-1'));
observer.observe(document.getElementById('target-2'));

// 还可以取消观察某个元素
observer.unobserve(document.getElementById('target-1'));

// 或者取消观察所有元素
observer.disconnect();

使用 observe 开始观察某个元素时,或是某个被观察的元素(可能不唯一)与根元素的交集增加或减少并且经过了上面设定的阈值时,就会执行 callback 函数。调用函数时使用的唯一的参数是由 IntersectionObserverEntry 对象组成的数组。

这个对象包含了和元素交叉的事件有关的一些信息,部分比较常用的属性:

  • time 是从新建“观察器”到触发函数间隔的时间,单位是毫秒。
  • isIntersectingintersectionRatio 分别表示被观察的元素和根元素是否有交集以及交集的大小,对应上面的 threshold
  • target 是被观察的元素。

触发函数时,intersectionRatio 和设定的 threshold 可能会有少量的误差~例如设定 threshold0.5,进入和离开根元素时 intersectionRatio 会略高于 / 略低于 0.5。另外开始观察某个元素时会立即触发一次函数,因此虽然设定了 threshold 但是在这里仍然需要对 intersectionRatio 进行检查。

上面的演示的完整代码:

<div id="demo-1-main" style="max-height: 200px; overflow-y: auto;">
    <div>
        <div style="height: 300px;"></div>
        <div id="demo-1-target-1">
            <div style="height: 75px; background-color: red;"></div>
            <div style="height: 75px; background-color: green;"></div>
        </div>
        <div style="height: 300px;"></div>
        <div id="demo-1-target-2">
            <div style="height: 75px; background-color: blue;"></div>
            <div style="height: 75px; background-color: yellow;"></div>
        </div>
        <div style="height: 300px;"></div>
    </div>
</div>
<pre id="demo-1-log"></pre>

<script>
let observer = new IntersectionObserver(entries => {
    let text = '';
    entries.forEach(e => {
        for (var key in e) {
            text += key + ': ' + e[key] + '<br>';
        }
    });
    document.getElementById('demo-1-log').innerHTML = text;
}, {
    root: document.getElementById('demo-1-main'),
    rootMargin: '0px 0px 0px 0px',
    threshold: [0, .5, 1],
});
observer.observe(document.getElementById('demo-1-target-1'));
observer.observe(document.getElementById('demo-1-target-2'));
</script>

实例:“懒加载”

如果一个网页上有很多图片的话,不是所有人都会一直滑到底的,那些没有显示出来的图片实际上也没有被加载的必要,所以就有了“懒加载”这种操作,当图片(的占位符)进入页面时再加载~

实际上“图片”也可以替换成其他需要按需加载的东西,比如在用 Vue 折腾的单页应用上,不同页面的 JS 代码也可以懒加载了。

以前的各种懒加载实现,都需要自己手动获取屏幕 / 图片的尺寸和位置,然后来个 setInterval 定时进行比较或者写进 onscroll 事件,后者还得加个去抖(debounce)或节流(throttle)。

……但是手动计算不麻烦吗?这么多参数怎么可能记得住?真的不会弄混吗?(╯‵□′)╯︵┻━┻

也有用 jQuery 实现的例子,不过这种落后的东西就没有什么学习的必要了嘛……

但是 Intersection Observer 就很适合拿来实现懒加载哦੭ ᐕ)੭*⁾⁾ 只要以窗口为根元素,观察所有的 <img>,在回调函数里设定它们的 src,就可以简单地实现懒加载了(⑉・̆-・̆⑉)

// 图片的URL不写在src而是写在别的属性里
// 例如:<img data-src="image.jpg">

// 新建“观察器”
// 可以设定一些垂直方向的边距,提前一段时间加载图片
let observer = new IntersectionObserver(entries => {
    entries.forEach(e => {
        // 只有图片进入根元素时才执行
        if (!e.isIntersecting) return;

        // 设定为src的值,图片加载过以后就可以取消观察了
        let t = e.target;
        if (!t.src) t.src = t.getAttribute('data-src');
        observer.unobserve(t);
    });
}, {rootMargin: '64px 0px'});

// 观察所有图片的位置
document.querySelectorAll('img[data-src]').forEach(e => {
    observer.observe(e);
});

而且这个功能已经在本站实装了~用的就是上面的自己写的代码(*´∀`*)

其实现在用着的主题是自带懒加载功能的,不过用的是一个 jQuery 插件,很久之前还被我砍掉了

然后我发现了另一个原生 JS 实现的懒加载插件,也是用到了 Intersection Observer,所以我自己造了个轮子?!

实例:“无限滚动”

很多“信息流”的网站,例如○乎和○浪微博,都实现了无限滚动功能,滑到页面底部就自动再加载一些东西放到时间线上。于是用户就这样不知不觉沉浸于由算法推荐的“信息饲料”中,出不去了

加载可以用 AJAX 实现,然后用 Intersection Observer 检查“信息流”的末尾是否出现,出现后就立即加载更多数据。下面的实例中,往下滑啊滑,出现“我们是有底线的”这行字就会自动加载下一个色块了~(为了展示效果,添加了少量的延迟)

我们是有底线的
let observer = new IntersectionObserver(function (e) {
    if (!e[0].isIntersecting) return;
    let el = document.createElement('div');
    el.style.cssText = `height:75px;background-color:rgb(${[1, 2, 3].map(() => Math.floor(256 * Math.random())).join()})`;
    document.getElementById('timeline').appendChild(el);
}, {root: document.getElementById('main')});
observer.observe(document.getElementById('end'));

实例:视频的“小窗播放”

这个例子是得到了 CSS-Tricks 的一篇文章的启发,不过那里面提到的是检测到视频离开窗口就自动暂停播放,其实还可以有更多的功能嘛……(・▽・〃)

经常上睿站看视频的小伙伴应该比较熟悉这个“小窗播放”的功能,在查看评论区的时候视频会自动切换到右下角的一个小窗口继续播放~

查看评论区的时候视频会出现在浏览器窗口外面,因此用 Intersection Observer 动态改变视频的位置就能实现上面的效果。我自己写了个简单的实例,点这里就能查看效果了(´゚ω゚`)


 
W3C 呢……最近2017 年发表了一个报告说呢……浏览器会通过一些渠道Intersection Observer去影响、干预元素的交集,你对这个看法有什么回应呢?

Intersection Observer 是个比较新的功能,如果问浏览器资词不资词,大多数都是资词的,除了思想江化的 IE 和(旧版)Safari。

为了给这些思想江化的浏览器提高姿势水平,可以使用 W3C 钦定的一个 Polyfill

window.IntersectionObserver||function(e){var n=e.createElement("script");n.src="https://cdn.jsdelivr.net/gh/w3c/IntersectionObserver/polyfill/intersection-observer.min.js",e.body.appendChild(n)}(document)
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
本文作者:✨小透明・宸✨
本文链接:https://akarin.dev/2020/03/26/intersection-observer/
chevron_left 上一篇 下一篇 chevron_right