共计 5279 个字符,预计需要花费 14 分钟才能阅读完成。
背景
在上篇文章:记一次「有限列表」滚动优化 中,
我介绍了「如何优化一个有限滚动列表」。
用到了 懒加载
计划,一个关键点是:须要判断元素是否在以后视区
。
咱们明天就看看这个问题。
明天的次要内容包含:
- 应用
元素地位
判断元素是否在以后视区 - 应用
Intersection Observer
判断元素是否在以后视区 - 实例:
懒加载
- 实例:
有限滚动
- 实用 npm 包举荐
注释
1. 应用元素地位判断元素是否在以后视区
这种办法实现起来比较简单,咱们一步一步来。
首先:编写一个 util 函数 isVisible,它将仅接管一个参数,即 element。
export const isVisible = (el) => {};
应用 getBoundingClientRect 获取该元素的地位
const rect = el.getBoundingClientRect();
将找到窗口的高度和宽度
const vWidth = window.innerWidth || document.documentElement.clientWidth;
const vHeight = window.innerHeight || document.documentElement.clientHeight;
再编写一个函数,该函数基本上将接管 x
和 y
点,并应用 elementFromPoint
函数返回元素。
const elementFromPoint = function (x, y) {return document.elementFromPoint(x, y);
};
查看元素是否在窗口内:
// Return false if it's not in the viewport
if (rect.right < 0
|| rect.bottom < 0
|| rect.left > vWidth
|| rect.top > vHeight) {return false;}
边界查看:
// Return true if any of its four corners are visible
return (el.contains(elementFromPoint(rect.left, rect.top))
|| el.contains(efp(rect.right, rect.top))
|| el.contains(efp(rect.right, rect.bottom))
|| el.contains(efp(rect.left, rect.bottom))
);
残缺代码:
export const isVisible = (el) => {const rect = el.getBoundingClientRect();
const vWidth = window.innerWidth || document.documentElement.clientWidth;
const vHeight = window.innerHeight || document.documentElement.clientHeight;
const efp = function (x, y) {return document.elementFromPoint(x, y); };
// Return false if it's not in the viewport
if (rect.right < 0 || rect.bottom < 0
|| rect.left > vWidth || rect.top > vHeight) {return false;}
// Return true if any of its four corners are visible
return (
el.contains(elementFromPoint(rect.left, rect.top))
|| el.contains(efp(rect.right, rect.top))
|| el.contains(efp(rect.right, rect.bottom))
|| el.contains(efp(rect.left, rect.bottom))
);
};
用法:
import {isVisible} from '../utils';
// ...
const ele = document.getElementById(id);
return isVisible(ele);
逻辑并不简单,不过多介绍。
2. 应用 Intersection Observer
判断元素是否在以后视区
Intersection Observer 是一种 更高效
的形式。
为什么这么说呢?
比如说,你想跟踪 DOM 树里的一个元素,当它进入可见窗口时失去告诉。
能够通过绑定 scroll
事件或者用一个 定时器
,而后再回调函数中调用元素的 getBoundingClientRect
获取元素地位实现这个性能。
然而,这种实现形式 性能极差
。
因为每次调用 getBoundingClientRect
都会强制浏览器 从新计算
整个页面的布局,可能给你的网站造成相当大的闪动。
如果你的站点被加载到一个 iframe
里,而你想要晓得用户什么时候能看到某个元素,这简直是不可能的。
单原模型(Single Origin Model)和浏览器不会让你获取 iframe 里的任何数据。
这对于常常在 iframe 里加载的广告页面来说是一个很常见的问题。
IntersectionObserver
就是为此而生的。
它让检测一个元素是否可见 更加高效
。
IntersectionObserver 能让你晓得一个被观测的元素什么时候进入或来到浏览器的可见窗口。
应用 IntersectionObserver 也非常简单,两步走:
- 创立 IntersectionObserver
const observer = new IntersectionObserver((entries, observer) => {entries.forEach((entry) => {
// ...
console.log(entry);
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
}, options);
- 将元素传递给 IntersectionObserver
const element = document.querySelector('.element');
observer.observe(element);
entries 参数会被传递给你的回调函数,它是一个 IntersectionObserverEntry
对象数组。
每个对象都蕴含更新过的交点数据针对你所观测的元素之一。
从输入最有用的个性是:
isIntersecting
target
intersectionRect
isIntersecting
:当元素与默认根(在本例中为视口)相交时,将为 true.
target
:这将是咱们将要察看的页面上的理论元素
intersectionRect
:intersectionRect 通知元素的可见局部。这将蕴含无关元素,其高度,宽度,视口地位等的信息。
在线 Demo:
https://codepen.io/myogeshcha…
更多有用的属性
当初咱们晓得:当被观测的元素局部进入可见窗口时会触发回调函数一次,当它来到可见窗口时会触发另一次。
这样就答复了一个问题:元素 X 在不在可见窗口里。
但在某些场合,仅仅如此还不够。
这时候就轮到 threshold
退场了。
它容许你定义一个 intersectionRatio 临界值。
每次 intersectionRatio 通过这些值的时候,你的回调函数都会被调用。
threshold 的默认值是[0],就是默认行为。
如果咱们把 threshold 改为[0, 0.25, 0.5, 0.75, 1]
,当元素的每四分之一变为可见时,咱们都会收到告诉:
还一个属性没在上文列出: rootMargin
.
rootMargin
容许你指定到跟元素的间隔,容许你无效的扩充或放大穿插区域面积。
这些 margin 应用 CSS 格调的字符串,例如: 10px 20px 30px 40px
,顺次指定上、右、下、右边距。
new IntersectionObserver(entries => {// do something with entries}, {
// options
// 用于计算相交区域的根元素
// 如果未提供,应用最下级文档的可见窗口
root: null,
// 同 margin,能够是 1、2、3、4 个值,容许时负值。// 如果显式指定了跟元素,该值能够应用百分比,即根元素大小的百分之多少。// 如果没指定根元素,应用百分比会出错。rootMargin: "0px",
// 触发回调函数的临界值,用 0 ~ 1 的比率指定,也能够是一个数组。// 其值是被观测元素可视面积 / 总面积。// 当可视比率通过这个值的时候,回调函数就会被调用。threshold: [0],
});
有一点要留神:IntersectionObserver 不是完满准确到像素级别,也不是低延时性的。
应用它实现相似 依赖滚动成果的动画
注定会失败。
因为回调函数被调用的时候那些数据——严格来说曾经过期了。
3. 实例:懒加载(lazy load)
有时,咱们心愿某些动态资源(比方图片),只有用户向下滚动,它们进入视口时才加载,这样能够节俭带宽,进步网页性能。这就叫做 ” 惰性加载 ”。
有了 IntersectionObserver API,实现起来就很容易了。
function query(selector) {return Array.from(document.querySelectorAll(selector));
}
const 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);
});
下面代码中,只有指标区域可见时,才会将模板内容插入实在 DOM,从而引发动态资源的加载。
4. 实例:有限滚动
有限滚动(infinite scroll)的实现也很简略:
const intersectionObserver = new IntersectionObserver(function (entries) {
// 如果不可见,就返回
if (entries[0].intersectionRatio <= 0) return;
loadItems(10);
console.log('Loaded new items');
});
// 开始察看
intersectionObserver.observe(document.querySelector('.scrollerFooter')
);
有限滚动时,最好在页面底部有一个页尾栏。
一旦页尾栏可见,就示意用户达到了页面底部,从而加载新的条目放在页尾栏后面。
这样做的益处是:
不须要再一次调用 observe()
办法, 现有的 IntersectionObserver
能够放弃应用。
5. 实用 Npm 包举荐
和明天话题相干的 npm 包举荐的是:react-visibility-sensor
地址:https://www.npmjs.com/package…
用法也很简答:
import VisibilitySensor from "react-visibility-sensor";
function onChange (isVisible) {console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
}
function MyComponent (props) {
return (<VisibilitySensor onChange={onChange}>
<div>...content goes here...</div>
</VisibilitySensor>
);
}
动态效果演示:
在线 demo :
https://codesandbox.io/s/p73k…:174-229
结尾
内容大略就这么多,心愿对大家有所启发。
关注我
如果你感觉这篇内容对你挺有启发,那就关注我吧~
更多精彩:
聊聊 ESM、Bundleless、Vite、Snowpack
记一次「有限列表」滚动优化
「面试三板斧」之 代码宰割(上)
「面试三板斧」之缓存 (上)
「面试三板斧」之缓存 (下)
「面试三板斧」之 HTTP(上)
「面试三板斧」之 HTTP(下)
「面试三板斧」之 this
参考文章
- https://www.webhek.com/post/i…