JS实现图片懒加载以及遇到的坑

本文参照了博文:http://axuebin.com/blog/2017/08/19/javascript-lazyload/ 在他的基础上做了一些改动

为什么要图片懒加载

因为一个图片就是一个请求,请求多了,自然浏览器的渲染速度下降,图片懒加载就是解决这个问题的一个方法。还有一个方法是使用雪碧图,可以看我另外一个blog。

所谓的图片懒加载就是一开始就不加载所有的图片,而是当我们滚动到图片所在的区域的时候,再加载它。

图片懒加载原理

标签有一个属性是src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有src属性,就不会发送请求。我先不设置src,等滚动到该图片的时候再用js设置他的src,把图片真正的URL放在另一个属性data-src中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到src中。

实现方式

  1. 通过document.documentElement.clientHeight获取屏幕可视窗口高度

  2. 通过element.offsetTop获取元素相对于文档顶部的距离

  3. 通过document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离

由上图可知如果offset - scrollTop(差值)的大小大于clientHeight说明还没有到图片所在位置,如果小于说明已经下拉到图片了,为负值说明图片已经被拖动到视口之上了。

 

效果如下:

 

DOM结构如下


    
loading
loading
loading
loading
loading
loading
loading

JS如下

//文档加载好之后就检查有无图片需要加载
window.onload = checkImgs;
// document.ready=checkImgs;

//滚动后触发节流函数
window.onscroll = throttle;

//检查图片是否进入可视区域
function isInSight(el) {
    console.log(el);
    
    const clientHeight = document.documentElement.clientHeight;
    const offsetTop = el.offsetTop;
    const scrollTop = document.getElementById('root').scrollTop;
    //下面等式右端加了一个50 作用是稍微提前一点加载图片 而不是看到了图片才进行加载 这样会影响体验
    return (offsetTop - scrollTop) <= clientHeight + 50; 
}

//对所有所有没有加载的图片进行检查
// 这里全局设置的index作用是记录页面加载到了第几张图片了,防止多次加载已经加载好了的图片
let index = 0;
function checkImgs() {
  const imgs = document.getElementsByClassName('my-photo');
  for (let i = index; i < imgs.length; i++) {
    if (isInSight(imgs[i])) {
      loadImg(imgs[i]);
      index = i;
    }
  }
}

//加载图片src
function loadImg(el) {
  if (!el.src) {
    const source = el.dataset.src;
    el.src = source;
  }
}

//这里使用了函数节流,防止多次触发,影响效率
var canrun = true;
function throttle() {
    if (!canrun) {
        return;
    }
    canrun = false;
    setTimeout(() => {
        checkImgs();
        canrun = true;
    }, 200);
}

 

 

涉及到的知识:

一.client

1.1 clientHeight:包括padding但不包括border、水平滚动条、margin的元素的高度。对于inline的元素这个属性一直是0,单位px,只读元素。在本博文需要用到可视窗口的高度,那么我们就使用window.clientHeight这个属性即可。

JS实现图片懒加载以及遇到的坑_第1张图片

1.2 clientTop/clientLeft/clientRight/clientBottom

clientTop:元素上边框的厚度,当没有指定边框厚底时,一般为0。

 

二.scroll

2.1 scrollHeight:当本元素的子元素比本元素高且overflow=scroll时,本元素会scroll,这时:
scrollHeight: 因为子元素比父元素高,父元素不想被子元素撑的一样高就显示出了滚动条,在滚动的过程中本元素有部分被隐藏了,scrollHeight代表包括当前不可见部分的元素的高度。而可见部分的高度其实就是clientHeight,也就是scrollHeight>=clientHeight恒成立。在有滚动条时讨论scrollHeight才有意义,在没有滚动条时scrollHeight==clientHeight恒成立。单位px,只读元素。

JS实现图片懒加载以及遇到的坑_第2张图片

2.2 scrollTop/scrollLeft/scrollRight/scrollBottom

scrollTop:代表在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度。在没有滚动条时scrollTop==0恒成立。单位px,可读可设置。

一个元素的 scrollTop 值是这个元素的顶部到视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为0。

JS实现图片懒加载以及遇到的坑_第3张图片

三 offset

3.1 offsetheight:包括padding、border、水平滚动条,但不包括margin的元素的高度。对于inline的元素这个属性一直是0,单位px,只读元素。

JS实现图片懒加载以及遇到的坑_第4张图片

3.2 offsetTop/offsetLeft/offsetRight/offsetBottom

offsetTop:当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系。单位px,只读元素。

JS实现图片懒加载以及遇到的坑_第5张图片

四 函数节流函数防抖

之前看了很多文章把函数防抖和函数节流说的很复杂也很不清楚,其实很简单的两个东西。

函数防抖:触发后会在你所设定的间隔时间完成后执行相关的代码,如果这个时候你再次触发了,那么你必须得重新再等一次间隔时间。间隔时间内不允许被打断!打断后重来!

你可以想成是一个单机游戏打boss,boss每一个10min出现一次,但是这个时间你要是退出游戏了,再次登陆那么又必须再重新等一个10min,而不是接着之前的继续等。

// 函数防抖
var timer = false;
document.getElementById("debounce").onscroll = function(){
    clearTimeout(timer); // 清除未执行的代码,重置回初始化状态

    timer = setTimeout(function(){
        console.log("函数防抖");
    }, 300);
};  

函数节流:函数节流和函数防抖的区别在于,函数节流允许被打断。触发函数节流后,必定在设定的间隔时间内执行一次,哪怕你在这中间无数次打断。

可以想成是一个联网游戏打boss,boss每一个10min出现一次,9点开始计时,9:05你退出游戏了,9:07再次登陆,只需再等个3min boss就刷出来了。

// 函数节流
var canRun = true;
document.getElementById("throttle").onscroll = function(){
    if(!canRun){
        // 判断是否已空闲,如果在执行中,则直接return,而并不会置零计时器
        return;
    }

    canRun = false;
    setTimeout(function(){
        console.log("函数节流");
        canRun = true;
    }, 300);
};

遇到的坑:

1 ScrollTop一直为0

之前那篇博文在scrollTop这里有一点不对的地方,这里的scroll最好不要使用body里面的元素,因为scrollTop的定义是带有滚动条的元素这个属性才有值,不然都为0。一开始我用root的scrolltop发现不能实现懒加载,问题就出在root的scrollTop始终为0的问题上。我把root换成body之后就解决了这个问题。

2 window.onload & document.ready

 

你可能感兴趣的:(JS实现图片懒加载以及遇到的坑)