图片懒加载实现思路

402 阅读3分钟

图片懒加载实现原理

预先将图片真实的src放在自定义的属性中,data-src 当图片出现在视口范围内,将data-src赋值给src,完成图片加载

这里有个点就是初始化时可以不给img标签加上src属性,因为只要存在src属性,浏览器就会去执行一次请求将其指向的资源下载并应用到文档内,这里不加上可以提升一些性能

判断图片所在位置是否在可视区内,图片移到可视区内进行加载,

提供三种判断方法

  1. offsetTop < clientHeight + scrollTop
  2. element.getBoundingClientRect().top < clientHeight
  3. IntersectionObserver

引用 作者:程洋cYang
链接:juejin.cn/post/689681… 来源:稀土掘金

js实现部分 /第二种
  • 获取浏览器视口的高度

    获取可视区域的高度我们通常使用window.innerHeight就可以拿到了,当然如果需要兼容低版本IE浏览器的话则可以使用document.documentElement.clientHeight来获取,这里我们做一个兼容处理

    
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight
    
  • 获取图片离顶部的距离

    直接使用getBoundingClientRect()这个方法来获取

  • **Element.getBoundingClientRect()** 方法返回元素的大小及其相对于视口的位置。

一个小疑问?

人家的库用的offsetTop还是getBoundingClientRect呢 ? 哈哈 是intersectionObserver不


x : 146

y : 50

width : 440

height : 240

top : 50

right : 586

bottom : 290

left : 146

// 获取所有图片
const imgList = document.querySelectorAll('img')
// 用于记录当前显示到了哪一张图片
let index = 0;
function lazyload() {
  // 获取浏览器视口高度,这里写在函数内部是考虑浏览器窗口大小改变的情况
  const viewPortHeight = window.innerHeight || document.documentElement.clientHeight
  for (let i = index; i < imgList.length; i++) {
    // 这里用可视区域高度减去图片顶部距离可视区域顶部的高度
    const distance = viewPortHeight - imgList[i].getBoundingClientRect().top;
    // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明图片已经出现在了视口范围内
    if (distance >= 0) {
      // 给图片赋值真实的src,展示图片
      imgList[i].src = imgList[i].getAttribute('data-src');
      // 前i张图片已经加载完毕,下次从第i+1张开始检查是否需要显示
      index = i + 1;
    }
  }
}
​
// 定义一个防抖函数
function debounce(fn, delay = 500) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}
​
// 页面加载完成执行一次lazyload,渲染第一次打开的网页视口内的图片
window.onload = lazyload;
// 监听Scroll事件,为了防止频繁调用,使用防抖函数进行优化
window.addEventListener("scroll", debounce(lazyload, 600));
// 浏览器窗口大小改变时重新计算
window.addEventListener("resize", debounce(lazyload, 600));
​
​
vue中的图片懒加载

vue3,我们可以使用 @vueuse/core 中的 useIntersectionObserver 来实现监听组件进入可视区域行为,需要配合vue3.0的组合API的方式才能实现

vue2

IntersectionObserver() 构造器

使用

var observer = new IntersectionObserver(callback[, options]);

callback

非传统实现方式,性能最优

/**
 * 懒加载
 * @description 可加载`<img>`、`<video>`、`<audio>`等一些引用资源路径的标签
 * @param {object} params 传参对象
 * @param {string?} params.lazyAttr 自定义加载的属性(可选)
 * @param {"src"|"background"} params.loadType 加载的类型(默认为`src`)
 * @param {string?} params.errorPath 加载失败时显示的资源路径,仅在`loadType`设置为`src`中可用(可选)
 */
function lazyLoad(params) {
    const attr = params.lazyAttr || "lazy";
    const type = params.loadType || "src";
​
    /** 更新整个文档的懒加载节点 */
    function update() {
        const els = document.querySelectorAll(`[${attr}]`);
        for (let i = 0; i < els.length; i++) {
            const el = els[i];
            observer.observe(el);
        }
    }
​
    /**
     * 加载图片
     * @param {HTMLImageElement} el 图片节点
     */
    function loadImage(el) {
        const cache = el.src; // 缓存当前`src`加载失败时候用
        el.src = el.getAttribute(attr);
        el.onerror = function () {
            el.src = params.errorPath || cache;
        }
    }
​
    /**
     * 加载单个节点
     * @param {HTMLElement} el 
     */
    function loadElement(el) {
        switch (type) {
            case "src":
                loadImage(el);
                break;
            case "background":
                el.style.backgroundImage = `url(${el.getAttribute(attr)})`;
                break;
        }
        el.removeAttribute(attr);
        observer.unobserve(el);
    }
​
    /** 
     * 监听器 
     * [MDN说明](https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver)
    */
    const observer = new IntersectionObserver(function(entries) {
        for (let i = 0; i < entries.length; i++) {
            const item = entries[i];
            if (item.isIntersecting) {
                loadElement(item.target);
            }
        }
    })
​
    update();
​
    return {
        observer,
        update
    }
}
复制代码

在vue中使用指令去使用

import Vue from "vue";
​
/** 添加一个加载`src`的指令 */
const lazySrc = lazyLoad({
    lazyAttr: "vlazy",
    errorPath: "./img/error.jpg"
})
​
Vue.directive("v-lazy", {
    inserted(el, binding) {
        el.setAttribute("vlazy", binding.value); // 跟上面的对应
        lazySrc.observer.observe(el);
    }
})
​
/** 添加一个加载`background`的指令 */
const lazyBg = lazyLoad({
    lazyAttr: "vlazybg",
    loadType: "background"
})
​
Vue.directive("v-lazybg", {
    inserted(el, binding) {
        el.setAttribute("vlazybg", binding.value); // 跟上面的对应
        lazyBg.observer.observe(el);
    }
})