转自于:https://ah.yxlblog.com/index/article/145.html
前记
在一些图片量比较大的网站(比如电商网站首页,或者团购网站、小游戏首页等),如果我们尝试在用户打开页面的时候,就把所有的图片资源加载完毕,那么很可能会造成白屏、卡顿等现象。但是打开页面的瞬间,呈现给用户的只有屏幕的一部分(我们称之为首屏)。只要我们可以在页面打开的时候把首屏的图片资源加载出来就可以了。
/* * 分析
* 1、 获取视口的高度
* 2、图片距离可视窗口的顶部距离
* */
//当前可视区域的高度, 在和现代浏览器及 IE9 以上的浏览器中,可以用 window.innerHeight 属性获取。在低版本 IE 的标准模式中,可以用 document.documentElement.clientHeight
//元素距离视口的高度可以使用getBoundingClientRect() 来获取
/**
* getBoundingClientRect用于获取某个html元素相对于视窗的位置集合。
* object.getBoundingClientRect();
* 会得到元素的top、right、bottom、left、width、height属性,这些属性以一个对象的方式返回。
* right是指元素的右边到页面视口最左边的距离,bottom是指底边到页面视口顶部的距离。
*
* ie5以上都能支持,但是又一点点地方需要修正一下,
IE67的left、top会少2px,并且没有width、height属性。
*
可以看下一下这张图一目了然:
var viewHeight = window.innerHeight || document.documentElement.clientHeight;
// 获取所有的图片标签
var imgs = document.getElementsByTagName('img');
function lazyLoad(){
var imgLen = imgs.length;
var i = 0;
for(i;i < imgLen;i++){
// 用可视区域高度减去元素顶部距离可视区域顶部的高度
var distance = viewHeight - imgs[i].getBoundingClientRect().top;
// console.log( distance );
// 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
if( distance >= 0 ){
imgs[i].src = imgs[i].getAttribute('data-src');
num = i + 1
}
}
}
//添加监听事件,页面初始化要执行一次
lazyLoad();
window.addEventListener('scroll',lazyLoad,false);
这个scroll 是个用户频繁触发的事件,对性能极大的影响。
可以看到console下输出的一堆log,所以我们还要优化下上面的代码,引入下面的防抖和节流
这两个概念
以闭包的形式存在,它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。
/**
* 每当用户触发了一次 scroll 事件,我们就为这个触发操作开启计时器。一段时间内,后续所有的 scroll 事件都无法触发新的 scroll 回调。直到定时器的时间到了,第一次触发的 scroll 事件对应的回调才会执行,而这个时间段内触发的后续的 scroll 回调都不会执行
* 说简单点就是:定时器时间内的第一次scroll有效,后面触发的scroll 都无效。
* **/
//第一种我们使用时间戳来实现
function throttle(fn,time){
// last为上一次触发回调的时间
var last = 0;
return function(){
//保留调用时的上下文
var context = this;
// 保留调用时传入的参数
var args = arguments;
//记录本次触发回调的时间
var now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if( now - last >= time ){
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context,args);
}else{
}
}
}
//定时器版本
function throttle2(func,wait,args){
var timeout;
return function(){
var context = this;
if(!timeout){
timeout = setTimeout(function(){
timeout = null;
func.apply(context,args);
},wait)
}
}
}
调用
// window.addEventListener('scroll',throttle(function(){
// console.log('触发了滚动事件')
// },1000),false);
//再从浏览器看到 无论我怎么滚动,只有到规定时间才会触发
防抖 debounce
当持续触发事件时,debounce 会合并事件且不会去触发事件,当一定时间内没有触发再这个事件时,才真正去触发事件。
/**
* @desc 函数防抖
* @param func 函数
* @param wait 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
function debounce(func,wait,immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
document.addEventListener('scroll', debounce(() => console.log('触发了滚动事件'), 1000,true))
具体的html:
<div class="wrap">
<div class="img-box">
<img data-src="./images/1.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/2.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/3.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/4.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/5.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/6.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/7.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/8.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/9.jpg" alt="">
</div>
<div class="img-box">
<img data-src="./images/10.jpg" alt="">
</div>
</div>
所以我们的图片懒加载可以这样使用:
window.addEventListener('scroll',debounce(lazyLoad,1000),false);