如何判断元素是否在可视区域ViewPort

个性签名: 生如夏花,逝如冬雪;人生如此,何悔何怨。css

前言: 常常须要计算元素的大小或者所在页面的位置,offsetWidth,clientWidth,scrollWidth,scrollTop这几个关键字的出现更是屡见不鲜,每次碰到都须要事先实验一番。为了下次开发提升效率。在这里一次性作个总结, 以用来判断元素是否在可视区域以及用原生js简单实现懒加载。文末有个简单的懒加载实现的demo,有须要的能够看一下。

目录html

工欲善其事,必先利其器。在判断元素是否在可视区域实现简单的原生懒加载前,咱们先简单回顾下如下几个关键的概念。git

ps: 若是你对这些概念已经比较熟悉了,能够直接跳到第五点查看关键代码示例。github

1. 偏移量

偏移量(offset dimension),元素的可见大小由其高度、宽度决定,包括全部内边距、滚动条和边框大小(注意,不包括外边距)。经过下列4个属性能够取得元素的偏移量。api

偏移量 概念 公式
offsetHeight 元素在垂直方向上占用的空间大小,以像素计。包括元素的高度、(可见的) 水平滚动条的高度、上边框高度和下边框高度。 offsetHeght = content + padding + border + scrollX
offsetWidth 元素在水平方向上占用的空间大小,以像素计。包括元素的宽度、(可见的)垂 直滚动条的宽度、左边框宽度和右边框宽度。 offsetWidth = content + padding + border + scrollY
offsetLeft 元素的左外边框至**包含元素的左内边框之间的像素距离。
offsetTop 元素的上外边框至包含元素的上内边框之间的像素距离。

其中,offsetLeft 和 offsetTop 属性与包含元素有关,包含元素的引用保存在 offsetParent 属性中。**offsetParent 属性不必定与 parentNode 的值相等。浏览器

以下图显示
偏移量图示app

注意: 全部这些偏移量属性都是只读的,并且每次访问它们都须要从新计算。所以,应该尽可能避免重复访问这些属性;若是须要重复使用其中某些属性的值,能够将它们保 存在局部变量中,以提升性能。函数

这也是上篇文章文字跑马灯项目中(戳此跳转),为何增长padding后,textWidth须要从新获取的缘由
文字跑马灯post

小结 性能

偏移量: 只读属性;包括滚动条和边框,不包括外边距。

2. 客户区大小

客户区大小是只读的,每次访问都要从新计算的。

客户区大小 概念 公式
clientWidth clientWidth 属性是元素内容区宽度加 上左右内边距宽度; clientWidth = content + padding
clientHeight 元素内容区高度加上上下内边距高度 clientHeight = content + padding

最经常使用到这些属性的状况,就是肯定浏览器视口大小的时候(在 IE7 以前的版本中)。以下面的例子所示:

function getViewport(){
    // 检查 document.compatMode 属性,以肯定浏览器是否运行在混杂模式。
    // Safari3.1 以前的版本不支持这个属性,所以就会自动执行 else 语句
    if (document.compatMode == "BackCompat"){
        return {
            width: document.body.clientWidth,
            height: document.body.clientHeight
        };
    } else {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        }; 
    }
}

小结

客户区大小: 只读属性;不包括滚动条和边框,不包括外边距。

3. 滚动大小

概念
scrollHeight 在没有滚动条的状况下,元素内容的总高度。
scrollWidth 在没有滚动条的状况下,元素内容的总宽度。
scrollLeft 被隐藏在内容区域左侧的像素数。经过设置这个属性能够改变元素的滚动位置。
scrollTop 被隐藏在内容区域上方的像素数。经过设置这个属性能够改变元素的滚动位置。

scrollWidth 和 scrollHeight 主要用于肯定元素内容的实际大小。

scrollLeft 和 scrollTop属性既能够肯定元素当前滚动的状态,也能够设置元素的滚动位 置。在元素还没有被滚动时,这两个属性的值都等于 0。若是元素被垂直滚动了,那么 scrollTop 的值 会大于 0,且表示元素上方不可见内容的像素高度。若是元素被水平滚动了,那么 scrollLeft 的值会 大于 0,且表示元素左侧不可见内容的像素宽度。这两个属性都是能够设置的,所以将元素的 scrollLeft 和 scrollTop 设置为 0,就能够重置元素的滚动位置。好比:上篇文章文字跑马灯项目中scrollLeft的使用(戳此跳转)

小结

只读属性,不包括滚动条、border。

4. 肯定元素大小

getBoundingClientRect

getBoundingClientRect的兼容性写法:

对于不支持 getBoundingClientRect()的浏览器,能够经过其余手段取得相同的信息。通常来 说,right 和 left 的差值与 offsetWidth 的值相等,而 bottom 和 top 的差值与 offsetHeight 相等。综合上述,就能够建立出下面这个跨浏览器的函数:

function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null){
        actualLeft += current.offsetLeft;
        current = current.offsetParent;
    }
    return actualLeft;
}

function getElementTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null){
        actualTop += current. offsetTop;
        current = current.offsetParent;
    }
    return actualTop;
}

function getBoundingClientRect(element) {
    var scrollTop = document.documentElement.scrollTop;
    var scrollLeft = document.documentElement.scrollLeft;
    if (element.getBoundingClientRect) {
        if (typeof arguments.callee.offset != "number") {
            var temp = document.createElement("div");
            temp.style.cssText = "position:absolute;left:0;top:0;"; document.body.appendChild(temp);
            arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop; document.body.removeChild(temp);
            temp = null;
        }
        var rect = element.getBoundingClientRect();
        var offset = arguments.callee.offset;
        return {
            left: rect.left + offset,
            right: rect.right + offset,
            top: rect.top + offset,
            bottom: rect.bottom + offset
        };
    } else {
        var actualLeft = getElementLeft(element);
        var actualTop = getElementTop(element);
        return {
            left: actualLeft - scrollLeft,
            right: actualLeft + element.offsetWidth - scrollLeft,
            top: actualTop - scrollTop,
            bottom: actualTop + element.offsetHeight - scrollTop
        }
    }
}

5.判断元素是否在可视区域

知道了元素的大小以及所位于的区域外,咱们能够作些什么呢?咱们能够经过上面学到的知识点来检测元素是否在可视区域,再说大一点,这也是懒加载图片的实现原理。

5.1 第一种方法

公式: el.offsetTop - document.documentElement.scrollTop <= viewPortHeight

function isInViewPortOfOne (el) {
    // viewPortHeight 兼容全部浏览器写法
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const offsetTop = el.offsetTop
    const scrollTop = document.documentElement.scrollTop
    const top = offsetTop - scrollTop
    console.log('top', top)
     // 这里有个+100是为了提早加载+ 100
    return top <= viewPortHeight + 100
}

5.2 第二种方法

公式: el.getBoundingClientReact().top <= viewPortHeight

其实, el.offsetTop - document.documentElement.scrollTop = el.getBoundingClientRect().top, 利用这点,咱们能够用下面代码代替方法一

function isInViewPortOfTwo (el) {
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
    console.log('top', top)
    return top  <= viewPortHeight + 100
}

5.3 第三种方法

公式: intersectionRatio > 0 && intersectionRatio <= 1

// 定义一个交叉观察器
const io = new IntersectionObserver(ioes => {
    ioes.forEach(ioe => {
        const el = ioe.target
        const intersectionRatio = ioe.intersectionRatio
        if (intersectionRatio > 0 && intersectionRatio <= 1) {
            loadImg(el)
            io.unobserve(el)
        }
         el.onload = el.onerror = () => io.unobserve(el)
    })
})
// 执行交叉观察器
function isInViewPortOfThree (el) {
    io.observe(el)
}

5.4 兼容性比较

在兼容性方面,咱们知道越原始的方法兼容性是最好的,那么第二种方法和第三种方法可否代替第三种方法呢?咱们来看看。

getBoundingClientReact

IntersectionObserver

从caniuse的数据来看,getBoundingClientReact的适配状况很乐观了。

因此,若是在移动端和桌面端都要作兼容适配的话,方法二彻底能够代替方法一进行适配了。若是仅仅是桌面端适配(好比运营后台),咱们或许能够尝试下新的IntersectionObserver方法,毕竟IntersectionObserver里面还有更多丰富的功能等着咱们去体验呢。

5.5 实例

有时,咱们但愿某些静态资源(好比图片),只有用户向下滚动,它们进入视口时才加载,这样能够节省带宽,提升网页性能。这就叫作"惰性加载",也称为懒加载。

惰性加载预览DEMO(放入你的本地图片便可经过更换不一样方法实现懒加载)

------------------------------ 华丽的分割线 ---------------------------

ps: 有公众号大佬想减轻平常写推文的时间消耗,收购技术原创文章的稿子放在本身公众号展现推送的,能够联系找我合做哈。(在公众号里能够查到个人联系方式)

关于我

公众号二维码

参考

相关文章
相关标签/搜索