scrollHeight、offsetHeight 和 clientHeight

321 阅读2分钟

IMG_0E40BA5831B6-1.jpeg

参照上图的标注,我们可以得出一个简易结论:scrollHeight 是关于元素内部内容的高度,而 clientHeightoffsetHeight 是关于元素自身的高度。

接下来,我们详细介绍一下三者:

clientHeight

clientHeight = content height + padding - 水平滚动条高度

⚠️伪元素的高度也会被记入

html 元素的 clientHeight 就是视口高度

scrollHeight

image.png

scrollHeight 的值等于该元素在不使用滚动条的情况下为了适应视口中所用内容所需的最小高度。

scrollHeight = 元素内容所占高度 + padding - (水平滚动条高度 || 0)

计算方式和 clientHeight 一样,只包含元素的 padding 和 伪元素高度,不包含 margin、border 和水平滚动条高度。所以当元素可以容纳内容,不出现滚动条时 clientHeight 等于 scrollHeight。

offsetHeight

offsetHeight = content height + padding + border + 水平滚动条高度

⚠️伪元素的高度不会被记入


应用

一、判断元素是否在可视范围

1.计算元素的位置

通过 getBoundingClientRect 方法获得元素上下左右的位置:
top:元素边缘与视口边的距离
right:元素边缘与视口边的距离
bottom: 元素边缘与视口边的距离
left:元素边缘与视口边的距离

image.png

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]);
}