HTML 图片懒加载

137 阅读7分钟

HTML图片懒加载技术

说到网页优化,图片懒加载绝对是个"狠角色"。啥叫懒加载?就是图片不着急全都加载,等你快要看到它的时候,它才姗姗来迟地冒出来。这样一来,页面加载速度嗖嗖的,用户体验直接起飞,流量也省了不少,简直就是前端界的"省钱小能手"。

一、原理简介

懒加载的核心思想就是"按需分配,量力而行"。你想啊,页面那么长,图片那么多,一股脑全加载,服务器都要"累瘫"了。我们聪明点,等用户快滚到图片那儿了再加载,这不就皆大欢喜了?

二、常用实现方式

1. 原生loading属性

HTML5给<img>标签塞了个新属性loading,用起来不要太简单:

<img src="example.jpg" loading="lazy" alt="示例图片">
  • loading="lazy":懒加载,图片"摸鱼"到快上班才来
  • loading="eager":默认,图片"卷王"一上来就干活
loading="lazy" 的工作机制详解
  1. 触发时机

    • 当图片距离视口底部还有一段距离(具体多少,得看浏览器心情),浏览器就会悄悄把图片加载上。
    • 这样用户一滚到图片,图片就"闪亮登场",不会让你等到花儿都谢了。
  2. 浏览器实现原理

    • 浏览器解析HTML时,发现loading="lazy"的图片,就把它们先放一边,等到快进视口了再加载。
    • 这样一来,首屏加载速度直接"起飞",服务器压力也小了不少。
  3. 兼容性

    • 主流浏览器都支持,除了Safari有点"特立独行",有些版本还不认这个属性。
    • 具体支持情况可以去Can I use查查,别到时候"翻车"了。
  4. 注意事项

    • 只对<img><iframe>生效,别想着给div加。
    • 懒加载图片可能被爬虫无视,SEO要注意,重要图片别懒。
    • 首屏大图、Logo啥的,建议别用懒加载,别让用户一进来啥都看不到。
    • 图片在display: none的容器里,懒加载可能"罢工"。

优点:简单粗暴,原生支持,写起来不要太爽。 缺点:兼容性有坑,老浏览器不买账。

2. Intersection Observer API

用JavaScript的Intersection Observer API,懒加载就像开了"外挂",灵活得很:

Intersection Observer API 详解
  1. 基本原理

    • 这个API就是个"监控器",专门盯着元素啥时候进视口。
    • 图片快进视口了,咱就让它加载,效率杠杠的。
  2. 核心API与参数说明

    • IntersectionObserver 构造函数:
      const observer = new IntersectionObserver(callback, options);
      
      • callback:元素可见性变化时的回调,图片"露脸"就通知你。
      • options:配置项,能指定"监控"范围、触发时机等。
        • root:谁当视口,不写就是浏览器窗口。
        • rootMargin:给视口加点"缓冲区",提前加载。
        • threshold:多少比例才算"露脸",0就是刚露头就算。
    • observer.observe(target):开始盯着目标元素。
    • observer.unobserve(target):不盯了。
    • observer.disconnect():全体解散。
  3. 懒加载代码示例

<img data-src="example.jpg" class="lazy" alt="示例图片">
<script>
const imgs = document.querySelectorAll('img.lazy');
const observer = new IntersectionObserver((entries, obs) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.remove('lazy');
      obs.unobserve(img);
    }
  });
});
imgs.forEach(img => observer.observe(img));
</script>

优点:灵活自如,兼容性还行,能自定义各种骚操作。 缺点:得写JS,IE直接"摆烂",要兼容得上polyfill。

3. 常用懒加载库

除了前面提到的 lazysizes 外,市面上还有一堆"卷王"库,个个身怀绝技:

  • lazysizes:功能全,插件多,社区活跃,懒加载界的"扛把子"。
  • lozad.jslozad.js 体积小,无依赖,简单粗暴,支持多种元素。
  • vanilla-lazyloadvanilla-lazyload 配置灵活,兼容性好,适合各种奇葩场景。
  • blazyblazy 轻量级,支持图片、iframe,兼容性也不错。
  • react-lazyloadreact-lazyload 专为React打造,API友好,集成无压力。

这些库大多用Intersection Observer当"底座",有的还会回退到滚动事件监听,开发者可以按需"拿来主义"。

五、WHY

为什么要用图片懒加载?

  1. 页面加载速度快到飞起,用户体验直接拉满

    • 图片是网页里的"重量级选手",全加载页面直接"龟速"。
    • 懒加载只让眼前的图片先上,首屏内容"秒开",用户再也不用"望穿秋水"。
  2. 省流量省资源,老板看了都说好

    • 用户没看到的图片就不加载,流量省一大截,移动端用户直呼"真香"。
    • 服务器压力小了,网站抗压能力up up。
  3. SEO也能兼顾,鱼和熊掌都能有

    • 首屏图片别懒,SEO和性能两手抓。

什么时候该用懒加载?

  1. 图片多、页面长,滚动到底都不带喘气的那种

    • 比如:电商商品列表、图片瀑布流、新闻资讯、博客文章列表。
    • 这些页面图片多得像下饺子,一次全加载,服务器都要"原地去世"。
  2. 移动端页面,流量就是命

    • 移动端用户流量有限,网络环境"玄学",懒加载能让体验"起飞"。
  3. 首屏外的图片,别让用户等到天荒地老

    • 首屏图片建议直接加载,别让用户一进来啥都看不到。
    • 首屏以下的图片,懒加载安排上。

具体例子与原因分析

  • 电商网站商品列表:用户一般只看前几屏,懒加载避免一次性加载全部商品图片,页面"嗖嗖"地就出来了。
  • 社交平台/图片社区(比如微博、知乎、Pinterest):图片流页面内容多,懒加载让你边刷边看,体验"丝滑"。
  • 新闻资讯类网站:文章列表页图片多,懒加载减少无效加载,首屏速度"起飞"。
  • 移动端App内嵌H5页面:流量宝贵,懒加载帮你省到极致,加载体验"嘎嘎好"。

一句话总结: 图片懒加载适合图片多、页面长、用户滚动浏览的场景。用得好,性能体验双赢;但首屏核心图片别懒,SEO和首屏体验都要顾全。

六、扩展应用

懒加载Vue3组件封装示例

如下是一个基于TypeScript和

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';

interface Props {
  src: string;
  alt?: string;
  placeholder?: string;
}

defineProps<Props>();

const imgRef = ref<HTMLImageElement | null>(null);
const loaded = ref(false);
let observer: IntersectionObserver | null = null;

onMounted(() => {
  if (imgRef.value) {
    observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          loaded.value = true;
          observer && observer.unobserve(entry.target);
        }
      });
    });
    observer.observe(imgRef.value);
  }
});

onBeforeUnmount(() => {
  if (observer && imgRef.value) {
    observer.unobserve(imgRef.value);
    observer.disconnect();
  }
});
</script>

<template>
  <img
    ref="imgRef"
    :src="loaded ? src : placeholder || ''"
    :alt="alt"
    style="width: 100%; display: block;"
  />
</template>

使用说明:

  • src:图片真实地址。
  • alt:图片描述(可选)。
  • placeholder:占位图地址(可选)。
  • 组件会在图片进入视口时自动加载真实图片。

用法示例:

<LazyImage src="https://example.com/real.jpg" placeholder="https://example.com/placeholder.jpg" alt="示例图片" />

react组件

如下是一个基于TypeScript的React图片懒加载组件示例,使用函数组件和useRef、useEffect实现:

import React, { useRef, useState, useEffect } from 'react';

interface LazyImageProps {
  src: string;
  alt?: string;
  placeholder?: string;
  style?: React.CSSProperties;
  className?: string;
}

const LazyImage: React.FC<LazyImageProps> = ({ src, alt = '', placeholder = '', style, className }) => {
  const imgRef = useRef<HTMLImageElement | null>(null);
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    const img = imgRef.current;
    if (!img) return;
    let observer: IntersectionObserver | null = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setLoaded(true);
          observer && observer.unobserve(entry.target);
        }
      });
    });
    observer.observe(img);
    return () => {
      if (observer && img) {
        observer.unobserve(img);
        observer.disconnect();
      }
    };
  }, []);

  return (
    <img
      ref={imgRef}
      src={loaded ? src : placeholder}
      alt={alt}
      style={style}
      className={className}
    />
  );
};

export default LazyImage;

使用说明:

  • src:图片真实地址。
  • alt:图片描述(可选)。
  • placeholder:占位图地址(可选)。
  • styleclassName:可自定义样式。
  • 组件会在图片进入视口时自动加载真实图片。

用法示例:

<LazyImage src="https://example.com/real.jpg" placeholder="https://example.com/placeholder.jpg" alt="示例图片" style={{ width: '100%' }} />

微信原生小程序组件封装

如下是一个微信原生小程序图片懒加载组件的封装示例,利用小程序的IntersectionObserver实现:

1. 组件结构

lazy-image.wxml

<image
  wx:if="{{show}}"
  src="{{src}}"
  alt="{{alt}}"
  class="lazy-image"
  mode="aspectFill"
/>
<image
  wx:else
  src="{{placeholder}}"
  alt="{{alt}}"
  class="lazy-image"
  mode="aspectFill"
/>

lazy-image.js

Component({
  properties: {
    src: String,
    alt: String,
    placeholder: {
      type: String,
      value: ''
    }
  },
  data: {
    show: false
  },
  lifetimes: {
    attached() {
      this.createIntersectionObserver()
        .relativeToViewport({ bottom: 100 })
        .observe('.lazy-image', (res) => {
          if (res.intersectionRatio > 0) {
            this.setData({ show: true });
            this.observer && this.observer.disconnect();
          }
        });
    },
    detached() {
      this.observer && this.observer.disconnect();
    }
  },
  methods: {
    createIntersectionObserver() {
      this.observer = this.createIntersectionObserver();
      return this.observer;
    }
  }
});

lazy-image.json

{
  "component": true
}

lazy-image.wxss

.lazy-image {
  width: 100%;
  display: block;
}