设为首页收藏本站喵玉殿官方微博

 找回密码
 少女注册中
搜索
查看: 24|回复: 3

[编程算法] 神工大成哈哈哈哈(欸不对这玩意是不是应该放技术馆)

[复制链接]
发表于 8 小时前 | 显示全部楼层 |阅读模式
本帖最后由 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:
  1. #loading-box
  2.   .loading-left-bg
  3.   .loading-right-bg
  4.   .spinner-box
  5.     .bagua
  6.       .bagua-inner
  7.     .loading-word 少女祈祷中...

  8. script.
  9.   (()=>{
  10.     const $loadingBox = document.getElementById('loading-box')
  11.     const $body = document.body
  12.     const $spinnerBox = document.querySelector('.spinner-box')
  13.    
  14.     const preloader = {
  15.       endLoading: () => {
  16.         if ($loadingBox.classList.contains('loaded')) return
  17.         $body.style.overflow = ''
  18.         $loadingBox.classList.add('loaded')
  19.       },
  20.       initLoading: () => {
  21.         $body.style.overflow = 'hidden'
  22.         $loadingBox.classList.remove('loaded')
  23.         // 移除淡入效果,为下次加载做准备
  24.         if ($spinnerBox) {
  25.           $spinnerBox.classList.remove('fade-in')
  26.         }
  27.       },
  28.       fadeInContent: () => {
  29.         // 添加淡入效果
  30.         if ($spinnerBox && !$spinnerBox.classList.contains('fade-in')) {
  31.           $spinnerBox.classList.add('fade-in')
  32.         }
  33.       }
  34.     }

  35.     preloader.initLoading()

  36.     // 函数:检测当前body的背景图是否加载完成
  37.     function checkBackgroundImage() {
  38.       return new Promise((resolve) => {
  39.         // 直接获取当前body的背景图
  40.         const computedStyle = window.getComputedStyle(document.body)
  41.         const bgImage = computedStyle.backgroundImage
  42.         
  43.         // 提取URL
  44.         const urlMatch = bgImage.match(/url\(['"]?(.*?)['"]?\)/)
  45.         
  46.         // 如果没有背景图,直接返回
  47.         if (!urlMatch || !urlMatch[1]) {
  48.           console.log('没有检测到背景图,跳过加载检测')
  49.           resolve(false)
  50.           return
  51.         }
  52.         
  53.         const bgUrl = urlMatch[1]
  54.         console.log('检测到背景图URL:', bgUrl)
  55.         
  56.         // 创建Image对象预加载背景图
  57.         const img = new Image()
  58.         img.onload = () => {
  59.           console.log('背景图加载完成')
  60.           resolve(true)
  61.         }
  62.         img.onerror = () => {
  63.           console.log('背景图加载失败')
  64.           resolve(false)
  65.         }
  66.         img.src = bgUrl
  67.         
  68.         if (img.complete) {
  69.           console.log('背景图已缓存')
  70.           resolve(true)
  71.         }
  72.       })
  73.     }

  74.     // 主加载逻辑 - 确保至少显示3秒
  75.     async function startLoading() {
  76.       // 记录开始时间
  77.       const startTime = Date.now()
  78.       const MINIMUM_DISPLAY_TIME = 3000 // 最小显示时间:3秒
  79.       
  80.       // 先触发淡入动画(延迟500ms让黑幕先显示)
  81.       setTimeout(() => {
  82.         preloader.fadeInContent()
  83.       }, 500)
  84.       
  85.       // 等待DOM加载完成
  86.       if (document.readyState === 'loading') {
  87.         await new Promise(resolve => {
  88.           document.addEventListener('DOMContentLoaded', resolve)
  89.         })
  90.       }
  91.       
  92.       console.log('DOM加载完成,开始检测背景图')
  93.       
  94.       // 等待当前背景图加载
  95.       const bgLoadResult = await checkBackgroundImage()
  96.       
  97.       // 计算已用时间
  98.       const elapsedTime = Date.now() - startTime
  99.       const remainingTime = MINIMUM_DISPLAY_TIME - elapsedTime
  100.       
  101.       console.log(`背景图检测${bgLoadResult ? '成功' : '失败'},已用时 ${elapsedTime}ms`)
  102.       
  103.       // 如果已经超过3秒,立即结束;否则等待剩余时间
  104.       if (remainingTime > 0) {
  105.         console.log(`需要等待额外 ${remainingTime}ms 达到3秒最小显示时间`)
  106.         await new Promise(resolve => setTimeout(resolve, remainingTime))
  107.       } else {
  108.         console.log('已超过3秒最小显示时间,立即结束')
  109.       }
  110.       
  111.       // 结束加载
  112.       preloader.endLoading()
  113.     }

  114.     // 启动加载检测
  115.     startLoading().catch((error) => {
  116.       console.error('加载检测出错:', error)
  117.       // 如果出错,至少显示2秒
  118.       setTimeout(preloader.endLoading, 2000)
  119.     })

  120.     // 超时保护:8秒后强制结束
  121.     setTimeout(preloader.endLoading, 8000)

  122.     // PJAX支持
  123.     if (!{theme.pjax && theme.pjax.enable}) {
  124.       btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init')
  125.       btf.addGlobalFn('pjaxComplete', async () => {
  126.         // 每次PJAX完成后重新触发淡入效果
  127.         setTimeout(() => {
  128.           preloader.fadeInContent()
  129.         }, 300)
  130.         
  131.         // 然后重新检测背景图并至少显示3秒
  132.         const startTime = Date.now()
  133.         const MINIMUM_DISPLAY_TIME = 3000
  134.         
  135.         try {
  136.           const bgLoadResult = await checkBackgroundImage()
  137.          
  138.           const elapsedTime = Date.now() - startTime
  139.           const remainingTime = MINIMUM_DISPLAY_TIME - elapsedTime
  140.          
  141.           console.log(`PJAX背景图检测${bgLoadResult ? '成功' : '失败'},已用时 ${elapsedTime}ms`)
  142.          
  143.           if (remainingTime > 0) {
  144.             setTimeout(preloader.endLoading, remainingTime)
  145.           } else {
  146.             setTimeout(preloader.endLoading, 300)
  147.           }
  148.         } catch (error) {
  149.           console.error('PJAX背景图检测出错:', error)
  150.           setTimeout(preloader.endLoading, 300)
  151.         }
  152.       }, 'preloader_end')
  153.     }
  154.   })()
复制代码


loading.styl:
  1. if hexo-config('preloader.enable') && hexo-config('preloader.source') == 1
  2.   .loading-bg
  3.     position: fixed
  4.     z-index: 1000
  5.     width: 50%
  6.     height: 100%
  7.     background-color: #000

  8.   #loading-box
  9.     .loading-left-bg
  10.       @extend .loading-bg

  11.     .loading-right-bg
  12.       @extend .loading-bg
  13.       right: 0

  14.     .spinner-box
  15.       position: fixed
  16.       z-index: 1001
  17.       display: flex
  18.       flex-direction: column
  19.       justify-content: center
  20.       align-items: center
  21.       width: 100%
  22.       height: 100vh
  23.       
  24.       /* 初始状态:透明 */
  25.       opacity: 0
  26.       transition: opacity 0.5s ease-out 0.1s
  27.       
  28.       /* 红白八卦图 */
  29.       .bagua
  30.         width: 120px
  31.         height: 120px
  32.         position: relative
  33.         border-radius: 50%
  34.         background: linear-gradient(to right, #fff 50%, #e74c3c 50%)
  35.         animation: rotate 3s linear infinite
  36.         box-shadow: 0 0 20px rgba(231, 76, 60, 0.7)
  37.         margin-bottom: 40px
  38.       
  39.       .bagua::before,
  40.       .bagua::after
  41.         content: ''
  42.         position: absolute
  43.         border-radius: 50%
  44.       
  45.       .bagua::before
  46.         width: 60px
  47.         height: 60px
  48.         top: 0
  49.         left: 30px
  50.         background: #e74c3c
  51.       
  52.       .bagua::after
  53.         width: 60px
  54.         height: 60px
  55.         bottom: 0
  56.         left: 30px
  57.         background: #fff
  58.       
  59.       .bagua-inner::before,
  60.       .bagua-inner::after
  61.         content: ''
  62.         position: absolute
  63.         border-radius: 50%
  64.         z-index: 1
  65.       
  66.       .bagua-inner::before
  67.         width: 20px
  68.         height: 20px
  69.         top: 20px
  70.         left: 50px
  71.         background: #fff
  72.       
  73.       .bagua-inner::after
  74.         width: 20px
  75.         height: 20px
  76.         bottom: 20px
  77.         left: 50px
  78.         background: #e74c3c

  79.       /* 文字样式 */
  80.       .loading-word
  81.         position: relative
  82.         color: #fff
  83.         font-size: 18px
  84.         font-weight: bold
  85.         text-shadow:
  86.           0 0 5px #e74c3c,
  87.           0 0 10px #e74c3c,
  88.           0 0 15px #e74c3c
  89.         letter-spacing: 3px
  90.         text-align: center
  91.         margin-top: 20px
  92.         animation: text-glow 2s ease-in-out infinite alternate

  93.       /* 淡入后的状态 */
  94.       &.fade-in
  95.         opacity: 1

  96.     &.loaded
  97.       .loading-left-bg
  98.         transition: all 1s
  99.         transform: translate(-100%, 0)

  100.       .loading-right-bg
  101.         transition: all 1s
  102.         transform: translate(100%, 0)

  103.       .spinner-box
  104.         opacity: 0
  105.         visibility: hidden
  106.         transition: opacity 0.5s ease, visibility 0.5s ease

  107.   @keyframes rotate
  108.     0%
  109.       transform: rotate(0deg)
  110.     100%
  111.       transform: rotate(360deg)
  112.   
  113.   @keyframes text-glow
  114.     0%
  115.       text-shadow:
  116.         0 0 5px #e74c3c,
  117.         0 0 10px #e74c3c,
  118.         0 0 15px #e74c3c
  119.       opacity: 0.8
  120.     100%
  121.       text-shadow:
  122.         0 0 10px #e74c3c,
  123.         0 0 20px #e74c3c,
  124.         0 0 30px #e74c3c
  125.       opacity: 1
复制代码





 楼主| 发表于 7 小时前 | 显示全部楼层
本帖最后由 Komeiji小五 于 2026-2-4 04:38 编辑

欸不对这玩意是不是应该放技术馆
好像放错位置了(

详细解读:https://blog.komeijiv.cn/posts/ee8eaac/
回复

使用道具 举报

 楼主| 发表于 7 小时前 | 显示全部楼层
本帖最后由 Komeiji小五 于 2026-2-4 03:50 编辑

还好我保持理智没去学前端,来几个任务能直接给我熬秃了。
突然想起高中有个从来没摸过电脑的去学了计算机,不知还活着么。
(不是我曲曲他,高考完他让我教他怎么使linux,教了两月完全没会,唯一学会的就是打瓦,我真服了。这货最后还嫌弃我教的不好,真无语)
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层
本帖最后由 Ahhaha233 于 2026-2-4 10:44 编辑

我选择用 svg (
话说阴阳鱼的旋转方向是不是错了🤔


摸鱼写了一个
跟楼主那版的主要区别是眼睛没有使用额外元素,而是使用径向渐变来实现
       
                       
       





回复

使用道具 举报

您需要登录后才可以回帖 登录 | 少女注册中

本版积分规则

合作与事务联系|无图版|手机版|小黑屋|喵玉殿

GMT+8, 2026-2-4 11:15

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表