背景 图片懒加载(Lazyload)是减少网络访问流量,提升网页访问性能的技巧。目前Hexo已有多种方式支持懒加载,例如hexo-lazyload-image , hexo-lazyload , NexT Theme懒加载开关 等等。然而,现有的懒加载/加载动画实现均存在局限性:
不支持加载动画 : 如NexT Theme懒加载开关 ;不兼容Fancybox等图片展示插件 : 如hexo-lazyload , hexo-lazyload-element , Fancybox的显示完全异常;对Fancybox等图片展示插件兼容性不佳 : 如hexo-lazyload-image 配合Fancybox多组图片显示时,其他图片始终显示为加载中;对JS concator兼容性不佳 : 如hexo-lazyload-image ,针对Fancybox的简单修复脆弱依赖于脚本和Fancybox的加载顺序,会被JS concator破坏。JS concator功能来自hexo-all-minifier 插件。作用为合并所有脚本文件为单个js文件,减少网页加载时的请求数量。
上述实现的主要问题来源于技术实现思路不够健壮——主要以修改<img>
的src
字段的方式实现加载动画,因此导致了:
依赖js脚本在原图加载完成后替换回src
; 与Fancybox等图片展示插件发生冲突: ① 需要主动适配 :例如在图片加载完成后修改src
以及Fancybox产生的其他DOM元素; ② Fancybox产生的其他DOM元素多且复杂 :用户在加载完成前是/否点开了Fancybox展示框、Fancybox展示框是否展示了多组懒加载图片等,难以正确全部修改; ③ 网页加载时序敏感 :图片是否在脚本执行前已被缓存会影响src
的替换逻辑;JS concator可能会调整Fancybox脚本的执行顺序。 技术路线 加载动画 为了避免上述问题,核心思路是避免动态修改<img>
的src
属性,以便让Fancybox等其他插件能够读取正确的图片URL。一个简单的实现思路是:
使用CSSbackground
属性在图片完整显示前显示加载图片; 图片加载完成后,删除图片的background
属性(否则透过透明图片仍可看到背景)。 懒加载 目前,主流浏览器已兼容了<img loading="lazy">
属性[1] 。
该属性允许浏览器自行使用启发式算法,以原生的方式优化图片的加载时机。
使用该方法可以降低懒加载策略实现的复杂度和不稳定性。
实现 下面在Hexo项目中添加懒加载插件。
代码结构 1 2 3 4 5 6 7 . ├─_config.yml ├─package.json └─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
代码要点 :
class="loading"
样式需要添加大小限制min-width: 200px; min-height: 200px
,大小最好和加载动画的大小相同 。因为在图片加载完成前,背景图片是不占体积的,因此加载动画并不显示,需要设置大小将<img>
撑开;而部分格式的图片(png
, webp
等)在加载完成前就可以逐步显示,此时<img>
会被设为原图的大小而自然撑开,因此不能直接使用width
, height
样式,而使用min-width
, min-height
样式兜底。使用pjax技术打开的网页不会触发DOMContentLoaded
事件,因此注入的脚本injectScript
需要改为监听pjax:success
事件。 注入的脚本injectScript
需要判断图像是否已加载完毕,已加载完毕的图像不会再触发onload
事件,需要立刻清除class="loading"
样式,以正确处理图像已缓存等情况。 相关工具 可以使用下面的网站创建高性能的加载动画(SVG
格式)。SVG
矢量格式支持动画,且体积较小,通常不超过10KiB,是作为加载动画的理想选择。
loading.io - Your SVG + GIF + PNG Ajax Loading Icons and Animation Generator
可以使用F12 开发者工具,审查元素获取定制好的加载动画的SVG代码,保存到.svg
文件中即可,无需注册账户。
效果展示 点击查看