首页背景图渐进式加载

本文修改主题为AnZhiYu主题,若使用其他主题,请自行确认修改位置。

该方法通过外部引入,无需修改主题源文件。

原文出处

https://blog.kouseki.cn/posts/4f72.html
https://satera.cn/posts/6a8fb549/
https://blhorizon.github.io/posts/29ff7730.html

效果预览

查看图片

效果预览

修改步骤

以本站使用的主题anzhiyu为例(butterfly通用),为首页顶部图配置渐进式加载。这个方法应该也同样适用于一图流的博客背景,有需要的朋友可以自己研究一下~

1. 图片加载JS

新建文件[Blogroot]\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
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
113
114
115
116
117
118
// 首页头图加载优化
/**
* @description 实现medium的渐进加载背景的效果
*/

if (!window.ProgressiveLoad) {
// 定义ProgressiveLoad类
class ProgressiveLoad {
constructor(smallSrc, largeSrc) {
this.smallSrc = smallSrc;
this.largeSrc = largeSrc;
this.initScrollListener(),
this.initTpl();
}
// 这里的1是滚动全程渐变 改为0.3就是前30%渐变后固定前30%产生的渐变效果
initScrollListener() {
window.addEventListener("scroll", (()=>{
var e = Math.min(window.scrollY / window.innerHeight, 0.7);
this.container.style.setProperty("--process", e)
}
))
}
/**
* @description 生成ui模板
*/
initTpl() {
this.container = document.createElement('div');
this.smallStage = document.createElement('div');
this.largeStage = document.createElement('div');
this.video = document.createElement('div');
this.smallImg = new Image();
this.largeImg = new Image();
this.container.className = 'pl-container';
this.container.style.setProperty("--process", 0),
this.smallStage.className = 'pl-img pl-blur';
this.largeStage.className = 'pl-img';
this.video.className = 'pl-video';
this.container.appendChild(this.smallStage);
this.container.appendChild(this.largeStage);
this.container.appendChild(this.video);
this.smallImg.onload = this._onSmallLoaded.bind(this);
this.largeImg.onload = this._onLargeLoaded.bind(this);
}

/**
* @description 加载背景
*/
progressiveLoad() {
this.smallImg.src = this.smallSrc;
this.largeImg.src = this.largeSrc;
}
/**
* @description 大图加载完成
*/
_onLargeLoaded() {
this.largeStage.classList.add('pl-visible');
this.largeStage.style.backgroundImage = `url('${this.largeSrc}')`;
}

/**
* @description 小图加载完成
*/
_onSmallLoaded() {
this.smallStage.classList.add('pl-visible');
this.smallStage.style.backgroundImage = `url('${this.smallSrc}')`;
}
}

const executeLoad = (config, target) => {
// console.log('执行渐进背景替换');
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: 'https://www.loliapi.com/acg/', // 小图链接 尽可能配置小于100k的图片
largeSrc: 'https://www.loliapi.com/acg/', // 大图链接 最终显示的图片
mobileSmallSrc: 'https://www.loliapi.com/acg/', // 手机端小图链接 尽可能配置小于100k的图片
mobileLargeSrc: 'https://www.loliapi.com/acg/', // 手机端大图链接 最终显示的图片
enableRoutes: ['/'],
};

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);
}
}

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

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

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

}

2. 新建样式文件

新建文件[Blogroot]\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
51
52
53
54
/* 首页头图加载 */
.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) translateZ(0);
opacity: 0;
}

to {
transform: scale(1) translateZ(0);
opacity: 1;
}
}

.pl-visible {
opacity: 1;
}

.pl-blur {
/* 小图锯齿多,增加高斯模糊 */
filter: blur(50px);
}

3. 引入JS和CSS

_config.anzhiyu.yml主题配置文件下inject配置项中headbottom处引入两个文件

1
2
3
4
5
6
inject:
head:
+ - <link rel="stylesheet" href="/css/imgloaded.css?1">

bottom:
+ - <script async data-pjax src="/js/imgloaded.js?1"></script> # 首页图片渐进式加载

4. 配置顶图

配置顶图,务必记得在主题配置文件中开启顶部图的功能,也可以像我这样配置空链接。因为js文件已经接替了图片加载功能,此处不需要配置图片(当然你也可以配置上)

1
index_img: "background: url() top / cover no-repeat"
1
2
3
4
5
6
7
const config = {
smallSrc: '/img/xiaotu.jpg', // 小图链接 尽可能配置小于100k的图片
largeSrc: '/img/tu.jpg', // 大图链接 最终显示的图片
mobileSmallSrc: '/img/sjxt.jpg', // 手机端小图链接 尽可能配置小于100k的图片
mobileLargeSrc: '/img/sjdt.jpg', // 手机端大图链接 最终显示的图片
enableRoutes: ['/'],
};

图片懒加载配置修改

为了使图片模糊效果更加平滑,可以尝试修改lazyload配置项。
在主题配置文件中找到lazyload,将field项改为postblur维持true

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

常见问题

  • 首页图下方的有个奇怪的边界的情况(还有一图流的时候,自行设计渐变)
  • 如果大图的下边界有不透明度变化,模糊小图,小图会超出不透明度范围,露出小图
  • 如果开了夜间模式,是因为由夜间模式的阅读模式叠加一层0.3的alpha,具体是[Blogroot]\themes\anzhiyu\source\css\_mode\darkmode.styl文件里的background-color: alpha($dark-black, 0.3),改为
    1
    2
    3
    4
    5
    6
    background-image: linear-gradient(
    to bottom,
    rgba($dark-black, 0.1) 0%,
    rgba($dark-black, 0) 75%,
    rgba($dark-black, 0) 100%
    );

拓展修改

此方法修改后,首页背景图的加载效果可以达到较好的效果,但其他页面并不会加载首页的背景图,如果需要所有页面都加载,则需要进一步修改。
这里我选择直接将背景图挂载到 #web_bg 元素下,直接当做背景,这样可以实现所有页面都加载首页背景图。

修改步骤

修改[Blogroot]\themes\anzhiyu\layout\includes\layout.pug文件

此步骤是因为JS文件中挂载元素会先检查有无full_page属性,所以需要先将header元素加上full_page属性。你也可以选择去除属性检查,直接挂载到 #web_bg 元素下,忽略此步骤。

1
2
3
    #web_bg
+ header#web-header(class="full_page")
#an_music_bg

修改[Blogroot]\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
51
52
53
body[style*="--animate: true"] #nav,
body[style*="--animate: true"] #scroll-down,
body[style*="--animate: true"] #site-info {
-webkit-animation: scale 2.2s cubic-bezier(.6, .1, .25, 1) .5s 1 backwards;
animation: scale 2.2s cubic-bezier(.6, .1, .25, 1) .5s 1 backwards
}

@media screen and (max-width: 768px) {
.pl-container {
top: 0;
}
}

@media screen and (min-width: 768px) {

#page-header.full_page,
#web-header.full_page,
.pl-container {
height: 100vh
}

#page-header.full_page.expand-to-full,
#web-header.full_page.expand-to-full,
.pl-container.expand-to-full {
height: 50vh !important
}

.pl-container {
will-change: opacity, transform, filter;
opacity: calc(1 - var(--process) * 1) !important;
transform: scale(calc(1 + var(--process) * .1));
filter: blur(calc(var(--process) * 10px));
}
}

.pl-container {
width: 100%;
height: 100%;
left: 0;
position: fixed;
z-index: -2;
overflow: hidden;
}

body[style*="--animate: true"] .pl-container {
will-change: transform;
animation: blur-to-clear 2.5s cubic-bezier(.6, .25, .25, 1) 0s 1 backwards,
scale 2.2s cubic-bezier(.6, .1, .25, 1) .5s 1 backwards;
}

[data-theme=dark] .pl-container {
filter: brightness(0.6)
}

修改[Blogroot]\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
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// 首页头图加载优化
/**
* @description 实现medium的渐进加载背景的效果
*/

function getRandomImage(platform) {
const num = Math.floor(Math.random() * 300) + 100;
if (platform === 'pc') {
return `https://img.loliapi.com/i/pc/img${num}.webp`;
} else if (platform === 'pe') {
return `https://img.loliapi.com/i/pe/img${num}.webp`;
}
}

// 判断initLargeSrc和initSmallSrc是否已经定义,如果没有定义则随机获取图片链接
if (typeof initLargeSrc === 'undefined') {
initLargeSrc = getRandomImage('pc');
}
if (typeof initSmallSrc === 'undefined') {
initSmallSrc = getRandomImage('pe');
}

if (!window.ProgressiveLoad) {
// 定义ProgressiveLoad类
class ProgressiveLoad {
constructor(smallSrc, largeSrc) {
this.smallSrc = smallSrc;
this.largeSrc = largeSrc;
this.initTpl();
this.initScrollListener();
}
// 这里的1是滚动全程渐变 改为0.3就是前30%渐变后固定前30%产生的渐变效果
initScrollListener() {
const header = document.getElementById('page-header');
if (header && header.classList.contains('full_page')) {
document.body.style.setProperty('--animate', 'true');
window.addEventListener("scroll", (()=>{
var e = Math.min(window.scrollY / window.innerHeight, 0.7);
this.container.style.setProperty("--process", e)
}
))
} else {
this.container.style.setProperty("--process", 0.7)
}
}
/**
* @description 生成ui模板
*/
initTpl() {
this.container = document.createElement('div');
this.smallStage = document.createElement('div');
this.largeStage = document.createElement('div');
this.video = document.createElement('div');
this.smallImg = new Image();
this.largeImg = new Image();
this.container.className = 'pl-container';
this.container.style.setProperty("--process", 0),
this.smallStage.className = 'pl-img pl-blur';
this.largeStage.className = 'pl-img';
this.video.className = 'pl-video';
this.container.appendChild(this.smallStage);
this.container.appendChild(this.largeStage);
this.container.appendChild(this.video);
this.smallImg.onload = this._onSmallLoaded.bind(this);
this.largeImg.onload = this._onLargeLoaded.bind(this);
}

/**
* @description 加载背景
*/
progressiveLoad() {
this.smallImg.src = this.smallSrc;
this.largeImg.src = this.largeSrc;
}
/**
* @description 大图加载完成
*/
_onLargeLoaded() {
this.largeStage.classList.add('pl-visible');
this.largeStage.style.backgroundImage = `url('${this.largeSrc}')`;
}

/**
* @description 小图加载完成
*/
_onSmallLoaded() {
this.smallStage.classList.add('pl-visible');
this.smallStage.style.backgroundImage = `url('${this.smallSrc}')`;
}
}

const executeLoad = (config, target) => {
console.log('执行渐进背景替换');
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.length > 0) {
target.insertBefore(loader.container, target.children[0]);
} else {
target.appendChild(loader.container);
}
loader.progressiveLoad();
};

const config = {
smallSrc: initLargeSrc, // 小图链接 尽可能配置小于100k的图片
largeSrc: initLargeSrc, // 大图链接 最终显示的图片
mobileSmallSrc: initSmallSrc, // 手机端小图链接 尽可能配置小于100k的图片
mobileLargeSrc: initSmallSrc, // 手机端大图链接 最终显示的图片
enableRoutes: ['/'],
};

function initProgressiveLoad(config) {
// 每次加载前先清除已有的元素
const container = document.querySelector('.pl-container');
if (container) {
container.remove();
}
const target = document.getElementById('web-header');
if (target && target.classList.contains('full_page')) {
executeLoad(config, target);
}
}

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

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

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

}

修改#web_bg的css样式,将其改为背景图样式,可以加在你的自定义样式文件中

1
2
3
4
5
6
7
8
9
10
11
12
#web_bg {
position: fixed;
transform: rotate(0deg);
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
z-index: -2;
}