Komeiji小五 发表于 前天 02:50

神工大成哈哈哈哈

本帖最后由 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小五 发表于 前天 03:35

本帖最后由 Komeiji小五 于 2026-2-4 04:38 编辑

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

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

Komeiji小五 发表于 前天 03:44

本帖最后由 Komeiji小五 于 2026-2-4 03:50 编辑

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

Ahhaha233 发表于 前天 08:26

本帖最后由 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]
查看完整版本: 神工大成哈哈哈哈