TODO: 懒加载
图片懒加载基础概念
height 和 width
innerHeight/innerWidth 是返回窗口的文档显示区的高度和宽度(只包括网页的部分)
outerHeight/outerWidth 是返回整个浏览器的高度和宽度(从浏览器最外部开始算)
offsetWidth 获取物体宽度的数值, 这个实际的宽度是受盒模型的影响的。offsetWidth 实际获取的是盒模型(width+border + padding)
image: naturalHeight, naturalWidth HTML5 的新属性,用来判断图片的真实宽度和高度。 但有个前提是,必须在图片完全下载到客户端浏览器才能判断,目前在 ie 9,Firefox, Chrome, Safari 和 Opera 都是可以使用的, 如果是不支持的版本浏览器,那可以用传统方法判断。
var myImage = document.getElementById("myImage"); if (typeof myImage.naturalWidth == "undefined") { // IE 6/7/8 var i = new Image(); i.src = myImage.src; var rw = i.width; var rh = i.height; } else { // HTML5 browsers var rw = myImage.naturalWidth; var rh = myImage.naturalHeight; }
特定属性
- data-srcset img 标签的预设 url, 自定义 html 属性可以一开始不加载 url 使用一个默认的很小的 base64 图片来显示,图片马上进入视图范围以内时,通过一个 loading 切换动画,并且监听器回调通过将预设的 url 替换到默认的 src 值上面,此时就会去发起 http 请求图片,达到一个懒加载的效果。
页面大量图片,如何优化加载,优化用户体验
- 懒加载:在页面的未可视区域添加一个滚动事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
- 预加载:如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
- 雪碧图:如果图片为 css 图片,可以使用 CSSsprite,SVGsprite 等技术。
- 缩略图:如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
图片懒加载与预加载
图片懒加载的原理就是暂时不设置图片的 src 属性,而是将图片的 url 隐藏起来,比如先写在 data-src 里面,等某些事件触发的时候(比如滚动到底部,点击加载图片)再将图片真实的 url 放进 src 属性里面,从而实现图片的延迟加载
图片预加载,是指在一些需要展示大量图片的网站,实现图片的提前加载。从而提升用户体验。常用的方式有两种,一种是隐藏在 css 的 background 的 url 属性里面,一种是通过 javascript 的 Image 对象设置实例对象的 src 属性实现图片的预加载。相关代码如下: css 实现
#preload-01 {
background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px;
}
#preload-02 {
background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px;
}
#preload-03 {
background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px;
}
Javascript 预加载图片的方式:
function preloadImg(url) {
var img = new Image();
img.src = url;
if (img.complete) {
//接下来可以使用图片了
//do something here
} else {
img.onload = function () {
//接下来可以使用图片了
//do something here
};
}
}
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。 两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。 懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力
懒加载原理
- 路由分割、图片、数据懒加载
- 数据日志、error、埋点上报
- 日志可视化
- 懒加载图片
- 接口、数据懒加载
一开始先给为 src 赋值成一个通用的预览图,下拉时候再动态赋值成正式的图片。如 下, preview.png 是预览图片,比较小,加载很快,而且很多图片都共用这个 preview.png ,加载一次即可。待页面下拉,图片显示出来时,再去替换 src 为 data-realsrc 的值。
<img src="preview.png" data-realsrc="abc.png"/>. 另外,这里为何要用 data- 开头的属性值?—— 所有 HTML 中自定义的属性,都应该用 data- 开头,因为 data- 开头的属性浏览器渲染的时候会忽略掉,提高渲染性能。
- scrollTop 顶部偏移的值
- clientHeight 类似 screenHeight
- scrollHeight 一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。
scrollTop + clientHeight >= scrollHeight - 100 (阈值) 如果满足的话 就需要执行回调了。
- 监听 window 的 scroll 事件, addEventListener 第三个参数是 true 将事件冒泡上来。
- 通过一些 api 进行限流操作,比如 lodash 的 throttle, 或者是 requestAnimationXXX ? 等。 高级 API observer IntersectionObserver
- 传入一个回调,加载更多的状态下 执行回调。
// 手动计算 可能会卡顿
function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight =
window.innerHeight || document.documentElement.clientHeight;
const { top, right, bottom, left } = element.getBoundingClientRect();
return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;
}
组件懒加载
Vue-LazyLoad
解决的问题
可视区域的图片加载,其他的图片可以暂时有一个占位 loading 图,等滚动它们到可视区域时再去请求真实图片并且替换就好了。
原理简述
- lazyload 被安装的时候会创建一个 lazyload 实例,其内部维护了一个 ListenerQueue,实例会通过 add 方法将使用到的队列中。
- vue-lazyload 是通过指令的方式实现的,定义的指令是 v-lazy 指令
- 指令被 bind 时会创建一个 listener,并将其添加到 listener queue 里面,可以拿到 target 真实 dom 节点,会找它的父元素如果没有指定的话,会一步一步往上找,执行 scrollParent 操作。为其注册 dom 事件(如 scroll 事件)
- dom 事件被触发(一般是父级的 scroll 事件),会遍历 listener queue 里的 listener,判断此 listener 绑定的 dom 是否处于页面中 perload 的位置,如果处于则加载异步加载当前图片的资源
- 同时 listener 会在当前图片加载的过程的 loading,loaded,error 三种状态触发当前 dom 渲染的函数,分别渲染三种状态下 dom 的内容
简单总结:
- 监听父元素容器滚动,触发 scrollHandler 事件
- 遍历所有的图片的 dom 元素,且执行 checkInView 方法。
- 如果发现在视图范围内的话, 会替换图片的链接。
设计解析
图片懒加载白话原理:监听父元素滚动,遍历所有图片的 dom 元素,并且执行 checkInView 函数,如果在视图范围内,那么就将标签上的 data-src 标签的值替换为 src 的值,进行图片请求。
v-lazy 与 lazy-component 的原理类似。
实现原理
- 怎么获取一个元素到视图顶部的距离。
- getBoundingClientRect 获取的 top 和 offsetTop 获取的 top 区别
v-lazy 指令
指令分别有 4 个钩子需要注意,分别是 bind, update, componentUpdated, unbind, 会分别调用 Lazyload 实例中 add, update, lazyLoadHandler 和 remove 方法。
Lazyload 实例创建完成之后,会在指令对应的节点 bind 的时候,执行 lazyLoad.add 操作,创建一个响应式的 listener ,搜索 target dom (指令对应的 dom),为其注册好对应的 dom 事件之后,比如 scroll, 加入到一个全局的 listenerQueue 队列中去。
checkInView 实现
function checkInView() {
this.getRect(); // 调用dom的getBoundingClientRect()
return (
this.rect.top < window.innerHeight * this.options.preLoad &&
this.rect.bottom > this.options.preLoadTop &&
this.rect.left < window.innerWidth * this.options.preLoad &&
this.rect.right > 0
);
}
首先看 y 轴方向的判断:this.rect.top < window.innerHeight * this.options.preLoad, 是 dom 的顶部是否到了 preload 的位置;this.rect.bottom > this.options.preLoadTop 判断 dom 的底部是否到达了 preload 的位置
lazyComponent 组件的实现
<lazy-component @show="handler">
<img class="mini-cover" :src="img.src" width="100%" height="400" />
</lazy-component>
源码分析
- 注入 Vue 创建 LazyClass, 传入 options 参数创建 lazy 实例
- 将 lazy 实例作为参数初始化 LazyContainer
- Vue 原型链上添加
$Lazyload