博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
手把手实现图片懒加载+封装vue懒加载组件
阅读量:5952 次
发布时间:2019-06-19

本文共 5073 字,大约阅读时间需要 16 分钟。

1、为什么要懒加载或者预加载

图片对页面加载速度影响非常大

当页面图片比较多,加载速度慢,非常影响用户体验 

思考一下,页面有可能有几百张图片,但是首屏上需要展示的可能就一张而已,其他的那些图片能不能晚一点再加载,比如用户往下滚动的时候…… 

这是为什么要用懒加载的原因  

那预加载呢? 这个非常语义化,预备,提前…… 就是让用户感觉到你加载图片非常快,甚至用户没有感受到你在加载图片

2、懒加载原理

图片先用占位符表示,不要将图片地址放到src属性中,而是放到其它属性(data-original)中 页面加载完成后,监听窗口滚动,当图片出现在视窗中时再给它赋予真实的图片地址,也就是将data-original中的属性拿出来放到src属性中 在滚动页面的过程中,通过给scroll事件绑定lazyload函数,不断的加载出需要的图片

注意:请对lazyload函数使用防抖与节流,不懂这两的可以自己去查

3、懒加载方式

1)纯粹的延迟加载,使用setTimeOut或setInterval

这种方式,本质上不算懒加载 加载完首屏内容后,隔一段时间,去加载全部内容 但这个时间差已经完成了用户对首屏加载速度的期待

2)条件加载

用户点击或者执行其他操作再加载 其实也包括的滚动可视区域,但大部分情况下,大家说的懒加载都是只可视区域的图片懒加载,所以就拿出来说了

3)可视区加载

这里也分为两种情况:

1、页面滚动的时候计算图片的位置与滚动的位置

2、通过新的API: IntersectionObserver API(可以自动"观察"元素是否可见)

4、懒加载代码实现

1、核心原理

将非首屏的图片的src属性设置一个默认值,监听事件scrollresizeorientationchange,判断元素进入视口viewport时则把真实地址赋予到src

2、img标签自定义属性相关

I'm an image!复制代码

如上,data-*属于自定义属性, ele.dataset.* 可以读取自定义属性集合 img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片,比如<img src="image-128.png" srcset="image-256.png 2x" />

3、判断元素进入视口viewport

常用的方式有两种

1)、图片距离顶部距离 < 视窗高度 + 页面滚动高度(太LOW了~)

imgEle.offsetTop < window.innerHeight + document.body.scrollTop复制代码

2)getBoundingClientRect (很舒服的一个API)

Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置,具体参考文档

function isInViewport(ele) {    // 元素顶部 距离 视口左上角 的距离top <= 窗口高度 (反例:元素在屏幕下方的情况)    // 元素底部 距离 视口左上角 的距离bottom > 0 (反例:元素在屏幕上方的情况)    // 元素display样式不为none    const notBelow = ele.getBoundingClientRect().top <= window.innerHeight ? true : false;    const notAbove = ele.getBoundingClientRect().bottom >= 0 ? true : false;    const visable = getComputedStyle(ele).display !== "none" ? true : false;    return notBelow && notAbove && visable ? true : false;  }复制代码

3)Intersection Observer(存在兼容性问题,但帅啊)

由于兼容性问题,暂时不写,具体可参考文档

4、具体实现(demo)

核心内容都在上面分析完了,下面就是整合一下,

1)适合简单的HTML文件或者服务端直出的首页

注意DOMContentLoaded,在DOM解析完之后立马执行,不适合前后端分离的单页应用,因为SPA应用一般来说图片数据是异步请求的,在DOMContentLoaded的时候,页面上未必完全解析完JS和CSS,这时候let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));拿到的不是真正首屏的所有图片标签

document.addEventListener("DOMContentLoaded", () => {  // 获取所有class为lazy的img标签  let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));  // 这个active是节流throttle所用的标志位,这里用到了闭包知识  let active = false;  const lazyLoad = () => {    // throttle相关:200ms内只会执行一次lazyLoad方法    if (active) return;    active = true;    setTimeout(() => {      lazyImages.forEach(lazyImage => {        // 判断元素是否进入viewport        if (isInViewport(lazyImage)) {          // I'm an image!          // ele.dataset.* 可以读取自定义属性集合,比如data-*          // img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片  比如          lazyImage.src = lazyImage.dataset.src;          lazyImage.srcset = lazyImage.dataset.srcset;          // 删除class  防止下次重复查找到改img标签          lazyImage.classList.remove("lazy");        }        // 更新lazyImages数组,把还没处理过的元素拿出来        lazyImages = lazyImages.filter(image => {          return image !== lazyImage;        });        // 当全部处理完了,移除监听        if (lazyImages.length === 0) {          document.removeEventListener("scroll", lazyLoad);          window.removeEventListener("resize", lazyLoad);          window.removeEventListener("orientationchange", lazyLoad);        }      })      active = false;    }, 200);  }  document.addEventListener("scroll", lazyLoad);  document.addEventListener("resize", lazyLoad);  document.addEventListener("orientationchange", lazyLoad);})复制代码

2)、适合单页应用的写法(模拟封装vue的懒加载)

① 核心实现

  • 因为是demo,所以执行时机放到vue的全局mounted钩子里面(这样的首屏体验其实是不好的),不过足够理解就好了 
  • 跟上面不同的地方:let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));的获取时机放在了定时器里面,不是一开始就拿到全局的lazyImages,而是每次刷新时才拿到还没处理过的

function LazyLoad() {  // 这个active是节流throttle所用的标志位,这里用到了闭包知识  let active = false;  const lazyLoad = () => {    // throttle相关:200ms内只会执行一次lazyLoad方法    if (active) return;    active = true;    setTimeout(() => {      // 获取所有class为lazy的img标签,这里由于之前已经把处理过的img标签的class删掉了  所以不会重复查找      let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));      lazyImages.forEach(lazyImage => {        // 判断元素是否进入viewport        if (isInViewport(lazyImage)) {          // I'm an image!          // ele.dataset.* 可以读取自定义属性集合,比如data-*          // img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片  比如          lazyImage.src = lazyImage.dataset.src;          lazyImage.srcset = lazyImage.dataset.srcset;          // 删除class  防止下次重复查找到改img标签          lazyImage.classList.remove("lazy");        }        // 当全部处理完了,移除监听        if (lazyImages.length === 0) {          document.removeEventListener("scroll", lazyLoad);          window.removeEventListener("resize", lazyLoad);          window.removeEventListener("orientationchange", lazyLoad);        }      })      active = false;    }, 200);  }  document.addEventListener("scroll", lazyLoad);  document.addEventListener("resize", lazyLoad);  document.addEventListener("orientationchange", lazyLoad);}复制代码

② 在全局中的`mounted`钩子中执行

const vm = new Vue({  el: '.wrap',  store,  mounted: function () {    LazyLoad();  }});复制代码

③ 封装 img-lazy组件

复制代码

④ 使用

复制代码

以上实现的只是比较粗糙的版本,要真正实现性能大幅提升优化还需要处理较多的细节,本文旨在让帮助部分同学了解基本原理,有了宏观的认识后,可以尝试去读一下相关这种懒加载插件的源码,能学到不少东西。

感谢

感谢您耐心看到这里,希望有所收获!

我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步,更多文章请看

转载地址:http://wjoxx.baihongyu.com/

你可能感兴趣的文章
Hyperledger Composer评测
查看>>
云监控状态调查:公有云和混合云的监控成熟度落后于传统数据中心
查看>>
C# 8的Ranges和递归模式
查看>>
TDD容易被忽略的五大前提
查看>>
GIF 太大?用 GIFSicle
查看>>
专访死马:为什么说Egg.js是企业级Node框架
查看>>
Facebook何恺明团队提出SlowFast网络,视频识别无需预训练
查看>>
Weaveworks增加发布自动化和事件管理
查看>>
刚刚,ACM宣布三位深度学习之父共同获得2018年图灵奖!
查看>>
LLVM 4中将加入新的LLVM链接器LLD
查看>>
在HubSpot是如何应对Fat JAR困境的
查看>>
十周后,62%的PHP网站将运行在一个不受支持的PHP版本上
查看>>
当中台遇上DDD,我们该如何设计微服务?
查看>>
超级账本HyperLedger初体验
查看>>
四种方式主导你的第一个敏捷项目
查看>>
InfoQ就Spring Boot 2.0 GA版发布采访了项目牵头人Phil Webb
查看>>
Oracle开源Fn,加入Serverless之争
查看>>
美团即时物流的分布式系统架构设计
查看>>
微软发布.NET Core Tools 1.0版本
查看>>
PHP常用函数之字符串处理
查看>>