这两天在看JavaScript的API时发现了这样一个东西IntersectionObserver
(准确说它是浏览器原生提供的一个异步API),它能够对一个元素进行监听,判断该元素是否在当前窗口中。
好官方的描述!!!
不用管它,简单点说,IntersectionObserver
就是观察一个元素是否在当前窗口中。
注意事项:该API是异步的
使用方法
/**
* @param {Function} callback 必填项,监听元素进入窗口时的回调函数
* @param {Object} options 非必填项,配置参数
* @param {Dom Element} options.root 监听元素的根元素,默认为全文档
* @param {String} options.rootMargin 元素到根元素边缘时的矩形偏移量,支持 px 和 %
* @param {Array} options.thresholds 交叉区域与边界区域的比例
*/
var io = new IntersectionObserver(callback, options);
io.observe(document.querySelector('img')); // 开始观察,接受一个DOM节点对象
io.unobserve(element); // 停止观察 接受一个element元素
io.disconnect(); // 关闭观察器
懵了么?还好,API的方法和属性比较少,先来撸一个案例你就明白怎么用了。
案例
<div id='app'>
<img src='./images/01.png'>
<img src='./images/01.png' >
<img src='./images/01.png' >
<img src='./images/01.png' >
<img src='./images/01.png' >
<img src='./images/01.png' >
</div>
<script>
// 创建IntersectionObserver实例
const io = new IntersectionObserver(function (entrys) {
console.log(entrys);
}, {threshold: [1]});
function update () {
const els = document.querySelectorAll('img');
els.forEach(ele => {
io.observe(ele); // 对元素进行监听
});
}
update();
</script>
此时,控制台会打印出监听的列表,注意列表中的每个元素都是一个监听器,其中target
属性就是监听的元素,isIntersecting
就是该元素是否在窗口内的标识。
图片懒加载
首先要明确我们的懒加载要有什么内容?
- 1、默认占位图
- 2、
img
元素上具有真实地址属性,如data-src
- 3、我们刚刚看到的
options.thresholds
,即交叉区域与边界区域的比例
class InterObser {
constructor (attr = 'data-src', defaultImg, options = {}) {
this.attr = attr;
this.defaultImg = defaultImg;
this.options = options;
this.install();
this.update();
}
install () {
const that = this;
// 初始化 IntersectionObserver 实例
that.io = new IntersectionObserver(function (entrys) {
// 当监听到元素进入窗口时,循环每个监听器
entrys.forEach(ele => {
// 如果该监听器进入窗口,则将真实地址替换占位图
if (ele.isIntersecting) {
that.imgLoad(ele);
// 停止对该监听器的监听,防止多次操作
that.io.unobserve(ele.target);
}
});
}, that.options);
}
// 图片加载
imgLoad (ele) {
const url = ele.target.dataset.src;
ele.target.src = url;
}
update () {
const els = document.querySelectorAll(`[${this.attr}]`);
// 为每个监听元素设置默认值
els.forEach(ele => {
ele.src = this.defaultImg;
// 开始监听
this.io.observe(ele);
});
}
}
// 调用
new InterObser('data-src', './images/01.png', {threshold: [1]});
自建vue懒加载插件
既然我们知道了用IntersectionObserver
做图片懒加载,那么我们是否可以将该功能做成插件呢?我目前的项目主要以vue为主,那么我就将其做成vue插件。
在src
目录下创建lib/index.js
文件。
class InterObser {
install (Vue, options) {
const that = this;
// 创建指令,使用方法是v-lazy='http://xxx'
Vue.directive('lazy', {
bind(el, binding) {
// 为元素添加默认占位图
that.init(el, binding.value, options.defaultSrc);
// 创建IntersectionObserver实例
that.initObserve(options);
},
inserted (el, binding) {
that.observe(el, binding); // 对该元素开启监听
}
})
}
init (el, val, def) {
// 将v-lazy的值挂载到该元素上,因为编译后元素上并没有v-lazy属性
el.setAttribute('data-src', val)
// 设置默认占位符
el.src = def
}
initObserve (options) {
// 该只需创建一次
var io = new IntersectionObserver(function (entrys) {
// 循环每个监听的元素,当该元素进入窗口,则替换真实地址,并取消监听
entrys.forEach(entry => {
if (entry.isIntersecting) { // 当isIntersecting为true,则代表该元素进入了窗口
const el = entry.target;
el.src = el.dataset.src; // 用真实地址替换占位符
el.removeAttribute('data-src')); // 移除多余的data-src属性
setTimeout(function () {
io.unobserve(el); // 取消监听
}, 0);
}
})
}, options.interOptions || {});
this.io = io;
}
observe (el) {
this.io.observe(el);
}
}
export default new InterObser();
使用
在main.js
中进行配置
import Vue from 'vue';
import LazyLoad from './lib/index.js';
// 全局配置
Vue.use(LazyLoad, {
defaultSrc: 'http://localhost:3000/01.jpg',
// IntersectionObserver原生配置项
interOptions: {
threshold: [1]
}
});
在组件中使用
<template>
<div>
<img v-lazy='lazySrc'>
<img v-lazy='lazySrc'>
<img v-lazy='lazySrc'>
<img v-lazy='lazySrc'>
<img v-lazy='lazySrc'>
</div>
</template>
<script>
export default {
data () {
return {
lazySrc: 'http://localhost:3000/02.jpg'
};
}
};
</script>
是不是非常简单,我们来看看传统的图片懒加载是怎么实现的。
传统的图片懒加载
传统图片懒加载主要是依赖监听scroll
和获取getBoundingClientRect().top
、getBoundingClientRect().bottom
来做的,但是这里涉及到性能问题,比如getBoundingClientRect
会引起回流等,我们能够做的就是减少这些性能上的操作。
我们可以将上述插件做一下优化,当浏览器不支持IntersectionObserver
时,我们就采用传统懒加载的方式。
class InterObser {
install () {
.....
}
initObserve (options) {
// 多了一个判断条件而已
// 仅当浏览器支持该API时才初始化
if (IntersectionObserver) {
var io = new IntersectionObserver(function () {
.......
}, options.interOptions || {});
this.io = io;
}
}
observe (el) {
// 如果浏览器支持IntersectionObserver,则使用,否则使用传统懒加载方式
if (IntersectionObserver) {
this.io.observe(el);
} else {
this.listenerScroll(el);
}
}
listenerScroll (el) {
const that = this;
that.load(el); // 为了使当前窗口的图片能够正常加载
window.addEventListener('scroll', function (e) {
// 防止多次触发
// 减少getBoundingClientRect操作从而达到优化的目的
if (el.dataset.src) {
that.load(el);
}
});
}
load (el) {
// 获取窗口高度
var docHeight = document.documentElement.clientHeight;
var boundingClientRect = el.getBoundingClientRect();
var bottom = boundingClientRect.bottom;
var top = boundingClientRect.top;
// 当元素进入窗口时,才加载真实图片
if (top < docHeight && bottom > 0) {
el.src = el.dataset.src;
el.removeAttribute('data-src');
}
}
}
遗留问题
这里还遗留几个问题
- 当用户传入了
options.interOptions
时没有做兼容处理 - 除了判断
el.dataset.src
进行优化之外还可配合节流函数 - 默认图片和
v-lazy
中的图片只能是网络图片
这几个问题后期慢慢补充,个人觉得可以完善这个插件,并且不止限于img
标签,还可以对video
等进行懒加载。
更多相关文档,请见:
线上地址 【前端橘子君】
GitHub仓库【前端橘子君】
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!