IntersectionObserver是什么?

简介: IntersectionObserver是什么?

image.png


  • IntersectionObserver概览
  • IntersectionObserver构造器
  • IntersectionObserver方法
  • IntersectionObserver懒加载(vue单文件组件简版)
  • IntersectionObserver吸顶(vue单文件组件简版)
  • IntersectionObserver触底(vue单文件组件简版)
  • IntersectionObserver懒加载、吸顶、触底综合(vue单文件组件实战版)
  • 基于Intersection的开源工具 Scrollama
  • 总结
  • 参考资料


IntersectionObserver概览


  • IntersectionObserver提供了一个方式去异步观察 有一个祖先element或者top-level document viewport的目标element 的交叉变化。
  • 祖先元素或者viewport被当做root节点。
  • 当IntersectionObserver创建出的时候,它会被配置到监听root内部的给定visibility的变化。
  • 一旦IntersectionObserver创建出来,它的配置是不能变的。 所以一个observer object只能用来监测一个指定visibility值的变化。
  • 虽然只能一对一去watch ratio,但是可以在同一个observer中watch多个target elements。也就是说一个visibility ratio可以检测多个不同的elements。


IntersectionObserver构造器


var observer = new IntersectionObserver(callback[, options]);

  • 和其他构造器一样,创建并返回一个新的Intersection对象。
  • rootMargin如果指定一个特殊值,是为了确保语法是否正确
  • 阀值是为了确保值在0.0到1.0之间,threshold会按照升序排列。若threshold是空,值为[0.0]。


参数


  • callback 当目标元素的透明度穿过设定的threshold值时,函数会被调用。callback接受两个 参数。
  • entries 传入各个threshold值的数组,比该阀值指定的百分比更明显或者更不明显。
  • observer 调用callback的observer实例。
  • options 若options没设置。observer使用document的viewport作为root,没有margin,0%的threshold(意味着即使有1 px的变化也会触发回调)
  • root 被当做viewport的元素。
  • rootMargin 语法是"0px 0px 0px 0px"
  • threshold 指明被监测目标总绑定盒模型的交叉区域ratio,值在0.0到1.0之间;0.0意味着即使是1px也会被当做可见的。1.0意味着整个元素是可见的。默认threshold值是0.0。


IntersectionObserver方法


  • IntersectionObserver.disconnect() 停止observe一个目标。
  • IntersectionObserver.observe() 告诉IntersectionObserver一目标元素去observe。
  • IntersectionObserver.takeRecords() 返回包含所有observe的对象一个数组。
  • IntersectionObserver.unobserve() 取消observe一个目标对象。


示例


下面的例子在threshold值变化在10%以上时触发myObserverCallback。

let observer = new IntersectionObserver(myObserverCallback, { "threshold": 0.1 });


IntersectionObserver懒加载(vue单文件组件简版)


<template>
  <div>
    <img v-for="(image, i) in images" :key="i" src :data-img-url="image" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {
    const images = document.querySelectorAll('img');
    const observerLazyLoad = new IntersectionObserver((entries) => {
      entries.forEach((item) => {
        if (item.isIntersecting) {
          item.target.src = item.target.dataset.imgUrl;
        }
      });
    });
    images.forEach((image) => {
      observerLazyLoad.observe(image);
    });
  },
};
</script>
<style lang="scss" scoped>
img {
  display: block;
  height: 500px;
  margin: 30px;
}
</style>


image.gif



IntersectionObserver吸顶(vue单文件组件简版)



<template>
  <div>
    <p class="fixed-top-helper"></p>
    <p class="fixed-top-reference"></p>
    <header>头部</header>
    <main>
      <img v-for="(image, i) in images" :key="i" :src="image" />
    </main>
  </div>
</template>
<script>
export default {
  data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {
    const header = document.querySelector('header');
    const fixedTopReference = document.querySelector('.fixed-top-reference');
    fixedTopReference.style.top = `${header.offsetTop}px`;
    const observerFixedTop = new IntersectionObserver((entries) => {
      entries.forEach((item) => {
        if (item.boundingClientRect.top < 0) {
          header.classList.add('fixed');
        } else {
          header.classList.remove('fixed');
        }
      });
    });
    observerFixedTop.observe(fixedTopReference);
  },
};
</script>
<style lang="scss" scoped>
.fixed-top-helper {
  height: 1px;
  background: #ccc;
}
header {
  background: #ccc;
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}
main {
  img {
    display: block;
    height: 500px;
    margin: 30px;
  }
}
</style>

1460000021085605.gif



注意事项:

  • fixedTopReference是为了避免缓慢移动时add remove .fixed死循环,死循环的结果是抖动
  • fixedTopHelper是为了避免被吸顶元素没有上一个sibling元素(也就是说被吸顶元素是最上层元素)时,避免缓缓移动时add remove .fixed死循环抖动,特殊引入的标签,需要设置1个px的height
  • fixedTopHelper需要与被吸顶元素保持样式一致,以确保好的用户体验。例如在本例中将其background设置为#ccc,很好的做到了隐藏


吸顶抖动

1460000021085609.gif


IntersectionObserver触底(vue单文件组件简版)



<template>
  <div>
    <main>
      <img v-for="(image, i) in images" :key="i" src="image" />
    </main>
    <footer>底部</footer>
  </div>
</template>
<script>
export default {
  data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {
    const footer = document.querySelector('footer');
    const observerTouchBottom = new IntersectionObserver((entries) => {
      entries.forEach((item) => {
        if (item.isIntersecting) {
          setTimeout(() => {
            console.log('滚动到了底部,可以发request请求数据了');
          }, 2000);
        }
      });
    });
    observerTouchBottom.observe(footer);
  },
};
</script>
<style lang="scss" scoped>
main {
  img {
    display: block;
    height: 500px;
    margin: 30px;
  }
}
footer {
  background: #ccc;
}
</style>

image.gif



IntersectionObserver懒加载、吸顶、触底综合(vue单文件组件实战版)


上面的例子是为了脱离框架更好的揭示IntersectionObserver的用法本质,如果在实际项目中使用,还需要考虑一些其他问题。


考虑内容如下:


  • 对象拆分,下面拆分出lazyLoad,touchFooter,stickHeader三个对象并新建target和observer来分别标识被监听者和监听者
  • 方法拆分,摒弃全部在mounted方法中变量的定义和赋值操作,很清晰的拆分出createLazyLoadObserver,createTouchFooterObserver,createStickHeaderObserver三个方法
  • 取消监听,新建unobserveAllIntersectionObservers方法,在beforeDestory生命周期内,调用IntersectionObserver的disconnect(),unbserve(target)取消监听目标对象


<template>
  <div>
    <p class="fixed-top-helper"></p>
    <p class="fixed-top-reference"></p>
    <header>头部</header>
    <main>
      <img v-for="(image, i) in images" :key="i" src :data-img-url="image" />
    </main>
    <footer>底部</footer>
  </div>
</template>
<script>
const images = [
  'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
  'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
  'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
];
export default {
  data() {
    return {
      images,
      lazyLoad: {
        target: null,
        observer: null,
      },
      touchFooter: {
        target: null,
        observer: null,
      },
      stickHeader: {
        target: null,
        reference: null,
        observer: null,
      },
    };
  },
  mounted() {
    this.createLazyLoadObserver();
    this.createTouchFooterObserver();
    this.createStickHeaderObserver();
  },
  beforeDestroy() {
    this.unobserveAllIntersectionObservers();
  },
  methods: {
    /*
    * 创建懒加载observer并遍历监听所有img
    */
    createLazyLoadObserver() {
      this.lazyLoad.target = document.querySelectorAll('img');
      this.lazyLoad.observer = new IntersectionObserver((entries) => {
        entries.forEach((item) => {
          if (item.isIntersecting) {
            item.target.src = item.target.dataset.imgUrl;
          }
        });
      });
      this.lazyLoad.target.forEach((image) => {
        this.lazyLoad.observer.observe(image);
      });
    },
    /*
    * 创建触底observer并监听footer
    */
    createTouchFooterObserver() {
      this.touchFooter.target = document.querySelector('footer');
      this.touchFooter.observer = new IntersectionObserver((entries) => {
        entries.forEach((item) => {
          if (item.isIntersecting) {
            setTimeout(() => {
              console.log('滚动到了底部,可以发request请求数据了');
            }, 2000);
          }
        });
      });
      this.touchFooter.observer.observe(this.touchFooter.target);
    },
    /*
    * 创建吸顶observer并监听header
    * 创建reference首次防抖,.fixed-top-helper二次防抖
    */
    createStickHeaderObserver() {
      this.stickHeader.target = document.querySelector('header');
      this.stickHeader.reference = document.querySelector('.fixed-top-reference');
      this.stickHeader.reference.style.top = `${this.stickHeader.target.offsetTop}px`;
      this.stickHeader.observer = new IntersectionObserver((entries) => {
        entries.forEach((item) => {
          if (item.boundingClientRect.top < 0) {
            this.stickHeader.target.classList.add('fixed');
          } else {
            this.stickHeader.target.classList.remove('fixed');
          }
        });
      });
      this.stickHeader.observer.observe(this.stickHeader.reference);
    },
    /*
     * 取消observe所有监听目标
     */
    unobserveAllIntersectionObservers() {
      /* 
      * disconncet()可以取消所有observed目标
      * 如果调用unobserve取消监听,稍显冗余的代码如下:
        this.lazyLoad.target.forEach((image) => {
          this.lazyLoad.observer.unobserve(image);
        });
      */
      this.lazyLoad.observer.disconnect();
      /*
       * 由于touchFooter和stickHeader只observe了一个目标,因此单独unobserve即可
       * 当然disconnect()也是ok的
       */
      this.touchFooter.observer.unobserve(this.touchFooter.target);
      this.stickHeader.observer.unobserve(this.stickHeader.reference);
    },
  },
};
</script>
<style lang="scss" scoped>
.fixed-top-helper {
  height: 1px;
  background: #ccc;
}
header {
  background: #ccc;
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}
main {
  img {
    display: block;
    height: 500px;
    margin: 30px;
  }
}
footer {
  background: #ccc;
}
</style>


基于Intersection的开源工具 Scrollama


官方提供了Basic Process,Progress,Sticky Side,Sticky Overlay几种示例。

Basic Process

1460000021085608.gif


Progress


1460000021085606.gif


Sticky Overlay


1460000021085602.gif



在项目中可以当做适当引用。

项目地址:https://github.com/russellgol...

demo地址:https://russellgoldenberg.git...


总结


  • 主要使用IntersectionObserver实现懒加载图片,触底,吸顶
  • vue单文件组件简版主要是用于演示,vue单文件组件实战版可用于项目
  • 虽然我这里演示的是vue单文件组件的版本,但是我相信聪明的你知道核心部分在哪里
  • IntersectionObserver可能还会有其他的用处,来日方长,慢慢探索


参考资料



https://developer.mozilla.org...

https://developer.mozilla.org...

https://developer.mozilla.org...

https://juejin.im/post/5ca15c...

https://medium.com/walmartlab...

https://juejin.im/post/5d6651...

https://github.com/russellgol...

原文地址:IntersectionObserver是什么?

相关文章
|
前端开发
react-antd中使用Upload实现图片裁剪-上传-预览的功能
使用react中antd实现图片的上传裁剪和预览,记录一下实现过程,希望能对大家有帮助
815 0
react-antd中使用Upload实现图片裁剪-上传-预览的功能
Element ui dialog弹窗最大化最小化关闭组件封装
封装一个最大化最小化关闭的dialog弹窗组件
2499 1
|
9月前
|
JavaScript 前端开发 API
深入解析JavaScript Generator 生成器的概念及应用场景
本文讲解了JS生成器的概念和应用场景。生成器是一个可以暂停和恢复执行的函数。利用生成器我们可以很方便地实现自定义的可迭代对象、状态机、惰性计算等,并且还能用它来简化我们的异步操作代码。
252 0
|
1天前
|
JavaScript 前端开发
vue element-ui分页插件 始终保持在页面底部样式
vue element-ui分页插件 始终保持在页面底部样式
88 0
|
12月前
|
芯片 开发者
玄铁RISC-V处理器入门与实战-RISC-V 处理器架构-RISC-V处理器课程学习
玄铁RISC-V处理器入门与实战-RISC-V 处理器架构-RISC-V处理器课程学习
448 0
|
8月前
|
资源调度 JavaScript 前端开发
vue3.3-TinyMCE:TinyMCE富文本编辑器基础使用
vue3.3-TinyMCE:TinyMCE富文本编辑器基础使用
231 0
|
11月前
|
SQL JavaScript 关系型数据库
如何使用Node操作MySQL数据库
如何使用Node操作MySQL数据库
166 0
|
11月前
|
Java Android开发
JAVA代码规范
JAVA代码规范
155 0
|
11月前
|
前端开发 API 容器
前端封装库/工具库的编辑器之Draft.js
在现代前端开发中,富文本编辑器是一个非常重要的组成部分。其中,Draft.js 是一个备受欢迎的 React 富文本编辑器库。
593 0
http://www.vxiaotou.com