We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文以懒加载为主,先看看懒加载和预加载两者的概念对比
对页面加载速度影响最大的就是图片 当页面图片比较多,加载速度慢,非常影响用户体验
思考一下,页面有可能有几百张图片,但是首屏上需要展示的可能就一张而已,其他的那些图片能不能晚一点再加载,比如用户往下滚动的时候……
这是为什么要用懒加载的原因
那预加载呢? 这个非常语义化,预备,提前…… 就是让用户感觉到你加载图片非常快,甚至用户没有感受到你在加载图片
图片先用占位符表示,不要将图片地址放到src属性中,而是放到其它属性(data-original)中 页面加载完成后,监听窗口滚动,当图片出现在视窗中时再给它赋予真实的图片地址,也就是将data-original中的属性拿出来放到src属性中 在滚动页面的过程中,通过给scroll事件绑定lazyload函数,不断的加载出需要的图片
注意:请对lazyload函数使用防抖与节流,不懂这两的可以自己去查
这种方式,本质上不算懒加载 加载完首屏内容后,隔一段时间,去加载全部内容 但这个时间差已经完成了用户对首屏加载速度的期待
用户点击或者执行其他操作再加载 其实也包括的滚动可视区域,但大部分情况下,大家说的懒加载都是只可视区域的图片懒加载,所以就拿出来说了
这里也分为两种情况 1、页面滚动的时候计算图片的位置与滚动的位置 2、通过新的API: IntersectionObserver API(可以自动"观察"元素是否可见)Intersection Observer API - Web API 接口 | MDN
提前加载图片,当用户需要查看时可直接从本地缓存中渲染
加载方式目前主要有两种
先通过CSS将图片加载到不可见区域
#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; }
待到满足触发条件后,再通过JS渲染
通过new Image()把图片下载到本地,让浏览器缓存起来,设置其src来实现加载,再使用onload方法回调加载完成事件
new Image()
其实两者的概念是相反的 一个是延迟加载,一个是提前加载 一个是减低服务器压力,一个是增加服务器压力(换取用户体验)
将非首屏的图片的src属性设置一个默认值,监听事件scroll resize``orientationchange,判断元素进入视口viewport时则把真实地址赋予到src上
scroll
resize``orientationchange
<img class="lazy" src="[占位图]" data-src="[真实url地址]" data-srcset="[不同屏幕密度下,不同的url地址]" alt="I'm an image!">
如上,data-*属于自定义属性, ele.dataset.* 可以读取自定义属性集合 img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片,比如<img src="image-128.png" srcset="image-256.png 2x" />
data-*
ele.dataset.*
img.srcset
<img src="image-128.png" srcset="image-256.png 2x" />
imgEle.offsetTop < window.innerHeight + document.body.scrollTop
Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置,具体参考文档Element.getBoundingClientRect() - Web API 接口 | MDN
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; }
由于兼容性问题,暂时不写,具体可参考文档 Intersection Observer - Web API 接口 | MDN
【更新】可参考这篇文章Intersection Observer + Vue指令 优雅实现图片懒加载
注意DOMContentLoaded,在DOM解析完之后立马执行,不适合前后端分离的单页应用,因为SPA应用一般来说图片数据是异步请求的,在DOMContentLoaded的时候,页面上未必完全解析完JS和CSS,这时候let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));拿到的不是真正首屏的所有图片标签
DOMContentLoaded
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)) { // <img class="lazy" src="[占位图]" data-src="[真实url地址]" data-srcset="[不同屏幕密度下,不同的url地址]" alt="I'm an image!"> // ele.dataset.* 可以读取自定义属性集合,比如data-* // img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片 比如<img src="image-128.png" srcset="image-256.png 2x" /> 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); })
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)) { // <img class="lazy" src="[占位图]" data-src="[真实url地址]" data-srcset="[不同屏幕密度下,不同的url地址]" alt="I'm an image!"> // ele.dataset.* 可以读取自定义属性集合,比如data-* // img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片 比如<img src="image-128.png" srcset="image-256.png 2x" /> 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
<template> <img :class="['lazy', className]" :src="defaultImg" :data-src="url" :data-srcset="`${url} 1x`" alt="fordeal"> </template> <script> export default { props: { url: { type: String }, defaultImg: { type: String, default: [默认图片] className: { type: String, default: '' } } } </script>
<img-lazy className="image" :url="item.display_image" />
The text was updated successfully, but these errors were encountered:
const notAbove = ele.getBoundingClientRect().bottom >= 0 ? true : false; 直接写出 const notAbove = ele.getBoundingClientRect().bottom >= 0; 就好了
const notAbove = ele.getBoundingClientRect().bottom >= 0 ? true : false;
const notAbove = ele.getBoundingClientRect().bottom >= 0;
Sorry, something went wrong.
老哥稳!!!
5年过去了,主流浏览器已经能很好的支持 Intersection Observer 了。
Intersection Observer
另外懒加载的同时可以捕获一下img的异常,提升网络不佳下的友好度。🤣
img
Intersection Observer - Web API 接口 | MDN
Javascript catch image load error
No branches or pull requests
【性能优化】手把手实现图片懒加载+Vue封装
本文以懒加载为主,先看看懒加载和预加载两者的概念对比
一、总览
1、为什么要懒加载或者预加载
对页面加载速度影响最大的就是图片
当页面图片比较多,加载速度慢,非常影响用户体验
思考一下,页面有可能有几百张图片,但是首屏上需要展示的可能就一张而已,其他的那些图片能不能晚一点再加载,比如用户往下滚动的时候……
这是为什么要用懒加载的原因
那预加载呢?
这个非常语义化,预备,提前……
就是让用户感觉到你加载图片非常快,甚至用户没有感受到你在加载图片
2、懒加载原理
图片先用占位符表示,不要将图片地址放到src属性中,而是放到其它属性(data-original)中
页面加载完成后,监听窗口滚动,当图片出现在视窗中时再给它赋予真实的图片地址,也就是将data-original中的属性拿出来放到src属性中
在滚动页面的过程中,通过给scroll事件绑定lazyload函数,不断的加载出需要的图片
注意:请对lazyload函数使用防抖与节流,不懂这两的可以自己去查
3、懒加载方式
1)、纯粹的延迟加载,使用setTimeOut或setInterval
这种方式,本质上不算懒加载
加载完首屏内容后,隔一段时间,去加载全部内容
但这个时间差已经完成了用户对首屏加载速度的期待
2)、条件加载
用户点击或者执行其他操作再加载
其实也包括的滚动可视区域,但大部分情况下,大家说的懒加载都是只可视区域的图片懒加载,所以就拿出来说了
3)、可视区加载
这里也分为两种情况
1、页面滚动的时候计算图片的位置与滚动的位置
2、通过新的API: IntersectionObserver API(可以自动"观察"元素是否可见)Intersection Observer API - Web API 接口 | MDN
4、预加载
提前加载图片,当用户需要查看时可直接从本地缓存中渲染
加载方式目前主要有两种
1)、CSS预加载
先通过CSS将图片加载到不可见区域
待到满足触发条件后,再通过JS渲染
2)、JS预加载
通过
new Image()
把图片下载到本地,让浏览器缓存起来,设置其src来实现加载,再使用onload方法回调加载完成事件5、两者对比
其实两者的概念是相反的
一个是延迟加载,一个是提前加载
一个是减低服务器压力,一个是增加服务器压力(换取用户体验)
二、懒加载具体实现代码分析
1、核心原理
将非首屏的图片的src属性设置一个默认值,监听事件
scroll
resize``orientationchange
,判断元素进入视口viewport时则把真实地址赋予到src上2、img标签自定义属性相关
如上,
data-*
属于自定义属性,ele.dataset.*
可以读取自定义属性集合img.srcset
属性用于设置不同屏幕密度下,image自动加载不同的图片,比如<img src="image-128.png" srcset="image-256.png 2x" />
3、判断元素进入视口viewport
1)、图片距离顶部距离 < 视窗高度 + 页面滚动高度(太LOW了~)
2)、getBoundingClientRect (很舒服的一个API)
Element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置,具体参考文档Element.getBoundingClientRect() - Web API 接口 | MDN3)、Intersection Observer(存在兼容性问题,但帅啊)
由于兼容性问题,暂时不写,具体可参考文档
Intersection Observer - Web API 接口 | MDN
【更新】可参考这篇文章Intersection Observer + Vue指令 优雅实现图片懒加载
3、具体实现
1)、适合简单的HTML文件或者服务端直出的首页
注意
DOMContentLoaded
,在DOM解析完之后立马执行,不适合前后端分离的单页应用,因为SPA应用一般来说图片数据是异步请求的,在DOMContentLoaded
的时候,页面上未必完全解析完JS和CSS,这时候let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
拿到的不是真正首屏的所有图片标签2)、适合单页应用的写法(模拟封装vue的懒加载)
① 核心实现
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
的获取时机放在了定时器里面,不是一开始就拿到全局的lazyImages
,而是每次刷新时才拿到还没处理过的② 在全局中的
mounted
钩子中执行③ 封装
img-lazy
组件④ 使用
The text was updated successfully, but these errors were encountered: