在这篇文章中,我们学习了如何设置固定数量的动态背景图。接下来,我们在这篇文章的基础上继续增加功能,让动态背景图的数量也不是固定的、图像也不是固定的。
顺便一提,预览效果点击这里查看。
在这里,我个人想从两个方面解释动态。
一方面是当前呈现的图片是不断轮转的,这是轮播图的基本定义。
另一方面就是呈现的图片并不是一开始就固定好的,而是在轮播的过程中不断更新的。
第一个方面我们已经实现了,在这里也就不多赘述。接下来我们思考如何实现第二个方面。
为了响应速度更快,我个人倾向于将loading
期间加载的内容固定下来,主要是为了能够让loading
期间有一个基本的轮播图。
然后,在第一张图片轮转消失后,更新第一张图片。这样的话,显示的是第二张图片,而第一张图片已经在你看不见的地方更新掉了。当回到第一张图片的时候,我们发现轮转出来的是一张新的图片。
这样就能够分两个阶段给出一大批背景图片了。
还记得上一篇文章吗,我们使用 64 s 64s 64s的时间作为一整轮的轮播动画时间。这也就意味着,算上淡入淡出,一张图片的整个周期也就是 64 ÷ 4 = 16 ( s ) 64{\div}4=16(s) 64÷4=16(s)
为了让第一张图片在这个周期之外更新,我们所需要做的,首先就是确定 16 s 16s 16s的周期执行更新动作。
但是,光有这个 16 s 16s 16s可不够。我们都知道,当浏览器加载过一次图片之后,下一次加载该图片将从缓存中直接读取,也就是我们寻常所说的命中缓存。
命中后,加载速度是相当迅猛的。我们不能赌他的枪里没有子弹。所以我们还需要额外设计一个定时器,要让这个 16 s 16s 16s的周期稍微缓上一缓,才能够保证更新时间是处于我们所看不到的时间段内。
还记得上一篇文章的流程图吗:
我们主要是额外增加一个逻辑,让图片能够跟着时间有些修改:
当然,除了mod
计算,还有满 4 4 4归零的方法,可以画成这样:
需要注意的是,在这里更新图片并不是与图片动画同步的。我这边设置的是当图片转过去之后再更改,也就是说,这里还有一个判断当前轮转索引与修改索引之间的关系。
在这里,主要讨论的就是更新图片过程中,我们是修改src
还是直接将整个img
标签替换掉。
如果你有一定的经验,你会发现,如果src
相同,则会直接命中缓存,不会更新图片。对于我们目前而言似乎确实可以使用,因为我们的背景图片并不会随意修改,而是始终保持链接。 当然,如果你有更多的经验,你会想到,在GET
请求中加上一个时间戳,浏览器就会误以为这是一次新的请求,能够保证始终更新图片。
而如果是整个img
直接替换掉,浏览器就会开始渲染流程,能够确保本次请求是一定能够被加载的。
当然,每种方法都有自己的优势跟弊端,这就需要各位自行判断了。接下来我将以替换整个img
为例进行说明。
好了,又到了show me the code
环节。
我们还是选择source/js/utils.js
文件,在我们最开始增加的addBackgroundImageDiv
方法的最下面继续增加:
// 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 = " +
" 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!
这些代码能够按照约定更新图片,达到更多图片的轮播效果。虽然本文展示案例的时候只用了 2 2 2个图片(有 4 4 4个是上一篇固定的),但是使用本文的代码就能够实现更多的图片了。
同时,在代码中我也留下了三个节点的console.log
方法,能够让各位能够感受到浏览器页面加载状态改变为complete
的时候、TimeOut
被触发的时候、Interval
被触发的时候。
如果不出意外的话,complete
输出将会很快出现,然后在 2 s 2s 2s后出现timeout
,接下来每 16 s 16s 16s产生changed
输出。
如果你是单纯复制本篇文章中的内容,可能并不能运行起来。因为诸如DEVICES
、BASE_URL
这类变量是上一篇文章中定义的变量。
所以,在这里,我将给出两篇文章的全部代码:
// 这是官方的自启动函数
(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 = " +
" 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 = " +
" 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!
}
到这里,动态修改、动态显示的背景图片就实现了。
我们通过植入source/js/utils.js
的形式实现了功能的植入,现在我们将这一整段拉出来,放在外面,并用config.yml
控制是否打开开关。我们先看看官方示例。
你可能会怀疑哪里来的官方示例,那当然是源码中给的提示。比如NexT
可以在themes/next/layout/_scripts/index.njk
下看到这些内容:
{%- include 'vendors.njk' -%}
{{- next_js('comments.js') }}
{{- next_js('utils.js') }}
{%- if theme.motion.enable %}
{{- next_js('motion.js') }}
{%- endif %}
{%- if theme.scheme === 'Muse' or theme.scheme === 'Mist' %}
{{- next_js('schemes/muse.js') }}
{%- endif %}
{{- next_js('next-boot.js') }}
{%- if theme.bookmark.enable %}
{{- next_js('bookmark.js') }}
{%- endif %}
{%- if theme.pjax %}
{{- next_js('pjax.js') }}
{%- endif %}
然后去看themes/next/source/js
文件夹下,还果然就是这些文件。
那么就是说,我只需要将代码放在这个文件夹下,然后再去themes/next/layout/_scripts/index.njk
中引用不就好了?
于是,把上面的完整代码单独拉出来放入独立的javascript
文件中,命名为add-background.js
。同时,为了方便管理,把add-background.js
文件放入自行创建的themes/next/source/js/custom
文件夹中,然后引入:
{%- if theme.transparent.enable %}
{{- next_js('custom/add-background.js') }}
{%- endif %}
于是,现在背景图片的轮播就能够随意开关了。只需要设置:
transparent:
enable: true
就能够打开轮播,同样的,false
能够关闭轮播。
比较尴尬的是,现在还需要长期维护这个列表。
后续的话,可能会在服务器端放一个管理器,或者在Hexo
段放一个读取服务器所有文件的东西。也指不定什么时候写,总之先给一个优化思路在这里。