1. 引言

【butterfly 官方文档】

当进入网页时,因为加载速度的问题,可能会导致 top_img 图片出现断层显示,或者网页加载不全而出现等待时间,开启 preloader 后,会显示加载动画,等页面加载完,加载动画会消失。

没有开启加载动画情况下,页面会由于部分资源加载不完全导致显示异常(比如封面图没出来的话会把背景底色露出来)。加载动画不光可以解决潜在的显示异常(省去很多测试时间),同时也能提高网页浏览的整体流畅度。

但是 butterfly 官方提供的默认动画比较单一(见下图),而且不匹配页面整体 UI 风格,便着手考虑修改此加载动画效果。

img

本文主要分两部分内容,第一部分的主要内容参考了

文章作者: 百里飞洋 Barry-Flynn
文章链接: https://blog.meta-code.top/2022/06/18/2022-73/

第一部分 中,本文将介绍涉及自定义动画的所有文件以及修改指导。在原文基础上,会额外给出每段修改代码的注释意见,方便理解。

而在 第二部分 中,本文将介绍利用 CodePen 网站 搜索并制作(修改)简单的 CSS 动画,有个人想法可以根据其底层逻辑进行个性化创作。


2. 基础配置

2.1 修改 pug 文件(HTML)

[!NOTE]

Pug 是一种简洁的 HTML 模板引擎,使用缩进表示 HTML 结构。它通常用于生成结构化的 HTML 文件, 适用于 Node.js 应用程序。

修改 \themes\butterfly\layout\includes\loading\fullpage-loading(loading.pug)

【修改前】

#loading-box
# 帷幕元素
.loading-left-bg
.loading-right-bg
# 动画主体元素
.spinner-box
.configure-border-1
.configure-core
.configure-border-2
.configure-core
.loading-word= _p('loading')
script().
...

Pug 代码创建了默认加载动画的 HTML 结构,但是默认情况下,渲染器会直接读取 loading.pug 中描述的动画结构,当我们存在不同的动画模板时,调整将变得困难。

里我们需要将每个动画固定的 HTML 代码打包成单独的文件,并在设置参数处设立选择参数。

【修改后】

if theme.preloader.enable
case theme.preloader.load_style
when 'circular'
include ./load_style/circular.pug
default
include ./load_style/default.pug
script().
...

其中, circular.pug 是我创建的动画模板文件,自定义模板需放在同级目录下的 load_style 文件夹中。(当然其他地方也可以,请自行调整代码中的引用位置)

Vscode 中 目录结构 如下,原文还添加了其他动画模板,这里不作为本文重点所以不进行添加。

在后续也有类似的处理,故也不再截图描述。

image-20241226115622263

[!TIP]

在自定义动画运行时,可以会出现长时间无法退出加载动画的情况,这是因为加载动画的关闭与否是与网站加载状态的load的返回值决定的,而网站加载完成与否这个概念是很模糊的。

这里给到一个超时自动结束的loaded方案,可以给加载动画设置一个settimeout()的函数来达到伪·加载完毕的效果,即超时了自动关闭加载动画,即使页面还在加载。得益于loading-js使用的是原生js,所以可以给script添加async属性实现异步加载,以免阻塞后续HTML渲染。仅仅需要给控制加载动画开关的loading-js.pug添加两行代码即可。

- script.
+ script(async). # 启动异步加载
var preloader = {
endLoading: () => {
document.body.style.overflow = 'auto';
document.getElementById('loading-box').classList.add("loaded")
},
initLoading: () => {
document.body.style.overflow = '';
document.getElementById('loading-box').classList.remove("loaded")

}
}
window.addEventListener('load',()=> {preloader.endLoading()})
+ setTimeout(function(){preloader.endLoading();}, 5000); # 5s超时则自动转入结束加载

2.2 修改 loading.styl(CSS)

[!NOTE]

Stylus 是一种 CSS 预处理器,允许使用更清晰和简洁的语法来生成 CSS。它支持嵌套、变量等高级功能。

修改 \themes\butterfly\source\css\_layout\loading.styl

【修改前】默认动画的样式代码

【修改后】

if hexo-config('preloader.enable')
if hexo-config('preloader.load_style') == 'circular'
@import './_load_style/circular'
else
@import './_load_style/default'

逻辑同上,也是将固定样式修改为自选样式,你要做的也是在同级目录下新建 _load_style 文件夹,并创建新的自定义动画样式文件(在这里是 circular.stly)

2.3 修改相关的配置文件

(1)修改 \themes\butterfly\layout\includes\layout.pug

此处控制加载动画的 HTML 文件对象选择- 为删减代码,+ 为新增代码(下同)

body
- if theme.preloader
+ if theme.preloader.enable
!=partial('includes/loading/index', {}, {cache: true})

(2)修改 \themes\butterfly\source\css\var.styl

此处控制加载动画的 自定义背景色配置

  // preloader
- $preloader-bg = #37474f
+ $preloader-bg = hexo-config('preloader.enable') && hexo-config('preloader.load_color') ? convert(hexo-config('preloader.load_color')) : #37474f
$preloader-word-color = #fff

(3)修改 _config.butterfly.yml 的 preloader 配置项

此处重新设置 preloader 类的参数,以适配刚刚的修改

- preloader: true
+ preloader:
+ enable: true # true|false
+ load_color: '#000000' # '#37474f'
+ load_style: circle # default|circle
+ load_image: # url

3. 动画设计

以上工作完成后,我们的自定义动画修改逻辑就极度简单了,即:

[!IMPORTANT]

得到动画的 HTML 与 CSS 文件 - > 转换为适用于博客的 PUG 与 STLY 文件

如何创建动画并获取对应的代码内容呢?这里我们可以使用 CodePen 来实现。

image-20241226181616418

这边就以我本站的小球动画为例,在 Trending (热门)界面搜索“ Loading ”,即可得到许多开源的加载动画

image-20241226181851655

进入到项目的编辑页面,可以看到对应 HTML 与 CSS 代码,在此处做任何调整,下方的动画效果也将实时的调整,因而可以十分方便地设计与修改个性化动画。

image-20241226182311069

3.1 HTML 转换

在这里,我将分别给出 HTML 与 CSS 代码的转换参考,他们的语法结构几乎是一模一样的,只是在表达上存在微调。

先以 HTML 为例:

.loader
.circle
.circle
.circle
.circle
.circle

对应的 PUG 代码:

#loading-box
div.loader
div.circle
div.circle
div.circle
div.circle
div.circle

你要做的只是加上开头的 loading-box 和块级容器对象 div。

3.2 CSS 转换

.loader {
--orbit-radius: 38px;
background: #000;
position: fixed;
z-index: 1000;
width: 100vw;
height: 100vh;
overflow: hidden;
text-align: center;

.circle {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
margin: auto;
transform: rotate(225deg);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation-iteration-count: infinite;
animation-name: orbit;
animation-duration: 5.5s;


&:after {
content: '';
position: absolute;
width: 5px;
height: 5px;
border-radius: 5px;
background: #fff; /* Pick a color */
}

&:nth-child(2) { animation-delay: 240ms; }
&:nth-child(3) { animation-delay: 480ms; }
&:nth-child(4) { animation-delay: 720ms; }
&:nth-child(5) { animation-delay: 960ms; }

}
}

@keyframes orbit {
0% { transform:rotate(225deg)translateX(38px); opacity: 1;
animation-timing-function: ease-out; }

7% { transform:rotate(345deg)translateX(38px);
animation-timing-function: linear; }

30% { transform:rotate(455deg)translateX(38px);
animation-timing-function: ease-in-out; }

39% { transform:rotate(690deg)translateX(38px);
animation-timing-function: linear; }

70% { transform:rotate(815deg)translateX(38px); opacity: 1;
animation-timing-function: ease-out; }

75% { transform:rotate(945deg)translateX(38px);
animation-timing-function: ease-out; }

76% { transform:rotate(945deg)translateX(38px); opacity: 0; }
100% { transform:rotate(945deg)translateX(38px); opacity: 0; }
}

对应的 styl 代码如下:

#loading-box{ 
--orbit-radius: 38px;
background: #000;
position: fixed;
z-index: 1000;
width: 100vw;
height: 100vh;
overflow: hidden;
text-align: center;

&.loaded {
z-index: -1000;
display: none;
}

.loader {
width: 400px;
height: 400px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #000;

/* 轨道圆 */
.circle {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
margin: auto;
transform: rotate(225deg);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation-iteration-count: infinite;
animation-name: orbit;
animation-duration: 5.5s;

/* 小圆 */
&:after {
content: '';
position: absolute;
width: 5px;
height: 5px;
border-radius: 5px;
background: #fff; /* Pick a color */
}

&:nth-child(2) { animation-delay: 240ms; }
&:nth-child(3) { animation-delay: 480ms; }
&:nth-child(4) { animation-delay: 720ms; }
&:nth-child(5) { animation-delay: 960ms; }
}
}

}

@keyframes orbit {
0% { transform:rotate(225deg) translateX(var(--orbit-radius)); opacity: 1;
animation-timing-function: ease-out; }

7% { transform:rotate(345deg) translateX(var(--orbit-radius));
animation-timing-function: linear; }

30% { transform:rotate(455deg) translateX(var(--orbit-radius));
animation-timing-function: ease-in-out; }

39% { transform:rotate(690deg) translateX(var(--orbit-radius));
animation-timing-function: linear; }

70% { transform:rotate(815deg) translateX(var(--orbit-radius)); opacity: 1;
animation-timing-function: ease-out; }

75% { transform:rotate(945deg) translateX(var(--orbit-radius));
animation-timing-function: ease-out; }

76% { transform:rotate(945deg) translateX(var(--orbit-radius)); opacity: 0; }
100% { transform:rotate(945deg) translateX(var(--orbit-radius)); opacity: 0; }
}

可以注意到,二者语法逻辑是一模一样的,唯一较大不同是 styl 添加了 &.loaded

&.loaded {
    z-index: -1000;
    display: none;
}

这段代码定义了加载动画完成后的显示状态,功能是将 动画元素置于底层并不可见

修改完毕后,可以 hexo g && hexo s 在本地查看动画效果。