现代化懒加载的方式

现代化懒加载的方式

为什么需要懒加载

通常用户打开网页时,整个网页的内容将被下载并且呈现在一个页面中,虽然允许浏览器缓存页面,但是不能保证用户查看所有下载的的内容,例如一个照片墙应用,可能用户仅仅查看第一个图片之后离开,结果就是白白浪费了内存和带宽。因此我们需要当用户需要访问页面的一部分时才去加载内容,而不是一看是就去加载全部内容。

如何实现懒加载

当有人向网页(图像,视频)等资源,资源引用一个小的占位符,当用户浏览网页,实际的资源被浏览器缓存,并且当资源在屏幕上可见时替换占位符,例如,如果用户加载网页并立即离开网页,则除了网页的顶部之外没有任何内容被加载。

<figure style="display: block; margin: 2.7rem auto; text-align: center;">
image

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>

</figure>

懒加载具体实现

以加载图片为例子,我们需要将img标签中设置一个data-src属性,它指向的是实际上我们需要加载的图像,而imgsrc指向一张默认的图片,如果为空的话也会向服务器发送请求。

<img src="default.jpg" data-src="www.example.com/1.jpg">

之后当用户访问的可视区域的img元素时,将src得值替换为data-src指向的实际资源加载的图像

具体代码

const lazy = (el) => {
 let scrTop = getTop();
 let windowHeight = document.documentElement.clientHeight;
 function getTop(){
  return document.documentElement.scrollTop || document.body.scrollTop; 
 }
 function getOffset(node){
  return node.getBoundingClientRect().top + scrTop;
 }
 function inView(node){
  // 设立阈值
 const threshold = 0;
 const viewTop = scrTop;
 const viewBot = viewTop + windowHeight;

 const nodeTop = getOffset(node);
 const nodeBot = nodeTop + node.offsetHeight;

 const offset = (threshold / 100) * windowHeight;
 console.log((nodeBot >= viewTop - offset), (nodeTop <= viewBot + offset))
    return (nodeBot >= viewTop - offset) && (nodeTop <= viewBot + offset)
 }
 function check(node){
   let el = document.querySelector(node);
   let images = [...el.querySelectorAll('img')];
   images.forEach(img => {
    if(inView(img)){
     img.src = img.dataset.src;
    }
   })
 }
 check(el);
}

window.onscroll = function(){
 lazy('.foo');
}

现代化懒加载实现方法

通过上面例子的实现,我们要实现懒加载都需要去监听scroll事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算scrollTop,offsetHeight等属性,有没有简单的不需要计算这些属性的方式呢,答案是有的---IntersectionObserver

根据MDN:

IntersectionObserver API为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)处于交叉状态的方式。祖先元素与视窗(viewport)被称为根(root)。

简单来说就是观察一个元素和另一个元素是否重叠。

IntersectionObserver初始化的过程中提供了三个主要元素的配置:

  • root: 这是用于观察的根元素。他定义了可观察元素的基本捕获框架,默认情况下,root指向的是浏览器的视口,但实际上可以是任意的DOM元素,要注意的是:root在这种情况下,要观察元素的必选要在root代表的Dom元素内部
image.png
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>

</figure>
  • rootMargin: 计算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的判定范围从而满足计算需要。值得选项与marginCSS类似,比如rootMargin: '50px 20px 10px 40px'(top, right, bottom, left)
image.png
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>

</figure>
  • threshold: 一个包含阈值的list, 升序排列, list中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会生成一个通知(Notification)。如果构造器未传入值, 则默认值为0.
image.png
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>

</figure>

为了告诉我们`intersectionObserver`我们想要得配置,我们只需要将我们得`config`对象和我们的回调函数一起传递到Observer构造函数中
const config = {
    root: null,
    rootMargin: '0px',
    threshold: 0.5
}
let observer = new IntersectionObserver(fucntion(entries){
    // ...
}, config)

现在我们需要去给IntersectionObserver实际观察的元素

const img = document.querySelector('image');
observer.observe(img);

关于这个实际观察的元素需要注意几点

  • 首先他应该位于root代表的DOM元素中
  • IntersectionObserver一次只能接受一个观察元素,不支持批量观察。这意味着如果你需要观察几个元素(比如说一个页面上的几个图像),你必须遍历所有元素并分别观察它们中的每一个
const images = document.querySelecttorAll('img');
images.forEach(image => {
    observer.observe(image)
})

  • 当使用Observer加载页面时,您可能会注意到,IntersectionObserver所有观察到的元素的回调已经被触发了。我们可以通过回调函数来解决这个问题

IntersectionObserver回调函数

new IntersectionObserver(function(entries, self))

entries我们得到我们的回调函数作为Array是特殊类型的:IntersectionObserverEntry首先IntersectionObserverEntry含有三个不同的矩形的信息

  • rootBounds: '捕捉框架(root + rootMargin)'的矩形

  • boundClientRect: 观察元素本身的矩形

  • intersectionRect: 捕捉框架和观察元素相交的矩形

    image.png

    此外,IntersectionObserverEntry还提供了isIntersecting,这是一个方便的属性,返回观察元素是否与捕获框架相交, 另外,IntersectionObserverEntry提供了利于计算的遍历属性intersctionRatio:返回intersectionRect 与 boundingClientRect 的比例值.

image.png

target则返回要观察的元素 好了

简单介绍完,让我们回到正题,用这个IntersectionObserver来实现代化的懒加载方式吧

const images = document.querySelectorAll('[data-src]')
const config = {
    rootMargin: '0px',
    threshold: 0
};
let observer = new IntersectionObserver((entries, self)=>{
    entries.forEach(entry => {
        if(entry.isIntersecting){
         // 加载图像
         preloadImage(entry.target);
         // 解除观察
           self.unobserve(entry.target)
        }
    })
}, config)

images.forEach(image => {
  observer.observe(image);
});

function preloadImage(img) {
  const src = img.dataset.src
  if (!src) { return; }
  img.src = src;
}

相比于之前懒加载的方式是不是更加简洁,而且只有当观察元素和捕捉框架交叉或重叠时,才会触发回掉函数(加载页面时也会触发回调函数,不过我们可以用isIntersecting来进行判断是否和观察元素相交)

延迟加载的好处

  • 延迟加载在优化内容加载和简化最终用户体验之间达成了平衡。
  • 用户可以更快地加载到内容,因为用户第一次打开网站时只需要加载一部分内容。
  • 网站看到更高的用户保留,因为不断向用户提供内容,减少了用户离开网站的机会。
  • 网站看到较低的资源成本,因为内容只在用户需要时才加载,而不是一次完成。

该方法目前有兼容性问题, github上有polyfill
https://github.com/w3c/IntersectionObserver/tree/master/polyfill

参考:

MDN

Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver

What is Lazy Loading?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,629评论 1 92
  • 【做不到一件事,死在最前方而不自知的就是,定义不清楚,没有准确、正确的概念。没有准确、正确的概念,自然就不能准确、...
    一一的试验田阅读 1,737评论 0 0
  • 朋友圈刚刚刷到这样一条动态,有人疯狂的追求了你三年,有人默默对你好了两年,有人体贴照顾你了一年,有人以一个朋友的名...
    白菜梗阅读 827评论 0 1