图片懒加载,也可以称为延迟加载。简单来说就是默认只加载可视区内的图片,可视区外的图片默认不加载,只有图片进入可视区才加载。这里说的可视区就是你能看到的区域。对于带宽较低的服务器来说,同时加载多张图片可能会导致图片长时间无法加载出,图片懒加载就是只加载用户能看到的区域的图片,对于流量计费的服务器和用户来说,图片懒加载也可以节省流量。

浏览器检测到 imgsrc 是一个 HTTP 链接就会发送请求加载图片,先要做的就是去除 imgsrc,还可以给 img 增加一个 data-src 的自定义属性用来存放图片地址,data-src 属性里的图片地址浏览器不会请求。

最近我给我的两个 Typecho 主题 MWordStarFacile 都加入了图片懒加载,这里就来简单写一下 Typecho 主题的图片懒加载。

编写 PHP 函数

Typecho 生成的 HTML 文章内容的 img 是包含 src 的,下面编写一个函数把 src 替换为 data-src,并且 data-src 里要包含图片地址:

function replaceImgSrc($content) {
    $pattern = '/<img(.*?)src(.*?)=(.*?)"(.*?)">/i';
    $replacement = '<img$1data-src$3="$4"$5 class="load-img">';
    return preg_replace($pattern, $replacement, $content);
}

上面的 replaceImgSrc 函数可以把所有的 imgsrc 替换为 data-src,还会给 img 加入一个 load-imgclass

上面的 replaceImgSrc 函数可以直接放到 Typecho 主题的 functions.php 调用,传入需要替换的 HTML 文章内容,返回替换后的 HTML 内容。

Typecho 主题输出文章内容可以调用 $this->content() 方法,这个方法会直接输出文章内容。也可以直接读取 $this->content 属性,这样就不会输出文章内容。

下面使用上面编写的 replaceImgSrc 函数来替换和输出文章内容:

<?php echo replaceImgSrc($this->content); ?>

$this->content 可以在 post.php 文章页和 page.php 独立页面使用。

CSS 样式

一般文章内的图片宽高都不固定,CSS 基本都是使用 max-width: 100% 来设置文章内的图片尺寸 ,在图片加载完成之前,宽度就是 alt 描述文字的宽度,高度也是 alt 描述文字的高度。

下面给待加载的图片设置默认样式:

.load-img {
  width: 100%;
  height: auto;
  aspect-ratio: 16/9;
  background-color: #CCCCCC;
  display: block;
}

上面的宽度是 100%,aspect-ratio: 16/9 是设置宽高比为 16 比 9,其中 aspect-ratio 属性不支持 IE 系列浏览器,如果需要支持 IE 可以通过媒体查询来设置高度。

我在替换图片 src 的时候给图片加了一个 load-imgclass ,这个 class 就是用来设置样式和检测加载图片的,加载完成后删除 load-imgclass 就可以直接去除待加载的图片默认样式。

JavaScript 加载图片

JS 需要做的就是监听滚动条,当图片进入可视区时给图片设置 src

jQuery:

// 监听 document 的滚动条
$(document).on('scroll', function () {
  $('.load-img').each(function () {
    // 如果文章内的 img 进入可视区就加载图片
    if (
      $(this).offset().top < $(document).scrollTop() + window.innerHeight &&
      $(this).offset().top + $(this).height() > $(document).scrollTop()
    ) {
      // 如果 img 不包含 src 就加载图片
      if ($(this).attr('src') === undefined) {
        $(this).attr('src', $(this).attr('data-src'));
      }
    }
  });
});

// 如果页面加载完成时有图片在可视区就直接加载图片
$('.load-img').each(function () {
  if ($(this).offset().top < window.innerHeight) {
    $(this).attr('src', $(this).attr('data-src'));
  }
});

// 文章图片加载完成后删除 load-img 的 class
$('.load-img').on('load', function () {
  $(this).removeClass('load-img');
});

下面是详细说明:

  1. document 添加 scroll 滚动条事件来监听 document 的滚动条
  2. 判断所有包含 load-imgclassimg 是否进入可视区
  3. 如果 img 进入可视区就继续判断 img 是否包含 src,只有 img 不包含 src 才继续加载
  4. imgdata-src 传给 src

上面是滚动加载图片的部分。

除了滚动加载外,页面加载完成也需要判断一下可视区是否包含图片,如果有图片在可视区就直接加载。

图片加载完成后还需要去除默认样式,也就是删除 load-imgclassload 事件会在图片加载完成后触发。

原生 JavaScript:

const imgList = document.querySelectorAll('.load-img');  // 通过 .load-img 获取图片

// 监听 document 的滚动条
document.addEventListener('scroll', () => {
  for (let i = 0;i < imgList.length;i ++) {
    // 判断图片是否进入可视区
    if (
      imgList[i].offsetTop < document.documentElement.scrollTop + window.innerHeight &&
      imgList[i].offsetTop + imgList[i].offsetHeight > document.documentElement.scrollTop
    ) {
      // 如果文章内的 img 进入可视区就继续判断 img 是否包含 src
      if (imgList[i].getAttribute('src') === null) {
        // 如果 img 不包含 src 就加载图片
        imgList[i].setAttribute('src', imgList[i].getAttribute('data-src'));
      }
    }
  }
});

// 如果页面加载完成时有图片在可视区就直接加载图片
for (let i = 0;i < imgList.length;i ++) {
  if (imgList[i].offsetTop < window.innerHeight) {
    imgList[i].setAttribute('src', imgList[i].getAttribute('data-src'));
  }
}

// 文章图片加载完成后删除 load-img 的 class
for (let i = 0;i < imgList.length;i ++) {
  imgList[i].addEventListener('load', ev => {
    ev.target.classList.remove('load-img');
  });
}

原生 JS 的步骤和判断方法和 jQuery 是一样的。

如果不需要兼容 IE 浏览器的话,也可以使用 forEach 来遍历元素,兼容 IE 的话就只能使用 for 循环来遍历元素,Babel 不会转换 forEach

classList 是一个用来操作元素 class 的 API,IE10 和 11 都可以使用,详细的 classList 使用和兼容老 IE 的方法可以看 JavaScript 操作元素的 class

测试

如果你测试加载的图片在本地,或者是服务器速度较快的话,图片出现的一瞬间可能就已经加载完成了。要想观察图片加载过程就需要对浏览器进行限速,下面简单写一下 Chrome 浏览器使用开发者工具限速的方法:

在开发者工具的主菜单打开 更多工具 子菜单,选择 网络状况 打开网络状况面板,如下:

Chrome打开网络状况面板

网络节流 设置为 低速3G ,选中 停用缓存 ,如下:

Chrome网络面板设置节流和停用缓存

刷新网页后,网页和图片的加载都会变得很慢,可以用来测试图片懒加载。关闭开发者工具就能恢复正常。