Hexo最佳实践:图片懒加载及加载动画

背景

图片懒加载(Lazyload)是减少网络访问流量,提升网页访问性能的技巧。目前Hexo已有多种方式支持懒加载,例如hexo-lazyload-image, hexo-lazyload, NexT Theme懒加载开关等等。然而,现有的懒加载/加载动画实现均存在局限性:

  1. 不支持加载动画
    NexT Theme懒加载开关
  2. 不兼容Fancybox等图片展示插件
    hexo-lazyload, hexo-lazyload-element, Fancybox的显示完全异常;
  3. 对Fancybox等图片展示插件兼容性不佳
    hexo-lazyload-image配合Fancybox多组图片显示时,其他图片始终显示为加载中;
  4. 对JS concator兼容性不佳
    hexo-lazyload-image,针对Fancybox的简单修复脆弱依赖于脚本和Fancybox的加载顺序,会被JS concator破坏。

JS concator功能来自hexo-all-minifier插件。作用为合并所有脚本文件为单个js文件,减少网页加载时的请求数量。

上述实现的主要问题来源于技术实现思路不够健壮——主要以修改<img>src字段的方式实现加载动画,因此导致了:

  1. 依赖js脚本在原图加载完成后替换回src
  2. 与Fancybox等图片展示插件发生冲突:
    需要主动适配:例如在图片加载完成后修改src以及Fancybox产生的其他DOM元素;
    Fancybox产生的其他DOM元素多且复杂:用户在加载完成前是/否点开了Fancybox展示框、Fancybox展示框是否展示了多组懒加载图片等,难以正确全部修改;
    网页加载时序敏感:图片是否在脚本执行前已被缓存会影响src的替换逻辑;JS concator可能会调整Fancybox脚本的执行顺序。

技术路线

加载动画

为了避免上述问题,核心思路是避免动态修改<img>src属性,以便让Fancybox等其他插件能够读取正确的图片URL。一个简单的实现思路是:

  1. 使用CSSbackground属性在图片完整显示前显示加载图片;
  2. 图片加载完成后,删除图片的background属性(否则透过透明图片仍可看到背景)。

懒加载

目前,主流浏览器已兼容了<img loading="lazy">属性[1]

该属性允许浏览器自行使用启发式算法,以原生的方式优化图片的加载时机。

使用该方法可以降低懒加载策略实现的复杂度和不稳定性。

实现

下面在Hexo项目中添加懒加载插件。

代码结构
1
2
3
4
5
6
7
.
├─_config.yml # 添加插件配置
├─package.json # 添加插件package
└─libs # 第三方插件库目录
└─hexo-lazyload-animation
├─index.js
└─package.json
/libs/hexo-lazyload-animation/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
'use strict';


const terser = require('terser');


const processPost = (data) => {
data.content = data.content.replace(/<img(.*?)>/gi, (_, p1) => {
if (!p1.includes('class=')) {
p1 = `${p1} class="loading"`;
} else {
p1 = p1.replace(/class="([^"]*)"/, 'class="$1 loading"')
}
if (!!p1.includes('loading=')) {
p1 = `${p1} loading="lazy"`;
}
return `<img${p1}>`;
});
return data;
};


const addScript = function (htmlContent) {
const injectStyle = `<style>img.loading{background:url('${hexo.config.lazyload.loadingImg}') no-repeat center center;min-width:200px;min-height:200px}</style>`;


const injectScript = `<script>${terser.minify_sync(`
const listener = () => {
document.querySelectorAll('img.loading').forEach(img => {
if (img.complete) {
img.classList.remove('loading');
} else {
img.onload = function () {
this.classList.remove('loading');
}
}
});
}
document.addEventListener('DOMContentLoaded', listener);
document.addEventListener('pjax:success', listener);
`).code}</script>`;


return htmlContent.replace('</body>', `${injectStyle}${injectScript}</body>`);
};


if (!hexo.config.lazyload || !hexo.config.lazyload.enable || !hexo.config.lazyload.loadingImg) {
return;
}


hexo.extend.filter.register('after_post_render', processPost);
hexo.extend.filter.register('after_render:html', addScript);
/libs/hexo-lazyload-animation/package.json
1
2
3
4
5
6
7
8
9
10
{
"name": "hexo-lazyload-animation",
"version": "0.0.1",
"description": "a hexo plugin to add loading animation to the images.",
"main": "index.js",
"scripts": {},
"dependencies": {
"terser": "^5.29.1"
}
}
/package.json
1
2
3
4
5
6
7
8
{
...,
"dependencies": {
...,
"hexo-lazyload-animation": "file:./libs/hexo-lazyload-animation"
},
...
}
/_config.yml
1
2
3
lazyload:
enable: true
loadingImg: /images/loading.svg # 这里替换成你的加载动画URL

代码要点

  1. class="loading"样式需要添加大小限制min-width: 200px; min-height: 200px大小最好和加载动画的大小相同。因为在图片加载完成前,背景图片是不占体积的,因此加载动画并不显示,需要设置大小将<img>撑开;而部分格式的图片(png, webp等)在加载完成前就可以逐步显示,此时<img>会被设为原图的大小而自然撑开,因此不能直接使用width, height样式,而使用min-width, min-height样式兜底。
  2. 使用pjax技术打开的网页不会触发DOMContentLoaded事件,因此注入的脚本injectScript需要改为监听pjax:success事件。
  3. 注入的脚本injectScript需要判断图像是否已加载完毕,已加载完毕的图像不会再触发onload事件,需要立刻清除class="loading"样式,以正确处理图像已缓存等情况。

相关工具

可以使用下面的网站创建高性能的加载动画(SVG格式)。SVG矢量格式支持动画,且体积较小,通常不超过10KiB,是作为加载动画的理想选择。

loading.io - Your SVG + GIF + PNG Ajax Loading Icons and Animation Generator

可以使用F12开发者工具,审查元素获取定制好的加载动画的SVG代码,保存到.svg文件中即可,无需注册账户。

效果展示

点击查看

壁纸1

壁纸2

壁纸3

壁纸4

壁纸5

壁纸6


  1. 1.懒加载 - Web 性能 | MDN