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

方法1:getBoundingClientRect

用法

js 复制代码
let domRect = dom.getBoundingClientRect();
js 复制代码
DOMRect:{
  x/left:视图原点(左上角)距离dom左边框距离,
  y/top:视图原点(左上角)距离dom上边框距离,
    right:视图原点(左上角)距离dom右边框距离,
    bottom:视图原点(左上角)距离dom底边框距离,
    width:dom的宽度,标准盒模型,width = 宽度+padding+border;怪异盒模型,width = 设置的宽度,
    height:dom的高度,
}

所以我们可以根据DOMRect的中的各个属性来判断dom是否在可视区域内。

eg:

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>getBoundingClientRect可视区域</title>
  <style>
    * {
      margin: 0
    }
    .circle-wrap {
      position: fixed;
      top: 50px;
      right: 50px;
      padding: 10px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      width: 140px;
      background: #fff;
    }

    .circle-wrap .circle {
      display: inline-block;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: red;
    }

    .card-wrap {
      height: 2000px;
      width: 3000px;
      margin-top: 100px;
    }

    .card-wrap .card {
      width: 200px;
      height: 200px;
      padding: 20px;
      box-sizing: border-box;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      margin-top: 500px;
      margin-left: 500px;
    }
  </style>
</head>

<body>
  <div class="circle-wrap">
    <span>当下方的卡片在可视区域内,我的圈圈是绿色,否则是红色</span>
    <span class="circle"></span>
  </div>
  <div class="card-wrap">
    <div class="card">我是一个卡片</div>
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js"></script>
  <script>
    const card = document.querySelector(".card");
    const circle = document.querySelector(".circle");
    const getBoundingClientRectJudge = () => {
      let domRect = card.getBoundingClientRect();
      console.log(domRect)
      let ch = document.documentElement.clientHeight;
      let cw = document.documentElement.clientWidth;
      let isInsert = true;
      if (domRect.bottom < 0 || domRect.top > ch || domRect.right < 0 || domRect.left > cw) {
        isInsert = false;
      }
      let background = null
      if (isInsert) {
        background = "green"
      } else {
        background = "red"
      }
      circle.style.background = background
    }
    window.addEventListener("scroll", _.throttle(getBoundingClientRectJudge, 500))
    getBoundingClientRectJudge()
  </script>
</body>

</html>

效果图:

问题

getBoundingClientRect并不能满足所有情况,甚至说,它只能满足一种情况的判断,那就是需要判断的那个dom节点,它只身处在一个滚动条的情况下。什么意思呢,就是它所有的祖先节点,加起来的滚动条只有1个,如果它父节点有滚动条,父父节点也有滚动条,那这种方法就不好用了,请看示例:

下面展示的结果是错误的,圈圈应该是红色才对。

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>getBoundingClientRect可视区域</title>
  <style>
    * {
      margin: 0
    }

    body {
      height: 2000px;
      width: 3000px;
    }

    .circle-wrap {
      position: fixed;
      top: 50px;
      right: 50px;
      padding: 10px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      width: 140px;
      background: #fff;
    }

    .circle-wrap .circle {
      display: inline-block;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: red;
    }

    .card-wrap {
      height: 400px;
      width: 600px;
      margin-top: 100px;
      margin-left: 100px;
      border: 1px solid green;
      overflow: auto;
    }

    .card-wrap .card {
      width: 200px;
      height: 200px;
      padding: 20px;
      box-sizing: border-box;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      margin-top: 500px;
      margin-left: 500px;
    }

    .head {
      width: 100%;
      height: 100px;
      background: pink;
      /* position: fixed; */
      top: 0;
      left: 0;
    }
  </style>
</head>

<body>
  <!-- <div class="head"></div> -->
  <div class="circle-wrap">
    <span>当下方的卡片在可视区域内,我的圈圈是绿色,否则是红色</span>
    <span class="circle"></span>
  </div>
  <div class="card-wrap">
    <div class="card">我是一个卡片</div>
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js"></script>
  <script>
    const card = document.querySelector(".card");
    const circle = document.querySelector(".circle");
    const getBoundingClientRectJudge = () => {
      let domRect = card.getBoundingClientRect();
      let ch = document.documentElement.clientHeight;
      let cw = document.documentElement.clientWidth;
      let isInsert = true;
      if (domRect.bottom < 0 || domRect.top > ch || domRect.right < 0 || domRect.left > cw) {
        isInsert = false;
      }
      let background = null
      if (isInsert) {
        background = "green"
      } else {
        background = "red"
      }
      circle.style.background = background
    }
    window.addEventListener("scroll", _.throttle(getBoundingClientRectJudge, 500))
    getBoundingClientRectJudge()
  </script>
</body>

</html>

由此,我们引出第二种方法,IntersectionObserver。

方法2:IntersectionObserver

用法

eg:

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>getBoundingClientRect可视区域</title>
  <style>
    * {
      margin: 0
    }

    body {
      height: 2000px;
      width: 3000px;
    }

    .circle-wrap {
      position: fixed;
      top: 50px;
      right: 50px;
      padding: 10px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      width: 140px;
      background: #fff;
    }

    .circle-wrap .circle {
      display: inline-block;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: red;
    }

    .card-wrap {
      height: 400px;
      width: 600px;
      margin-top: 100px;
      margin-left: 100px;
      border: 1px solid green;
      overflow: auto;
    }

    .card-wrap .card {
      width: 200px;
      height: 200px;
      padding: 20px;
      box-sizing: border-box;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      margin-top: 500px;
      margin-left: 500px;
    }
    .head{
      width: 100%;
      height: 100px;
      background: pink;
      /* position: fixed; */
      top:0;
      left: 0;
    }
  </style>
</head>

<body>
  <!-- <div class="head"></div> -->
  <div class="circle-wrap">
    <span>当下方的卡片在可视区域内,我的圈圈是绿色,否则是红色</span>
    <span class="circle"></span>
  </div>
  <div class="card-wrap">
    <div class="card">我是一个卡片</div>
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js"></script>
  <script>
    const card = document.querySelector(".card");
    const circle = document.querySelector(".circle");
    const observer = new IntersectionObserver((changes) => {
      // changes是数组
      changes.forEach(one => {
        console.log(one)
        let isIntersecting = one.isIntersecting
        let background = null
        if (isIntersecting) {
          background = "green"
        } else {
          background = "red"
        }
        circle.style.background = background
      })
    })
    observer.observe(card)
    // console.log(observer)
    /* 取消观察特定的元素:observer.unobserve(dom) */
    /* 对象停止监听目标 observer.disconnect() */
  </script>
</body>

</html>

补充说明

IntersectionObserver可以传root,进一步判断dom是相对于哪个节点,来判断是否在可视区域内,默认rootdocument

js 复制代码
const observer = new IntersectionObserver((changes) => {
      console.log(changes)
      changes.forEach(one => {
        console.log(one)
        let isIntersecting = one.isIntersecting
        let background = null
        if (isIntersecting) {
          background = "green"
        } else {
          background = "red"
        }
        circle.style.background = background
      })
    },{root:document.querySelector(".card-wrap")})

方法3:offsetTop、scrollTop

说明

  • offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离,其他方向相同
  • offsetWidth:元素两端算上外边框的宽度,其他方向相同
  • scrollLeftscrollTop:既可以确定当前元素的滚动状态,也可以设置元素的滚动位置
  • scrollWidthscrollHeight:确定元素内容的实际大小
  • clientWidth:元素内容区宽度加上左右内边距宽度,即clientWidth = content + padding
  • clientHeight:元素内容区高度加上上下内边距高度,即clientHeight = content + padding

使用

公式:

js 复制代码
0 <= el.offsetTop - document.documentElement.scrollTop <= viewPortHeight

写法:

js 复制代码
function isInViePortOfOne(el){
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight||document.body.clientHeight
    const offsetTop = el.offsetTop;
    const scollTop = document.documentElement.scrollTop
    const top = offsetTop - scollTop;
    return top <= viewPortHeight && top >= 0
}
相关推荐
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂2 小时前
工程化实战内功修炼测试题
前端·javascript
红中马喽5 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新6 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
Dread_lxy7 小时前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
奔跑草-8 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与8 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲8 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR8 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式