图片懒加载

# 为什么图片需要懒加载

  • 图片数量和体积过大往往会影响页面加载,容易造成不良的用户体验
  • 实现懒加载后,可以减少浏览器和服务器压力

推荐:Medium (opens new window) 非常流畅的图片懒加载应用网站,使用了 aspect-ratio 创建占位元素,并且添加了 blur 效果

# 原理

在合适的时机替换 src 地址

<div>
  <img src="占位图片" data-src="真正的图片地址" alt="">
  <img src="占位图片" data-src="真正的图片地址" alt="">
  <img src="占位图片" data-src="真正的图片地址" alt="">
  many img...
</div>
1
2
3
4
5
6

# 方案一:原生懒加载

判断浏览器是否支持原生的懒加载

浏览器不支持的话,可以使用这个库:lazysizes (opens new window)

if ("loading" in HTMLImageElement.prototype) {
    console.log("Yes!");
    // <img src="xxx" alt="" loading="lazy">
} else {
    console.log("No~");
    // 引入 lazysizes
    // 或者自定义逻辑
}
1
2
3
4
5
6
7
8

# 方案二:监听页面滚动

监听页码滚动,判断图片进入视窗后加载图片

对需要懒加载的图片添加标记,加载后移除

const imgs = document.getElementsByTagName("img");
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
let imgIndex = 0;

const canView = (element) => {
    return element.getBoundingClientRect().top <= viewHeight;
};

const main = () => {
    for (let i = imgIndex; i < imgs.length; i++) {
        const element = imgs[i];
        if (canView(element)) {
          element.src = element.dataset.src;
          imgIndex = i + 1;
        }
    }
};

window.addEventListener("scroll", main);
window.onload = main;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 方案三:IntersectionObserver

const imgs = document.querySelectorAll("img[data-src]");

const loadImg = (image) => {
    image.setAttribute("src", image.getAttribute("data-src"));
    image.addEventListener("load", () => {
        image.removeAttribute("data-src");
    });
};

const observerImg = new IntersectionObserver((items, observer) => {
    items.forEach((item) => {
        if (item.isIntersecting) {
            loadImg(item.target);
            observer.unobserve(item.target);
        }
    });
});

imgs.forEach((img) => {
    observerImg.observe(img);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 滚动性能优化

利用 requestAnimationFrame 保证每一帧只执行一次滚动事件

window.addEventListener("scroll", handleScroll);
handleScroll();

function handleScroll() {
    beforeNextFrameOnce(_handleScroll);
}

function _handleScroll() {
    // doing somthing
}
1
2
3
4
5
6
7
8
9
10

beforeNextFrameOnce():

let onceCbs = [];
const paramsMap = new WeakMap();
function flushOnceCallbacks() {
    onceCbs.forEach((cb) => cb(...paramsMap.get(cb)));
    onceCbs = [];
}
function beforeNextFrameOnce(cb, ...params) {
    paramsMap.set(cb, params);
    if (onceCbs.includes(cb)) return;
    onceCbs.push(cb) === 1 && requestAnimationFrame(flushOnceCallbacks);
}
1
2
3
4
5
6
7
8
9
10
11