☂️如何判断元素是否在可视区域内?

223 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

🎈大家好,我是橙橙子,新人初来乍到,请多多关照~

📝小小的前端一枚,分享一些日常的学习和项目实战总结~

😜如果本文对你有帮助的话,帮忙点点赞呀!ღ( ´・ᴗ・` )比心~

一、用途

可视区域就是我们浏览网页的设备肉眼可见的区域

在开发中,我们经常需要判断目标元素是否在可视区域内,从而实现一些常用的功能,例如:

  • 图片的懒加载
  • 列表的无限滚动

二、实现

判断一个元素是否在可视区域,常用的有三种办法:

  • offsetTop、scrollTop
  • getBoundingClientRect
  • Intersection Observer

offsetTop、scrollTop

  • offsetTop,元素的上外边框到包含元素的上内边框之间的像素距离

  • clientWidth:元素内容区宽度加上左右内边距宽度,即clientWidth = content + padding

  • clientHeight:元素内容区高度加上上下内边距高度,即clientHeight = content + padding

client元素都不包括外边距

关于scroll系列的属性:

  • scrollWidth 和 scrollHeight 主要用于确定元素内容的实际大小
  • scrollLeft 和 scrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置
    • 垂直滚动 scrollTop > 0
    • 水平滚动 scrollLeft > 0
  • 将元素的 scrollLeft 和 scrollTop 设置为 0,可以重置元素的滚动位置
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
    return top <= viewPortHeight
}

getBoundingClientRect

返回值是一个 DOMRect对象,拥有lefttoprightbottomxywidth, 和 height属性

const target = document.querySelector('.target');
const clientRect = target.getBoundingClientRect();
console.log(clientRect);

// {
//   bottom: 556.21875,
//   height: 393.59375,
//   left: 333,
//   right: 1017,
//   top: 162.625,
//   width: 684
// }

当页面发生滚动的时候,topleft属性值都会随之改变

如果一个元素在视窗之内的话,那么它一定满足下面四个条件:

  • top 大于等于 0
  • left 大于等于 0
  • bottom 小于等于视窗高度
  • right 小于等于视窗宽度

代码

function isInViewPort(element) {
  const viewWidth = window.innerWidth || document.documentElement.clientWidth;
  const viewHeight = window.innerHeight ||    document.documentElement.clientHeight;
  const {
    top,
    right,
    bottom,
    left,
  } = element.getBoundingClientRect();

  return (
    top >= 0 &&
    left >= 0 &&
    right <= viewWidth &&
    bottom <= viewHeight
  );
}

Intersection Observer

Intersection Observer 即重叠观察者,它用于判断两个元素是否重叠,因为不用进行事件的监听,性能方面相比getBoundingClientRect 会好很多

使用分为两步:创建观察者和传入被观察者

创建观察者

const options = {
  // 表示重叠面积占被观察者的比例,从 0 - 1 取值,
  // 1 表示完全被包含
  threshold: 1.0, 
  root:document.querySelector('#scrollArea') // 必须是目标元素的父级元素
};

const callback = (entries, observer) => { ....}

const observer = new IntersectionObserver(callback, options);

通过new IntersectionObserver创建了观察者 observer,传入的参数 callback 在重叠比例超过 threshold 时会被执行`

callback回调函数:

const callback = function(entries, observer) { 
    entries.forEach(entry => {
        entry.time;               // 触发的时间
        entry.rootBounds;         // 根元素的位置矩形,这种情况下为视窗位置
        entry.boundingClientRect; // 被观察者的位置举行
        entry.intersectionRect;   // 重叠区域的位置矩形
        entry.intersectionRatio;  // 重叠区域占被观察者面积的比例(被观察者不是矩形时也按照矩形计算)
        entry.target;             // 被观察者
    });
11};

传入被观察者

通过 observer.observe(target) 即可注册被观察者

const target = document.querySelector('.target');
observer.observe(target);

三、Demo

创建了一个列表,当节点滚入到可视窗口的时候,背景就会从红色变为蓝色

Html

<div class="container"></div>

css

.container {
    display: flex;
    flex-wrap: wrap;
}
.target {
    margin: 5px;
    width: 20px;
    height: 20px;
    background: red;
}

container插入元素

const $container = $(".container");

function createTargets() {
  const htmlString = new Array(1000)
    .fill('<div class="target"></div>')
    .join("");
  $container.html(htmlString);
}

使用getBoundingClientRect 方法进行判断元素是否在可视区域

function isInViewPort(element) {
    const viewWidth = window.innerWidth || document.documentElement.clientWidth;
    const viewHeight =
          window.innerHeight || document.documentElement.clientHeight;
    const { top, right, bottom, left } = element.getBoundingClientRect();

    return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;
}

监听scroll事件,判断页面上哪些元素在可视区域中,在可视区域中将背景颜色设置为blue

$(window).on("scroll", () => {
    console.log("scroll !");
    $targets.each((index, element) => {
        if (isInViewPort(element)) {
            $(element).css("background-color", "blue");
        }
    });
});

可以看到可视区域颜色会变成蓝色