如何时时判断元素是否进入当前视区

更新日期: 2020-12-22阅读: 1.6k标签: 元素

1. 使用元素位置判断元素是否在当前视区

这种方法实现起来比较简单, 我们一步一步来。

首先:编写一个 util 函数 isVisible,它将仅接收一个参数,即 element。

export const isVisible = (el) => { };

使用 getBoundingClientRect 获取该元素的位置

const rect = el.getBoundingClientRect();

将找到窗口的高度和宽度

const vWidth = window.innerWidth || document.documentElement.clientWidth;

const vHeight = window.innerHeight || document.documentElement.clientHeight;

再编写一个函数,该函数基本上将接收 x 和 y 点,并使用elementFromPoint函数返回元素。

const elementFromPoint = function (x, y) { 
  return document.elementFromPoint(x, y); 
};

检查元素是否在窗口内:

// Return false if it's not in the viewport
if (rect.right < 0 
  || rect.bottom < 0
  || rect.left > vWidth 
  || rect.top > vHeight) { 
  return false; 
}

边界检查:

// Return true if any of its four corners are visible
 return (
   el.contains(elementFromPoint(rect.left, rect.top))
   || el.contains(efp(rect.right, rect.top))
   || el.contains(efp(rect.right, rect.bottom))
   || el.contains(efp(rect.left, rect.bottom))
 );

完整代码

export const isVisible = (el) => {
  const rect = el.getBoundingClientRect();
  const vWidth = window.innerWidth || document.documentElement.clientWidth;
  const vHeight = window.innerHeight || document.documentElement.clientHeight;
  const efp = function (x, y) { return document.elementFromPoint(x, y); };

  // Return false if it's not in the viewport
  if (rect.right < 0 || rect.bottom < 0
            || rect.left > vWidth || rect.top > vHeight) { return false; }

  // Return true if any of its four corners are visible
  return (
    el.contains(
      elementFromPoint(rect.left, rect.top))
      || el.contains(efp(rect.right, rect.top))
      || el.contains(efp(rect.right, rect.bottom))
      || el.contains(efp(rect.left, rect.bottom))
  );
};

用法:

import { isVisible } from '../utils';
// ...
const ele = document.getElementById(id);
return isVisible(ele);

逻辑并不复杂,不过多介绍。


2. 使用 Intersection Observer 判断元素是否在当前视区

Intersection Observer 是一种更高效的方式。

为什么这么说呢?

比如说,你想跟踪 dom 树里的一个元素,当它进入可见窗口时得到通知。

可以通过绑定 scroll 事件或者用一个定时器,然后再回调函数中调用元素的 getBoundingClientRect 获取元素位置实现这个功能。

但是,这种实现方式性能极差。

因为每次调用 getBoundingClientRect 都会强制浏览器重新计算整个页面的布局,可能给你的网站造成相当大的闪烁。

如果你的站点被加载到一个 iframe 里,而你想要知道用户什么时候能看到某个元素,这几乎是不可能的。

单原模型(Single Origin Model)和浏览器不会让你获取 iframe 里的任何数据

这对于经常在 iframe 里加载的广告页面来说是一个很常见的问题。

IntersectionObserver 就是为此而生的。

它让检测一个元素是否可见更加高效。

IntersectionObserver 能让你知道一个被观测的元素什么时候进入或离开浏览器的可见窗口。

使用 IntersectionObserver 也非常简单, 两步走:

创建 IntersectionObserver

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    // ...
    console.log(entry);
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
}, options);

将元素传递给 IntersectionObserver

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

entries 参数会被传递给你的回调函数,它是一个 IntersectionObserverEntry 对象数组。

每个对象都包含更新过的交点数据针对你所观测的元素之一。

从输出最有用的特性是:

isIntersecting

target

intersectionRect

isIntersecting:当元素与默认根(在本例中为视口)相交时,将为true.

target:这将是我们将要观察的页面上的实际元素

intersectionRect: intersectionRect 告诉元素的可见部分。这将包含有关元素,其高度,宽度,视口位置等的信息。

在线 Demo: https://codepen.io/


更多有用的属性

现在我们知道: 当被观测的元素部分进入可见窗口时会触发回调函数一次,当它离开可见窗口时会触发另一次。

这样就回答了一个问题:元素 X 在不在可见窗口里。

但在某些场合,仅仅如此还不够。

这时候就轮到 threshold 登场了。

它允许你定义一个 intersectionRatio 临界值。

每次 intersectionRatio 经过这些值的时候,你的回调函数都会被调用。

threshold 的默认值是[0],就是默认行为。

如果我们把 threshold 改为[0, 0.25, 0.5, 0.75, 1],当元素的每四分之一变为可见时,我们都会收到通知。

还一个属性没在上文列出: rootMargin .

rootMargin 允许你指定到跟元素的距离,允许你有效的扩大或缩小交叉区域面积。

这些 margin 使用 css 风格的字符串,例如: 10px 20px 30px 40px,依次指定上、右、下、左边距。

new IntersectionObserver(entries => {
  // do something with entries
}, {
  // options
  // 用于计算相交区域的根元素
  // 如果未提供,使用最上级文档的可见窗口
  root: null,
  // 同 margin,可以是 1、2、3、4 个值,允许时负值。
  // 如果显式指定了跟元素,该值可以使用百分比,即根元素大小的百分之多少。
  // 如果没指定根元素,使用百分比会出错。
  rootMargin: "0px",
  // 触发回调函数的临界值,用 0 ~ 1 的比率指定,也可以是一个数组。
  // 其值是被观测元素可视面积 / 总面积。
  // 当可视比率经过这个值的时候,回调函数就会被调用。
  threshold: [0],
});

有一点要注意:IntersectionObserver 不是完美精确到像素级别,也不是低延时性的。

使用它实现类似依赖滚动效果的动画注定会失败。

因为回调函数被调用的时候那些数据——严格来说已经过期了。


3. 实例:懒加载(lazy load)

有时,我们希望某些静态资源(比如图片),只有用户向下滚动,它们进入视口时才加载,这样可以节省带宽,提高网页性能。这就叫做"惰性加载"。

有了 IntersectionObserver api,实现起来就很容易了。

function query(selector) {
  return Array.from(document.querySelectorAll(selector));
}

const observer = new IntersectionObserver(
  function(changes) {
    changes.forEach(function(change) {
      var container = change.target;
      var content = container.querySelector('template').content;
      container.appendChild(content);
      observer.unobserve(container);
    });
  }
);

query('.lazy-loaded').forEach(function (item) {
  observer.observe(item);
});

上面代码中,只有目标区域可见时,才会将模板内容插入真实 DOM,从而引发静态资源的加载。


4. 实例:无限滚动

无限滚动(infinite scroll)的实现也很简单:

const intersectionObserver = new IntersectionObserver(
  function (entries) {
    // 如果不可见,就返回
    if (entries[0].intersectionRatio <= 0) return;
    loadItems(10);
    console.log('Loaded new items');
  });

// 开始观察
intersectionObserver.observe(
  document.querySelector('.scrollerFooter')
);

无限滚动时,最好在页面底部有一个页尾栏。

一旦页尾栏可见,就表示用户到达了页面底部,从而加载新的条目放在页尾栏前面。

这样做的好处是:

不需要再一次调用 observe() 方法, 现有的 IntersectionObserver 可以保持使用。


5. 实用 npm 包推荐

和今天话题相关的npm 包推荐的是:react-visibility-sensor

地址: https://www.npmjs.com/package...

用法也很简答:

import VisibilitySensor from "react-visibility-sensor";
 
function onChange (isVisible) {
  console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
}
 
function MyComponent (props) {
  return (
    <VisibilitySensor onChange={onChange}>
      <div>...content goes here...</div>
    </VisibilitySensor>
  );
}

在线demo :https://codesandbox.io/


结尾

内容大概就这么多, 希望对大家有所启发。

我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:sskk2,邀请大家一同入驻:https://www.oschina.net/

链接: https://www.fly63.com/article/detial/9970

使用原生js来控制、修改CSS伪元素的方法总汇, 例如:before和:after

在网页中,如果需要使用辅助性/装饰性的内容的时候,这就需要使用伪元素了。在使用伪元素的时候,会发现js并不真能直接控制它,这篇文章主要就介绍下如果间接的控制、修改css中伪元素的方法

CSS隐藏元素的五种方法

用css隐藏页面元素有许多种方法。1、opacity:0;2、visibility:hidden;3、diaplay:none;4、position:absolute;5、clip-path。大家可以根据具体情况选择适合的方法来隐藏元素

CSS隐藏页面元素常用方法_不同场景下使用CSS隐藏元素

使用 CSS 让元素不可见的方法很多,剪裁、定位到屏幕外、明度变化等都是可以的。虽然它们都是肉眼不可见,但背后却在多个维度上都有差别

js动态生成html元素并为元素追加属性

动态生成HTML元素的方法有三种:document.createElement()创建元素,再用appendChild( )添加、使用innerHTML直接将元素添加到指定节点、jQuery创建节点...

原生js删除元素

通过id删除;通过class获取元素;清空一个元素,即删除一个元素的所有子元素 ;原理很简单,就是不断的判断要清空的div还有没有子节点,有的话就删除一个子节点(这里是它的首个子节点),直到删除完毕为止。

原生JS如何获取当前元素属于父元素第几个子元素

我们经常通过document.getElementById 方法来获取到一个元素,这个时候我们经常需要有一个需求,那就是如何判断这个元素在父元素中的位置。原生JS有一个常见的小技巧那就是通过元素的previousSibling 属性,额外需要注意的是该属性会遍历text节点,即回车键。

在js中获取页面元素的属性值时,弱类型导致的诡异事件踩坑记录

前几天写一个js的时候遇到一个非常诡异的事情,这个问题是这样的,我要获取一个页面的DOM元素的val值,判断这个值是否比某个变量大,这个需求原先数字最大也就是10,现在要改了,可能会更多,这个时候我发现比较大小的判断就出了问题:

JS 创建元素的三种方法

动态创建元素一 document.write()body标签中就会插入但是这种方法几乎不用,因为这回影响页面的布局,甚至会将页面原来的内容冲刷掉,从而只显示输出内容;动态创建元素二 innerHTML

去除inline-block元素间的间距

真正意义上的inline-block水平呈现的元素间,换行显示或者空格隔开的情况下会有间距,这是因为浏览器在解析时,会将换行等读取成一个空格导致。

什么是可替换元素?

请问什么是可替换元素和非可替换元素,它们的差异是什么?并举例说明。前端面试中 HTML 的题目本来就最少,而且并不难,翻来覆去也就那几样。我们之前已经谈到过最经典的 HTML 语义化 ,今天就借此机会来谈谈可替换元素。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!