关于判断dom元素是否在视口中显示

之前项目中有遇到这么一个动画需求,页面滚动到视口中的时候控制dom元素展示动画,离开视口时页面中的dom元素位置复原。

一开始的思路是想通过offset属性递归查询dom元素的位置,进而判断当前dom元素是否显示在视口当中,再做动画需求。

// 获取元素的纵坐标(相对于窗口)
 function getTop(e){
   var offset=e.offsetTop;
   if(e.offsetParent!=null) offset+=getTop(e.offsetParent);
   return offset;
 }


// 获取元素的横坐标(相对于窗口)
 function getLeft(e){
   var offset=e.offsetLeft;
   if(e.offsetParent!=null) offset+=getLeft(e.offsetParent);
   return offset;
 }

 

毫无疑问,递归获取dom元素位置无疑是在大大的浪费浏览器性能,现代浏览器给我们操作dom预留了这么一个方法:getBoundingClientRect,该方法用于获取dom元素的width、height、top、right、bottom、left等属性,该方法最早是在IE浏览器中出现的(给IE颁个奖)且只支持后四个位置属性,后来被各大浏览器广泛支持并得到了发展,加上了元素的width和height属性。

var box = document.getElementById("box");
var pos = box.getBoundingClientRect();

// bottom: 790
// height: 36
// left: 452
// right: 1186.984375
// top: 754
// width: 734.984375
// x: 452
// y: 754

值得注意的是:bottom是元素底部相对于窗口顶部的距离,而不是像css里面position的bottom相对于窗口底部,同理,rihgt属性是元素最右边相对于窗口左边的距离。

 

到了这里以后需求应该好实现了吧,但是我又想还有没有更好的方式呢,然后我就查到了这么一个构造方法:IntersectionObserver。

IntersectionObserver构造方法是chrome率先提供的一个的API,具体用法如下

var io = new IntersectionObserver(callback, option);

// 开始观察
io.observe(document.getElementById('example'));

// 停止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();

上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点。

上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。

io.observe(elementA);
io.observe(elementB);

callback 参数

目标元素的可见性变化时,就会调用观察器的回调函数callback

callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

var io = new IntersectionObserver(
  entries => {
    console.log(entries);
  }
);

callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

IntersectionObserverEntry对象提供目标元素的信息,一共有六个属性。

{
  time: 3893.92,
  rootBounds: ClientRect {
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
  },
  boundingClientRect: ClientRect {
     // ...
  },
  intersectionRect: ClientRect {
    // ...
  },
  intersectionRatio: 0.54,
  target: element
}

每个属性的含义如下。

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,是一个 DOM 节点对象
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,完全可见时为1,完全不可见时小于等于0

Option参数:

1. threshold 属性

threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }
);

用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数

 2. root 属性,rootMargin 属性

很多时候,目标元素不仅会随着窗口滚动,还会在容器里面滚动(比如在iframe窗口里滚动)。容器内滚动也会影响目标元素的可见性,参见本文开始时的那张示意图。

IntersectionObserver API 支持容器内滚动。root属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。

var opts = { 
  root: document.querySelector('.container'),
  rootMargin: "500px 0px" 
};

var observer = new IntersectionObserver(
  callback,
  opts
);

上面代码中,除了root属性,还有rootMargin属性。后者定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小。它使用CSS的定义方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。

这样设置以后,不管是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器。

 

嗯~,至此看似完美的通过IntersectionObserver API实现了这个动画需求,功能做完自测的时候发现在某些浏览器上没有动画效果,一查发现是IntersectionObserver存在兼容性问题 = =,把这茬给忘了,最后还是老老实实的用回了第二种方式。

 

借此记录一下学习到的这个新的API,学无止境,加油!

参考链接:阮一峰的网络日志https://www.jianshu.com/p/7c06669ed98e

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值