图片对页面加载速度影响很是大前端
当页面图片比较多,加载速度慢,很是影响用户体验 vue
思考一下,页面有可能有几百张图片,可是首屏上须要展现的可能就一张而已,其余的那些图片能不能晚一点再加载,好比用户往下滚动的时候…… git
这是为何要用懒加载的缘由 github
那预加载呢? 这个很是语义化,预备,提早…… 就是让用户感受到你加载图片很是快,甚至用户没有感觉到你在加载图片
后端
图片先用占位符表示,不要将图片地址放到src
属性中,而是放到其它属性(data-original
)中 页面加载完成后,监听窗口滚动,当图片出如今视窗中时再给它赋予真实的图片地址,也就是将data-original
中的属性拿出来放到src属性中 在滚动页面的过程当中,经过给scroll
事件绑定lazyload
函数,不断的加载出须要的图片
数组
注意:请对lazyload
函数使用防抖与节流,不懂这两的能够本身去查
bash
这种方式,本质上不算懒加载 加载完首屏内容后,隔一段时间,去加载所有内容 但这个时间差已经完成了用户对首屏加载速度的期待
闭包
用户点击或者执行其余操做再加载 其实也包括的滚动可视区域,但大部分状况下,你们说的懒加载都是只可视区域的图片懒加载,因此就拿出来讲了前后端分离
这里也分为两种状况:
异步
一、页面滚动的时候计算图片的位置与滚动的位置
二、经过新的API: IntersectionObserver API
(能够自动"观察"元素是否可见)Intersection Observer API - Web API 接口 | MDN
将非首屏的图片的src属性设置一个默认值,监听事件scroll
、resize
、orientationchange
,判断元素进入视口viewport时则把真实地址赋予到src
上
<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" />
经常使用的方式有两种
1)、图片距离顶部距离 < 视窗高度 + 页面滚动高度(太LOW了~)
imgEle.offsetTop < window.innerHeight + document.body.scrollTop
复制代码
2)getBoundingClientRect
(很舒服的一个API)
Element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置,具体参考文档Element.getBoundingClientRect() - Web API 接口 | MDN
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
(存在兼容性问题,但帅啊)
因为兼容性问题,暂时不写,具体可参考文档 Intersection Observer - Web API 接口 | MDN
核心内容都在上面分析完了,下面就是整合一下,
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)) {
// <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);
})
复制代码
2)、适合单页应用的写法(模拟封装vue的懒加载)
① 核心实现
mounted
钩子里面(这样的首屏体验实际上是很差的),不过足够理解就行了 et 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)) {
// <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" />复制代码
以上实现的只是比较粗糙的版本,要真正实现性能大幅提高优化还须要处理较多的细节,本文旨在让帮助部分同窗了解基本原理,有了宏观的认识后,能够尝试去读一下相关这种懒加载插件的源码,能学到很多东西。
感谢您耐心看到这里,但愿有所收获!
我在学习过程当中喜欢作记录,分享的是本身在前端之路上的一些积累和思考,但愿能跟你们一块儿交流与进步,更多文章请看amandakelake的Github博客