Hexo设置更多的动态背景图

前言

这篇文章中,我们学习了如何设置固定数量的动态背景图。接下来,我们在这篇文章的基础上继续增加功能,让动态背景图的数量也不是固定的、图像也不是固定的。

动态的背景图

在这里,我个人想从两个方面解释动态

一方面是当前呈现的图片是不断轮转的,这是轮播图的基本定义。

另一方面就是呈现的图片并不是一开始就固定好的,而是在轮播的过程中不断更新的。

第一个方面我们已经实现了,在这里也就不多赘述。接下来我们思考如何实现第二个方面。

更新图库

为了响应速度更快,我个人倾向于将loading期间加载的内容固定下来,主要是为了能够让loading期间有一个基本的轮播图。

然后,在第一张图片轮转消失后,更新第一张图片。这样的话,显示的是第二张图片,而第一张图片已经在你看不见的地方更新掉了。当回到第一张图片的时候,我们发现轮转出来的是一张新的图片。

这样就能够分两个阶段给出一大批背景图片了。

时间计算

还记得上一篇文章吗,我们使用的时间作为一整轮的轮播动画时间。这也就意味着,算上淡入淡出,一张图片的整个周期也就是

为了让第一张图片在这个周期之外更新,我们所需要做的,首先就是确定的周期执行更新动作。

但是,光有这个可不够。我们都知道,当浏览器加载过一次图片之后,下一次加载该图片将从缓存中直接读取,也就是我们寻常所说的命中缓存

命中后,加载速度是相当迅猛的。我们不能赌他的枪里没有子弹。所以我们还需要额外设计一个定时器,要让这个的周期稍微缓上一缓,才能够保证更新时间是处于我们所看不到的时间段内。

还记得上一篇文章的流程图吗:

graph LR
    A[第一张图片]--25%-->B[第二张图片]--25%-->C[第三张图片]--25%-->D[第四张图片]--25%-->A

我们主要是额外增加一个逻辑,让图片能够跟着时间有些修改:

graph LR
    subgraph 轮播图
        A[第一张图片]--25%-->B[第二张图片]--25%-->C[第三张图片]--25%-->D[第四张图片]--25%-->A
    end
    subgraph 同步更新
        E[等待2s]-->F[等待25%的动画结束]-->G[更新图片]-->E
    end
    subgraph 计数器
        H[[count = 0]] --> I[count] --> K[count mod 4]
        G --> I
        K -->|=0| A
        K -->|=1| B
        K -->|=2| C
        K -->|=3| D
    end

当然,除了mod计算,还有满归零的方法,可以画成这样:

graph LR
    subgraph 轮播图
        A[第一张图片]--25%-->B[第二张图片]--25%-->C[第三张图片]--25%-->D[第四张图片]--25%-->A
    end
    subgraph 同步更新
        E[等待2s]-->F[等待25%的动画结束]-->G[更新图片]-->E
    end
    subgraph 计数器
        G-->I{count = 4 ?}--yes-->J[count - 4]-->H-->A
        O[[count = 0]] --> I
        H[count = 0]
        L[count = 1] --> B
        M[count = 2] --> C
        N[count = 3] --> D
        I--no-->K[+1]-->|+1| L & M & N
    end

需要注意的是,在这里更新图片并不是与图片动画同步的。我这边设置的是当图片转过去之后再更改,也就是说,这里还有一个判断当前轮转索引与修改索引之间的关系。

graph LR
    subgraph 轮播图
        A[第一张图片]--25%-->B[第二张图片]--25%-->C[第三张图片]--25%-->D[第四张图片]--25%-->A
    end
    subgraph 同步更新
        E[等待2s]-->F[等待25%的动画结束]-->G[更新图片]-->E
    end
    subgraph 计数器
        I--(count+2)%4-->P[index]--更新即触发-->G
        I{count = 4 ?}--yes-->J[count - 4]-->H-->A
        O[[count = 0]] --> I
        H[count = 0]
        L[count = 1] --> B
        M[count = 2] --> C
        N[count = 3] --> D
        I--no-->K[+1]-->|+1| L & M & N
    end

修改src属性还是直接将整个img标签替换掉

在这里,主要讨论的就是更新图片过程中,我们是修改src还是直接将整个img标签替换掉。

如果你有一定的经验,你会发现,如果src相同,则会直接命中缓存,不会更新图片。对于我们目前而言似乎确实可以使用,因为我们的背景图片并不会随意修改,而是始终保持链接。 当然,如果你有更多的经验,你会想到,在GET请求中加上一个时间戳,浏览器就会误以为这是一次新的请求,能够保证始终更新图片。

而如果是整个img直接替换掉,浏览器就会开始渲染流程,能够确保本次请求是一定能够被加载的。

当然,每种方法都有自己的优势跟弊端,这就需要各位自行判断了。接下来我将以替换整个img为例进行说明。

代码实现

好了,又到了show me the code环节。

保存位置

我们还是选择source/js/utils.js文件,在我们最开始增加的addBackgroundImageDiv方法的最下面继续增加:

增加代码

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
// set background image url after rolling
// --------------------------------------
let count = 0, index = 0;
const imgUrls = { 'pc': [
'/race-miku.jpg', '/masuri-miku.jpg', '/planet-miku.jpg', '/4mikus.jpg', '/84672028_p0.jpg', '/84932457_p0.png'
] }
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
console.log('done');
let imgChangeInterval = null;
let imageChangeTimeOut = setTimeout(function () {
console.log('timeout');
if (imgChangeInterval != null) {
clearInterval(imgChangeInterval);
imgChangeInterval = null;
}
imgChangeInterval = setInterval(function () {
index = (count + 2) % 4
const imageDivElement = document.getElementById("image-scroller").children[count % 4];
let sampleImg = Math.floor(Math.random() * imgUrls[DEVICES[0]].length);
imageDivElement.innerHTML = "<img" +
" src='" + BASE_URL + DEVICES[0] + imgUrls[DEVICES[0]][sampleImg] + "'" +
" style='width: 100%; height: 100%;'" +
" alt='network broken?' />";
console.log(`changed, now is ${count % 4} and ${imgUrls[DEVICES[0]][sampleImg]}`) // yes
count = (count + 1) % 4;
}, 64000 / 4);
clearTimeout(imageChangeTimeOut);
}, 2000);
}
}
// well done! now images can be updated!

这些代码能够按照约定更新图片,达到更多图片的轮播效果。虽然本文展示案例的时候只用了个图片(有个是上一篇固定的),但是使用本文的代码就能够实现更多的图片了。

没什么必要的其他东西

同时,在代码中我也留下了三个节点的console.log方法,能够让各位能够感受到浏览器页面加载状态改变为complete的时候、TimeOut被触发的时候、Interval被触发的时候。

如果不出意外的话,complete输出将会很快出现,然后在后出现timeout,接下来每产生changed输出。

两篇文章的全部代码

如果你是单纯复制本篇文章中的内容,可能并不能运行起来。因为诸如DEVICESBASE_URL这类变量是上一篇文章中定义的变量。

所以,在这里,我将给出两篇文章的全部代码:

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
144
145
146
147
// 这是官方的自启动函数
(function() {
const onPageLoaded = () => document.dispatchEvent(
new Event('page:loaded', {
bubbles: true
})
);
if (document.readyState === 'loading') {
document.addEventListener('readystatechange', onPageLoaded, { once: true });
addBackgroundImageDiv(); // 我在这里增加了自己的内容
} else {
onPageLoaded();
}

document.addEventListener('pjax:success', onPageLoaded);
})();

// 然后就是实现自己的内容
// add our custom dynamic background image
function addBackgroundImageDiv () {
// create dom element for background images
// ----------------------------------------
const opacityMask = document.createElement("div");
opacityMask.style.background = "linear-gradient(#fff, #ffced9, #fff)";
opacityMask.style.position = "fixed";
opacityMask.style.top = "0";
opacityMask.style.left = "0";
opacityMask.style.content = "";
opacityMask.style.width = "100%";
opacityMask.style.height = "100%";
opacityMask.style.opacity = "0.8";
opacityMask.style.zIndex = "-2";
const imageContainer = document.createElement("div");
imageContainer.style.position = "fixed";
imageContainer.style.top = "0";
imageContainer.style.left = "0";
imageContainer.style.content = "";
imageContainer.style.width = "100%";
imageContainer.style.height = "100%";
imageContainer.style.zIndex = "-3";
const imageScroller = document.createElement("div");
imageScroller.id = "image-scroller";
imageScroller.style.position = "fixed";
imageScroller.style.top = "0";
imageScroller.style.left = "0";
imageScroller.style.content = "";
imageScroller.style.width = "400%";
imageScroller.style.height = "100%";
imageScroller.style.display = "flex";
imageContainer.style.justifyContent = "space-around";
imageContainer.style.alignContent = "center";
imageContainer.style.alignItems = "center";
imageScroller.style.zIndex = "-4";
document.body.appendChild(opacityMask);
document.body.appendChild(imageContainer);
document.body.appendChild(imageScroller);
// well done! basic frames established!

// url for background images
// -------------------------
const BASE_URL = 'http://images.sakebow.cn/bgimage/'
const DEVICES = ['pc']
const imgWindowUrl = { 'pc': [
'/race-miku.jpg', '/masuri-miku.jpg', '/planet-miku.jpg', '/4mikus.jpg'
] };
for (const imgUrlItem of imgWindowUrl['pc']) {
const imageFrameItemContainer = document.createElement("div");
imageFrameItemContainer.style.width = imageContainer.style.width;
imageFrameItemContainer.style.height = "100%";
imageFrameItemContainer.innerHTML = "<img" +
" src='" + BASE_URL + DEVICES[0] + imgUrlItem + "'" +
" style='width: 100%; height: 100%;'" +
" alt='network broken?' />";
imageScroller.appendChild(imageFrameItemContainer);
}
// well done! all images ready!

// keyframe to roll images
// -------------------------------
// create style element
const imageRollStyle = document.createElement('style');
// set animation time for all
const EPOCH_TIME = 64;
// set animation style for all
const ANIMATION_DEFAULT_SETTINGS = "s linear infinite running ";
// set keyframes into style element
imageRollStyle.innerHTML = `@keyframes image-roll {
0% { left: 0; } 24% { left: 0; } 25% { left: -100%; } 49% { left: -100%; } 50% { left: -200%; }
74% { left: -200%; } 75% { left: -300%; } 99% { left: -300%; } 100%{ left: 0; }
}@keyframes image-translate-child-1 {
0% { scale: 1; opacity: 0 } 2% { scale: 1; opacity: 1; } 23% { scale: 1.1; } 25%, 100% { scale: 1.1; opacity: 0; }
}#image-scroller>div:nth-child(1) {
animation: ${EPOCH_TIME + ANIMATION_DEFAULT_SETTINGS} image-translate-child-1;
}@keyframes image-translate-child-2 {
0%, 25% { scale: 1; opacity: 0 } 27% { scale: 1; opacity: 1; } 48% { scale: 1.1; } 50%, 100% { scale: 1.1; opacity: 0; }
}#image-scroller>div:nth-child(2) {
animation: ${EPOCH_TIME + ANIMATION_DEFAULT_SETTINGS} image-translate-child-2;
}@keyframes image-translate-child-3 {
0%, 50% { scale: 1; opacity: 0 } 52% { scale: 1; opacity: 1; } 73% { scale: 1.1; } 75%, 100% { scale: 1.1; opacity: 0; }
}#image-scroller>div:nth-child(3) {
animation: ${EPOCH_TIME + ANIMATION_DEFAULT_SETTINGS} image-translate-child-3;
}@keyframes image-translate-child-4 {
0%, 75% { scale: 1; opacity: 0 } 77% { scale: 1; opacity: 1; } 98% { scale: 1.1; } 100% { scale: 1.1; opacity: 0; }
}#image-scroller>div:nth-child(4) {
animation: ${EPOCH_TIME + ANIMATION_DEFAULT_SETTINGS} image-translate-child-4;
}`;
// well done! now images can be rolling with fadeIn and fadeOut style, as well as scale 1.1x slowly

// 将style样式存放到head标签
// ----------------------
document.getElementsByTagName('head')[0].appendChild(imageRollStyle);
imageScroller.style.animation = `${EPOCH_TIME + ANIMATION_DEFAULT_SETTINGS} image-roll`;
// well done! keyframes in effect!

// set background image url after rolling
// --------------------------------------
let count = 0, index = 0;
const imgUrls = { 'pc': [
'/race-miku.jpg', '/masuri-miku.jpg', '/planet-miku.jpg', '/4mikus.jpg', '/84672028_p0.jpg', '/84932457_p0.png'
] }
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
console.log('done');
let imgChangeInterval = null;
let imageChangeTimeOut = setTimeout(function () {
console.log('timeout');
if (imgChangeInterval != null) {
clearInterval(imgChangeInterval);
imgChangeInterval = null;
}
imgChangeInterval = setInterval(function () {
index = (count + 2) % 4
const imageDivElement = document.getElementById("image-scroller").children[count % 4];
let sampleImg = Math.floor(Math.random() * imgUrls[DEVICES[0]].length);
imageDivElement.innerHTML = "<img" +
" src='" + BASE_URL + DEVICES[0] + imgUrls[DEVICES[0]][sampleImg] + "'" +
" style='width: 100%; height: 100%;'" +
" alt='network broken?' />";
console.log(`changed, now is ${count % 4} and ${imgUrls[DEVICES[0]][sampleImg]}`) // yes
count = (count + 1) % 4;
}, 64000 / 4);
clearTimeout(imageChangeTimeOut);
}, 2000);
}
}
// well done! now images can be updated!
}

到这里,动态修改、动态显示的背景图片就实现了。