我的博客一直没有加载动画,每次打开网页内容都是直接蹦出来,总觉得不太美观。为了彰显我的技术力厨力,我就想自己动手,做一个旋转的红白阴阳图来当加载动画。
这图看上去挺简单的:一个完整的圆,由两条对称的阴阳鱼组成,每条鱼里还有个相反颜色的小圆点。
可光是怎么实现,我就想了了两天。
一开始我异想天开,打算用四个伪元素(::before、::after)来拼出这个图形。结果在CSS里调来调去,各种对不齐,左边矫正了,右边就不知道偏到哪里去了。
硬着头皮试了两个小时,只能放弃。
然后就是到处查资料,参考别人的加载动画——说来也怪,我愣是没找到一个用太极图当加载动画的例子。
最后,我想到可以用三层结构来搭建这个复杂的图形。
第一层:基础圆形 + 左右分色
1 2 3 4 5 6
| .bagua { width: 120px; height: 120px; border-radius: 50%; background: linear-gradient(to right, #fff 50%, #e74c3c 50%); }
|
- 使用 border-radius: 50% 创建完美圆形
- 使用 linear-gradient 实现左右分色(左白右红)
第二层:上下半圆创建阴阳鱼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .bagua::before { top: 0; left: 50%; width: 50%; height: 50%; background: #e74c3c; }
.bagua::after { bottom: 0; left: 50%; width: 50%; height: 50%; background: #fff; }
|
第三层:鱼眼(小圆点)
1 2 3 4 5 6 7 8 9 10 11
| .bagua-inner::before { top: 25px; background: #fff; }
.bagua-inner::after { bottom: 25px; background: #e74c3c; }
|
- 使用额外的容器元素(bagua-inner)来承载鱼眼
- 两个小圆点放在对应鱼形的中心
- 颜色与所在鱼形相反
关键技巧
1.相对定位计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .bagua::before { width: 50%; height: 50%; left: 25%; top: 0; }
.bagua-inner::before { width: 16.67%; height: 16.67%; left: 41.67%; top: 16.67%; }
|
- 使用百分比确保在不同尺寸下保持比例
- 精确计算位置,使图形对称美观
2.旋转动画
1 2 3 4 5 6 7 8
| @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.bagua { animation: rotate 3s linear infinite; }
|
- 对整个太极图应用旋转动画
- 线性动画确保匀速旋转
- 无限循环创建持续动感
CSS代码总算写出来了,效果看着挺满意。
可我的博客用的是Butterfly主题,样式文件是.styl格式,纯CSS不能直接往里扔。于是又花了几个小时,吭哧吭哧地重新移植了一遍。
没想到,等我把加载动画部署好的时候,发现它一闪就没了,根本起不到遮丑的作用。
检查了好几遍fullpage-loading.pug,代码明明是对的,命令行也没报错。缓存清了一次又一次,还是老样子。
没辙了,只能开始漫长的排查。查了两天,什么也没发现。
实在没办法,我又回去翻店长的旧教程(新版本已经不适配了)。看了一半,突然看到了夜间背景切换的教程,突然悟了,我把背景魔改了。
原版的Butterfly没法检测到我那个自定义背景是否加载完成,所以只要md文档一加载好,它就认为页面准备好了,加载动画也就立刻结束了。
都折腾到这地步了,不继续那我写的加载动画不白瞎了吗?只好又硬着头皮,从背景加载的逻辑开始重新啃。
说起来都怪自己,当初图省事,魔改背景的时候完全是照抄,一行注释都没加,现在看自己的代码像看天书一样。
偷懒果然没好下场
又花了几天时间,一边理解一边加注释,等到终于调通的时候,注释也差不多写满了,整个主题文件的大小估计涨了四分之一。
今天总算全部搞定了。我把核心的CSS样式贴在下面,万一有朋友能用得上呢。
完整代码
fullpage-loading.pug:
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 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| #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[1]) { console.log('没有检测到背景图,跳过加载检测') resolve(false) return } const bgUrl = urlMatch[1] 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:
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
| 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
|