神工大成哈哈哈哈
本帖最后由 Komeiji小五 于 2026-2-4 12:33 编辑写了十五天的加载动画,终于成了。
先展示
blog.komeijiv.cn
komeijiv.cn
其实还是有点没对齐
https://cloud.komeijiv.cn/f/bgiY/2026-02-04%20043955.png
我的博客一直没有加载动画,每次打开网页内容都是直接蹦出来,总觉得不太美观。
为了彰显我的技术力厨力,我就想自己动手,做一个旋转的红白阴阳图来当加载动画。
这图看上去挺简单的:一个完整的圆,由两条对称的阴阳鱼组成,每条鱼里还有个相反颜色的小圆点。
可光是怎么实现,我就想了了两天。
一开始我异想天开,打算用四个伪元素(::before、::after)来拼出这个图形。
结果在CSS里调来调去,各种对不齐,左边矫正了,右边就不知道偏到哪里去了。
硬着头皮试了两个小时,只能放弃。
然后就是到处查资料,参考别人的加载动画——说来也怪,我愣是没找到一个用太极图当加载动画的例子。
最后,我想到可以用三层结构来搭建这个复杂的图形。
CSS代码总算写出来了,效果看着挺满意。
可我的博客用的是Butterfly主题,样式文件是.styl格式,纯CSS不能直接往里扔。
于是又花了几个小时,吭哧吭哧地重新移植了一遍。
没想到,等我把加载动画部署好的时候,发现它一闪就没了,根本起不到遮丑的作用。
检查了好几遍fullpage-loading.pug,代码明明是对的,命令行也没报错。缓存清了一次又一次,还是老样子。
没辙了,只能开始漫长的排查。查了两天,什么也没发现。
实在没办法,我又回去翻店长的旧教程(新版本已经不适配了)。看了一半,突然看到了夜间背景切换的教程,突然悟了,我把背景魔改了。
原版的Butterfly没法检测到我那个自定义背景是否加载完成,所以只要md文档一加载好,它就认为页面准备好了,加载动画也就立刻结束了。
都折腾到这地步了,不继续那我写的加载动画不白瞎了吗?只好又硬着头皮,从背景加载的逻辑开始重新啃。
说起来都怪自己,当初图省事,魔改背景的时候完全是照抄,一行注释都没加,现在看自己的代码像看天书一样。
偷懒果然没好下场
又花了几天时间,一边理解一边加注释,等到终于调通的时候,注释也差不多写满了,整个主题文件的大小估计涨了四分之一。
今天总算全部搞定了。我把核心的CSS样式贴在下面,万一论坛里也有朋友用Butterfly主题,说不定能用得上呢。
这段代码主要就是画出那个会旋转的太极图,你可以把它放在自定义的样式文件里,再配合一点点JavaScript来控制显示和隐藏的时机就好了。
更详细的解读我会放在博客里的。
fullpage-loading.pug:
#loading-box
.loading-left-bg
.loading-right-bg
.spinner-box
.bagua
.bagua-inner
.loading-word 少女祈祷中...
script.
(()=>{
const $loadingBox = document.getElementById('loading-box')
const $body = document.body
const $spinnerBox = document.querySelector('.spinner-box')
const preloader = {
endLoading: () => {
if ($loadingBox.classList.contains('loaded')) return
$body.style.overflow = ''
$loadingBox.classList.add('loaded')
},
initLoading: () => {
$body.style.overflow = 'hidden'
$loadingBox.classList.remove('loaded')
// 移除淡入效果,为下次加载做准备
if ($spinnerBox) {
$spinnerBox.classList.remove('fade-in')
}
},
fadeInContent: () => {
// 添加淡入效果
if ($spinnerBox && !$spinnerBox.classList.contains('fade-in')) {
$spinnerBox.classList.add('fade-in')
}
}
}
preloader.initLoading()
// 函数:检测当前body的背景图是否加载完成
function checkBackgroundImage() {
return new Promise((resolve) => {
// 直接获取当前body的背景图
const computedStyle = window.getComputedStyle(document.body)
const bgImage = computedStyle.backgroundImage
// 提取URL
const urlMatch = bgImage.match(/url\(['"]?(.*?)['"]?\)/)
// 如果没有背景图,直接返回
if (!urlMatch || !urlMatch) {
console.log('没有检测到背景图,跳过加载检测')
resolve(false)
return
}
const bgUrl = urlMatch
console.log('检测到背景图URL:', bgUrl)
// 创建Image对象预加载背景图
const img = new Image()
img.onload = () => {
console.log('背景图加载完成')
resolve(true)
}
img.onerror = () => {
console.log('背景图加载失败')
resolve(false)
}
img.src = bgUrl
if (img.complete) {
console.log('背景图已缓存')
resolve(true)
}
})
}
// 主加载逻辑 - 确保至少显示3秒
async function startLoading() {
// 记录开始时间
const startTime = Date.now()
const MINIMUM_DISPLAY_TIME = 3000 // 最小显示时间:3秒
// 先触发淡入动画(延迟500ms让黑幕先显示)
setTimeout(() => {
preloader.fadeInContent()
}, 500)
// 等待DOM加载完成
if (document.readyState === 'loading') {
await new Promise(resolve => {
document.addEventListener('DOMContentLoaded', resolve)
})
}
console.log('DOM加载完成,开始检测背景图')
// 等待当前背景图加载
const bgLoadResult = await checkBackgroundImage()
// 计算已用时间
const elapsedTime = Date.now() - startTime
const remainingTime = MINIMUM_DISPLAY_TIME - elapsedTime
console.log(`背景图检测${bgLoadResult ? '成功' : '失败'},已用时 ${elapsedTime}ms`)
// 如果已经超过3秒,立即结束;否则等待剩余时间
if (remainingTime > 0) {
console.log(`需要等待额外 ${remainingTime}ms 达到3秒最小显示时间`)
await new Promise(resolve => setTimeout(resolve, remainingTime))
} else {
console.log('已超过3秒最小显示时间,立即结束')
}
// 结束加载
preloader.endLoading()
}
// 启动加载检测
startLoading().catch((error) => {
console.error('加载检测出错:', error)
// 如果出错,至少显示2秒
setTimeout(preloader.endLoading, 2000)
})
// 超时保护:8秒后强制结束
setTimeout(preloader.endLoading, 8000)
// PJAX支持
if (!{theme.pjax && theme.pjax.enable}) {
btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init')
btf.addGlobalFn('pjaxComplete', async () => {
// 每次PJAX完成后重新触发淡入效果
setTimeout(() => {
preloader.fadeInContent()
}, 300)
// 然后重新检测背景图并至少显示3秒
const startTime = Date.now()
const MINIMUM_DISPLAY_TIME = 3000
try {
const bgLoadResult = await checkBackgroundImage()
const elapsedTime = Date.now() - startTime
const remainingTime = MINIMUM_DISPLAY_TIME - elapsedTime
console.log(`PJAX背景图检测${bgLoadResult ? '成功' : '失败'},已用时 ${elapsedTime}ms`)
if (remainingTime > 0) {
setTimeout(preloader.endLoading, remainingTime)
} else {
setTimeout(preloader.endLoading, 300)
}
} catch (error) {
console.error('PJAX背景图检测出错:', error)
setTimeout(preloader.endLoading, 300)
}
}, 'preloader_end')
}
})()
loading.styl:
if hexo-config('preloader.enable') && hexo-config('preloader.source') == 1
.loading-bg
position: fixed
z-index: 1000
width: 50%
height: 100%
background-color: #000
#loading-box
.loading-left-bg
@extend .loading-bg
.loading-right-bg
@extend .loading-bg
right: 0
.spinner-box
position: fixed
z-index: 1001
display: flex
flex-direction: column
justify-content: center
align-items: center
width: 100%
height: 100vh
/* 初始状态:透明 */
opacity: 0
transition: opacity 0.5s ease-out 0.1s
/* 红白八卦图 */
.bagua
width: 120px
height: 120px
position: relative
border-radius: 50%
background: linear-gradient(to right, #fff 50%, #e74c3c 50%)
animation: rotate 3s linear infinite
box-shadow: 0 0 20px rgba(231, 76, 60, 0.7)
margin-bottom: 40px
.bagua::before,
.bagua::after
content: ''
position: absolute
border-radius: 50%
.bagua::before
width: 60px
height: 60px
top: 0
left: 30px
background: #e74c3c
.bagua::after
width: 60px
height: 60px
bottom: 0
left: 30px
background: #fff
.bagua-inner::before,
.bagua-inner::after
content: ''
position: absolute
border-radius: 50%
z-index: 1
.bagua-inner::before
width: 20px
height: 20px
top: 20px
left: 50px
background: #fff
.bagua-inner::after
width: 20px
height: 20px
bottom: 20px
left: 50px
background: #e74c3c
/* 文字样式 */
.loading-word
position: relative
color: #fff
font-size: 18px
font-weight: bold
text-shadow:
0 0 5px #e74c3c,
0 0 10px #e74c3c,
0 0 15px #e74c3c
letter-spacing: 3px
text-align: center
margin-top: 20px
animation: text-glow 2s ease-in-out infinite alternate
/* 淡入后的状态 */
&.fade-in
opacity: 1
&.loaded
.loading-left-bg
transition: all 1s
transform: translate(-100%, 0)
.loading-right-bg
transition: all 1s
transform: translate(100%, 0)
.spinner-box
opacity: 0
visibility: hidden
transition: opacity 0.5s ease, visibility 0.5s ease
@keyframes rotate
0%
transform: rotate(0deg)
100%
transform: rotate(360deg)
@keyframes text-glow
0%
text-shadow:
0 0 5px #e74c3c,
0 0 10px #e74c3c,
0 0 15px #e74c3c
opacity: 0.8
100%
text-shadow:
0 0 10px #e74c3c,
0 0 20px #e74c3c,
0 0 30px #e74c3c
opacity: 1
本帖最后由 Komeiji小五 于 2026-2-4 04:38 编辑
欸不对这玩意是不是应该放技术馆
好像放错位置了(
详细解读:https://blog.komeijiv.cn/posts/ee8eaac/
本帖最后由 Komeiji小五 于 2026-2-4 03:50 编辑
还好我保持理智没去学前端,来几个任务能直接给我熬秃了。
突然想起高中有个从来没摸过电脑的去学了计算机,不知还活着么。
(不是我曲曲他,高考完他让我教他怎么使linux,教了两月完全没会,唯一学会的就是打瓦,我真服了。这货最后还嫌弃我教的不好,真无语)
本帖最后由 Ahhaha233 于 2026-2-4 10:44 编辑
我选择用 svg (
话说阴阳鱼的旋转方向是不是错了🤔
摸鱼写了一个
跟楼主那版的主要区别是眼睛没有使用额外元素,而是使用径向渐变来实现
<body>
<div id="taichi"></div>
</body>
<style>
:root {
--taichi-size-min: 100px;
--taichi-size-max: 300px;
--taichi-size-auto: 20vw;
--color-dark: #333;
--color-light: #fafafa;
--taichi-head-gradient: 10% 100%;
--taichi-eye-gradient: 15%;
}
#taichi {
position: relative;
min-width: var(--taichi-size-min);
width: var(--taichi-size-auto);
max-width: var(--taichi-size-max);
min-height: var(--taichi-size-min);
height: var(--taichi-size-auto);
max-height: var(--taichi-size-max);
background-image: linear-gradient(90deg, var(--color-dark) 50%, var(--color-light) 50% 100%);
border-radius: 50%;
box-shadow: 0 0 3px #aaa;
overflow: hidden;
animation: 4s linear infinite rotate;
}
#taichi::before,
#taichi::after {
content: "";
position: absolute;
width: 50%;
height: 50%;
left: 0;
right: 0;
margin: auto;
border-radius: 50%;
overflow: hidden;
}
#taichi::before {
top: 0;
background-image: radial-gradient(var(--color-dark) var(--taichi-eye-gradient), var(--color-light) var(--taichi-head-gradient));
}
#taichi::after {
bottom: 0;
background-image: radial-gradient(var(--color-light) var(--taichi-eye-gradient), var(--color-dark) var(--taichi-head-gradient));
}
@keyframes rotate {
100% {
transform: rotate3d(0, 0, 1, -360deg);
}
}
</style>
页:
[1]