Anzhiyu主题 一图流渐进式加载

安知鱼主题 - 一图流渐进式加载教程

本文介绍如何在安知鱼主题下为 Hexo 博客实现图片渐进式加载效果。通过先加载低分辨率并模糊处理的小图,再在后台加载高清大图的方式,可以有效提升页面加载体验,避免加载大图时页面空白或卡顿。

渐进式加载原理:先加载体积小的小图(建议小于100k),对其应用高斯模糊效果,待大图加载完成后替换显示。


效果预览


1. 功能简介

使用渐进式加载技术,可以先展示模糊效果的小图,随后自动切换为高清大图。此方案适用于首页顶部图及一图流背景,提升了视觉体验和加载速度。


2. 操作步骤

本教程分为两部分:

  1. 首页顶部图渐进式加载
  2. 首页一图流渐进式加载

2.1 首页顶部图渐进式加载

2.1.1 JS 文件配置 (imgloaded.js)

themes/anzhiyu/source/js/ 目录下新建 imgloaded.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 首页头图加载优化
/**
* 实现图片渐进加载效果
*/
class ProgressiveLoad {
constructor(smallSrc, largeSrc) {
this.smallSrc = smallSrc;
this.largeSrc = largeSrc;
this.initTpl();
}
// 生成UI模板
initTpl() {
this.container = document.createElement('div');
this.smallStage = document.createElement('div');
this.largeStage = document.createElement('div');
this.smallImg = new Image();
this.largeImg = new Image();
this.container.className = 'pl-container';
this.smallStage.className = 'pl-img pl-blur';
this.largeStage.className = 'pl-img';
this.container.appendChild(this.smallStage);
this.container.appendChild(this.largeStage);
this.smallImg.onload = this._onSmallLoaded.bind(this);
this.largeImg.onload = this._onLargeLoaded.bind(this);
}
// 开始加载图片
progressiveLoad() {
this.smallImg.src = this.smallSrc;
this.largeImg.src = this.largeSrc;
}
// 大图加载完成
_onLargeLoaded() {
this.largeStage.classList.add('pl-visible');
this.largeStage.style.backgroundImage = `url('${this.largeSrc}')`;
}
// 小图加载完成
_onSmallLoaded() {
this.smallStage.classList.add('pl-visible');
this.smallStage.style.backgroundImage = `url('${this.smallSrc}')`;
}
}

const executeLoad = (config, target) => {
const isMobile = window.matchMedia('(max-width: 767px)').matches;
const loader = new ProgressiveLoad(
isMobile ? config.mobileSmallSrc : config.smallSrc,
isMobile ? config.mobileLargeSrc : config.largeSrc
);
if (target.children[0]) {
target.insertBefore(loader.container, target.children[0]);
}
loader.progressiveLoad();
};

const config = {
smallSrc: '/img/web.jpg', // 小图(建议小于100k)
largeSrc: '/img/web.jpg', // 大图
mobileSmallSrc: '/img/web.jpg', // 手机端小图
mobileLargeSrc: '/img/web.jpg', // 手机端大图
enableRoutes: ['/'],
};

function initProgressiveLoad(config) {
const target = document.getElementById('page-header');
if (target && target.classList.contains('full_page')) {
executeLoad(config, target);
}
}

document.addEventListener("DOMContentLoaded", function() {
initProgressiveLoad(config);
});
document.addEventListener("pjax:complete", function() {
initProgressiveLoad(config);
});

2.1.2 CSS 文件配置 (imgloaded.css)

themes/anzhiyu/source/css/ 目录下新建 imgloaded.css 文件,内容如下:

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
/* 首页头图加载样式 */
.pl-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
will-change: transform;
animation: blur-to-clear 2s cubic-bezier(.62,.21,.25,1) 0s 1 normal backwards running, scale 1.5s cubic-bezier(.62,.21,.25,1) 0s 1 both;
}
.pl-img {
width: 100%;
height: 100%;
position: absolute;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 1s;
}

@keyframes blur-to-clear {
0% {
filter: blur(50px);
opacity: 1;
}
100% {
filter: blur(0);
opacity: 1;
}
}

@keyframes scale {
0% {
transform: scale(1.5);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}

.pl-visible {
opacity: 1;
}
.pl-blur {
filter: blur(50px);
}

2.2 首页一图流渐进式加载

2.2.1 JS 文件配置 (imgloaded.js)

如果要实现首页“一图流”背景加载,需修改或新建 imgloaded.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 首页一图流加载优化
(function() {
class ProgressiveLoad {
constructor(smallSrc, largeSrc) {
this.smallSrc = smallSrc;
this.largeSrc = largeSrc;
this.initTpl();
// 小图加载完成后隐藏小图层
this.container.addEventListener('animationend', () => {
this.smallStage.style.display = 'none';
}, {once: true});
}
initTpl() {
this.container = document.createElement('div');
this.smallStage = document.createElement('div');
this.largeStage = document.createElement('div');
this.smallImg = new Image();
this.largeImg = new Image();
this.container.className = 'pl-container';
this.smallStage.className = 'pl-img pl-blur';
this.largeStage.className = 'pl-img';
this.container.appendChild(this.smallStage);
this.container.appendChild(this.largeStage);
this.smallImg.onload = this._onSmallLoaded.bind(this);
this.largeImg.onload = this._onLargeLoaded.bind(this);
}
progressiveLoad() {
this.smallImg.src = this.smallSrc;
this.largeImg.src = this.largeSrc;
}
_onLargeLoaded() {
this.largeStage.classList.add('pl-visible');
this.largeStage.style.backgroundImage = `url('${this.largeSrc}')`;
}
_onSmallLoaded() {
this.smallStage.classList.add('pl-visible');
this.smallStage.style.backgroundImage = `url('${this.smallSrc}')`;
}
}

const executeLoad = (config, target) => {
const isMobile = window.matchMedia('(max-width: 767px)').matches;
const loader = new ProgressiveLoad(
isMobile ? config.mobileSmallSrc : config.smallSrc,
isMobile ? config.mobileLargeSrc : config.largeSrc
);
if (target.children[0]) {
target.insertBefore(loader.container, target.children[0]);
}
loader.progressiveLoad();
};

const ldconfig = {
light: {
smallSrc: '/img/web.jpg', // 浅色模式小图
largeSrc: '/img/web.jpg', // 浅色模式大图
mobileSmallSrc: '/img/web.jpg',
mobileLargeSrc: '/img/web.jpg',
enableRoutes: ['/'],
},
dark: {
smallSrc: '/img/web.jpg', // 深色模式小图
largeSrc: '/img/web.jpg', // 深色模式大图
mobileSmallSrc: '/img/web.jpg',
mobileLargeSrc: '/img/web.jpg',
enableRoutes: ['/'],
},
};

const getCurrentTheme = () => {
return document.documentElement.getAttribute('data-theme');
}

const onThemeChange = () => {
const currentTheme = getCurrentTheme();
const config = ldconfig[currentTheme];
initProgressiveLoad(config);
document.addEventListener("DOMContentLoaded", function() {
initProgressiveLoad(config);
});
document.addEventListener("pjax:complete", function() {
initProgressiveLoad(config);
});
}

const initTheme = getCurrentTheme();
const initConfig = ldconfig[initTheme];
initProgressiveLoad(initConfig);

const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.attributeName === "data-theme" && location.pathname === '/') {
onThemeChange();
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"]
});

function initProgressiveLoad(config) {
const container = document.querySelector('.pl-container');
if (container) {
container.remove();
}
const target = document.getElementById('page-header');
if (target && target.classList.contains('full_page')) {
executeLoad(config, target);
}
}
})();

2.2.2 CSS 文件配置 (imgloaded.css)

themes/anzhiyu/source/css/ 下新建或修改 imgloaded.css 文件,内容如下:

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
/* 首页一图流加载样式 */
.pl-container {
width: 100%;
height: 100%;
z-index: -2;
position: fixed;
overflow: hidden;
will-change: transform;
animation: blur-to-clear 2s cubic-bezier(.62,.21,.25,1) 0s 1 normal backwards running, scale 1.5s cubic-bezier(.62,.21,.25,1) 0s 1 both;
}
.pl-img {
width: 100%;
height: 100%;
position: absolute;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 1s;
}

@keyframes blur-to-clear {
0% {
filter: blur(50px);
opacity: 1;
}
100% {
filter: blur(0);
opacity: 1;
}
}

@keyframes scale {
0% {
transform: scale(1.5);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}

.pl-visible {
opacity: 1;
}

.pl-blur {
filter: blur(50px);
}

3. 文件引入

在主题配置文件 _config.anzhiyu.yml 中的 inject 项目下,添加以下引用代码:

1
2
3
4
5
inject:  
head:
- <link rel="stylesheet" href="/css/imgloaded.css?1">
bottom:
- <script async data-pjax src="/js/imgloaded.js?1"></script>

4. 配置图片

  • 开启顶部图片功能:在主题配置文件中将 top_image 设置为 true
  • 由于图片加载和渲染由 JS 完成,因此无需在配置文件中提供图片 URL,只需在 JS 中修改相应图片链接即可。

示例配置:

1
2
# 首页顶部图配置(仅用于背景占位,不影响实际图片加载)
index_img: "background: url() top / cover no-repeat"

5. 图片懒加载配置(可选)

如果同时需要使用图片懒加载功能,可在主题配置文件中添加如下配置:

1
2
3
4
5
6
lazyload:
enable: true
field: post
placeholder:
blur: true
progressive: true

6. 完成

按照以上步骤配置好后,执行 Hexo 的 cleangenerateserver 命令,即可查看渐进式加载效果。
如图片文件路径和链接设置正确,页面加载时会先显示经过模糊处理的小图,随后自动替换为高清大图。