|
|
本帖最后由 Komeiji小五 于 2026-2-4 04:43 编辑
写了十五天的加载动画,终于成了。
先展示
blog.komeijiv.cn
komeijiv.cn
其实还是有点没对齐
 |
我的博客一直没有加载动画,每次打开网页内容都是直接蹦出来,总觉得不太美观。
为了彰显我的技术力厨力,我就想自己动手,做一个旋转的红白阴阳图来当加载动画。
这图看上去挺简单的:一个完整的圆,由两条对称的阴阳鱼组成,每条鱼里还有个相反颜色的小圆点。
可光是怎么实现,我就想了了两天。
一开始我异想天开,打算用四个伪元素(::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[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:
- 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
复制代码
|
|