参照上图的标注,我们可以得出一个简易结论:scrollHeight 是关于元素内部内容的高度,而 clientHeight 和 offsetHeight 是关于元素自身的高度。
接下来,我们详细介绍一下三者:
clientHeight
clientHeight = content height + padding - 水平滚动条高度
⚠️伪元素的高度也会被记入
html 元素的 clientHeight 就是视口高度
scrollHeight
scrollHeight 的值等于该元素在不使用滚动条的情况下为了适应视口中所用内容所需的最小高度。
scrollHeight = 元素内容所占高度 + padding - (水平滚动条高度 || 0)
计算方式和 clientHeight 一样,只包含元素的 padding 和 伪元素高度,不包含 margin、border 和水平滚动条高度。所以当元素可以容纳内容,不出现滚动条时 clientHeight 等于 scrollHeight。
offsetHeight
offsetHeight = content height + padding + border + 水平滚动条高度
⚠️伪元素的高度不会被记入
应用
一、判断元素是否在可视范围
1.计算元素的位置
通过 getBoundingClientRect 方法获得元素上下左右的位置:
top:元素上边缘与视口上边的距离
right:元素右边缘与视口左边的距离
bottom: 元素下边缘与视口上边的距离
left:元素左边缘与视口左边的距离
function isElementInViewport(el) {
var rect = el.getBoundingClientRect()
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
)
}
2.Intersection Observer API
创建观察器,当目标元素与指定元素(root)的交集达到所设置的 threshold(0.0~1.0),则触发回调
// 🌰 封装成一个自定义 hook
function useOnScreenCallback({ ref, callback, options, enabled = true, offScreenCallback}) {
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
if (!enabled) {
return;
}
const observer = new IntersectionObserver(([entry]) => {
// observer 回调函数触发时,更新 isIntersecting state
setIntersecting(entry.isIntersecting);
}, {threshold: 1.0, ...options});
if (ref.current) {
observer.observe(ref.current);
}
return () => {
if (ref.current) {
observer.unobserve(ref.current); // 停止监听
setIntersecting(false);
}
};
}, [ref.current, options?.root || void 0, enabled]);
// 元素进入视口,调用 callback;离开视口,调用 offScreenCallback
useEffect(() => {
if (isIntersecting) {
callback();
}
else if (!isIntersecting && offScreenCallback) {
offScreenCallback();
}
}, [isIntersecting]);
}