每日一题:如何判断一个元素是否在可视区域中?

简介: 每日一题:如何判断一个元素是否在可视区域中?

一、用途

可视区域即我们浏览网页的设备肉眼可见的区域,如下图

f26cd200342d448291dac894bf6cf8f6.png


在日常开发中,我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值(例如 100 px),从而实现一些常用的功能,例如:

  • 图片的懒加载
  • 列表的无限滚动
  • 计算广告元素的曝光情况
  • 可点击链接的预加载


二、实现方式

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

  • offsetTop、scrollTop
  • getBoundingClientRect
  • Intersection Observer


offsetTop、scrollTop

offsetTop,元素的上外边框至包含元素的上内边框之间的像素距离,其他offset属性如下图所示:

90c0c0bbf1fb4480a52586907184d100.png


下面再来了解下clientWidth、clientHeight:

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


这里可以看到client元素都不包括外边距

最后,关于scroll系列的属性如下:

  • scrollWidthscrollHeight 主要用于确定元素内容的实际大小
  • scrollLeftscrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置
  • 垂直滚动 scrollTop > 0
  • 水平滚动 scrollLeft > 0
  • 将元素的 scrollLeftscrollTop 设置为 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对象,拥有left, top, right, bottom, x, y, width, 和 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
// }


属性对应的关系图如下所示:

098e4661acfe41bda7b8fdc325e38360.png


当页面发生滚动的时候,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回调函数常用属性如下:

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


传入被观察者

通过 observer.observe(target) 这一行代码即可简单的注册被观察者

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


三、案例分析

实现:创建了一个十万个节点的长列表,当节点滚入到视窗中时,背景就会从红色变为黄色

Html结构如下:

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


css样式如下:

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


container插入1000个元素

const $container = $(".container");
// 插入 100000 个 <div class="target"></div>
function createTargets() {
  const htmlString = new Array(100000)
    .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事件,判断页面上哪些元素在可视区域中,如果在可视区域中则将背景颜色设置为yellow

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


通过上述方式,可以看到可视区域颜色会变成黄色了,但是可以明显看到有卡顿的现象,原因在于我们绑定了scroll事件,scroll事件伴随了大量的计算,会造成资源方面的浪费

下面通过Intersection Observer的形式同样实现相同的功能

首先创建一个观察者

const observer = new IntersectionObserver(getYellow, { threshold: 1.0 });


getYellow回调函数实现对背景颜色改变,如下:

function getYellow(entries, observer) {
    entries.forEach(entry => {
        $(entry.target).css("background-color", "yellow");
    });
}


最后传入观察者,即.target元素

$targets.each((index, element) => {
    observer.observe(element);
});


可以看到功能同样完成,并且页面不会出现卡顿的情况



目录
相关文章
|
机器学习/深度学习 应用服务中间件 Linux
API一键搭建智能时光相册,记录你的美
API时代,要搭建一个云相册,就相对来说简单很多,或者说一个开发人员就可以快速实现,并且还能具备智能分析识别、归类、搜索等功能齐全的智能云相册。
4408 0
|
9月前
|
JSON 缓存 程序员
玩转HarmonyOS NEXT网络请求:从新手到高手的实战秘籍
本文以通俗易懂的方式讲解了HarmonyOS网络请求的核心知识,从基础概念到实战技巧,再到进阶优化,帮助开发者快速上手。通过“点外卖”的类比,形象解释了HTTP请求方法(如GET、POST)和JSON数据格式的作用。同时,提供了封装工具类的示例代码,简化重复操作,并分享了常见问题的解决方法(如权限配置、参数格式、内存泄漏等)。最后,还探讨了如何通过拦截器、缓存机制和重试机制提升请求功能。无论你是新手还是进阶开发者,都能从中受益,快动手实现一个新闻App试试吧!
545 5
|
存储 开发工具 git
Git日常问题: 什么是LFS?及其错误解决办法
Git LFS(Git Large File Storage)是Git的一个扩展,用于管理大型文件,通过将大文件的实际内容存储在远程服务器上,而Git仓库中只保留一个轻量级的文本指针,从而加速仓库操作的速度并减小仓库大小。当遇到Git LFS相关错误时,通常需要安装Git LFS工具并按照官方文档进行配置。
1426 2
Git日常问题: 什么是LFS?及其错误解决办法
|
缓存 前端开发 JavaScript
前端性能优化:打造流畅用户体验的秘籍
【10月更文挑战第20天】前端性能优化:打造流畅用户体验的秘籍
332 3
|
关系型数据库 数据库 数据安全/隐私保护
使用 Docker 搭建属于自己的个人相册,让美好不会丢失!
使用 Docker 搭建属于自己的个人相册,让美好不会丢失!
|
JSON JavaScript 前端开发
vue中使用echarts实现省市地图绘制,根据数据在地图上显示柱状图信息,增加涟漪特效动画效果
vue中使用echarts实现省市地图绘制,根据数据在地图上显示柱状图信息,增加涟漪特效动画效果
4622 0
|
移动开发 JavaScript
vue自定义指令监听元素是否进入父元素视窗内
一般涉及 dom 的操作,我们都可以通过自定义指令来实现,比如点击文本直接复制到粘贴板、按钮权限的判断(无权限时隐藏或禁用)...这一点一般通过指令钩子函数的第一个参数 el 就能实现
|
前端开发
边框也疯狂:CSS创造令人瞩目的流光闪烁效果!
边框也疯狂:CSS创造令人瞩目的流光闪烁效果!
【亲测有效】解决url中&times会被转成×的问题 &timestamp=打印出来是 ×tamp=
【亲测有效】解决url中&times会被转成×的问题 &timestamp=打印出来是 ×tamp=
407 0
|
JavaScript 前端开发 测试技术
JavaScript中的switch-case:与if-else比较
【4月更文挑战第7天】探索JavaScript中的switch-case语句,用于多分支选择,替代嵌套if-else。理解其概念、应用及与if-else的区别。示例展示如何根据数字显示星期和处理不同事件。注意使用break避免意外穿透,利用const或let声明局部变量。在适当场景下,switch-case提供更清晰的代码结构,但面对复杂逻辑,if-else可能是更好的选择。了解这些,能提升代码编写效率和可读性。
1223 0