IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    给博客主题切换加个动画

    @1900\'Blog发表于 2025-06-28 09:24:59
    love 0

    Photo by Jon Tyson / Unsplash

    这个功能其实想搞很久了,之前在某个主题上看到过一个圆形扩散的过渡动画。

    当时自己尝试着结合AI实现了一下,虽然大致效果实现了,但是整个流畅度很差,而且存在很多BUG。

    前些日子看到大佬发布的 Retypeset 主题上有一个自上而下的过渡动画。流畅度,美观度都非常不错,而且这个主题也是基于Astro实现的,我要抄的话也简单一些 😄。

    0:00
    /0:07

    效果

    整个效果实现依旧是基于 Chrome 的 View-Transition 这个特性实现的。如果你的博客有主题切换功能,且是MPA网页,就能通过添加一些简单的脚本和CSS实现这个效果。(SPA能不能用我没测试,根据文档是可以的,但是效果应该不好?)

    我粗略理解的整个实现原理大概为:

    在切换主题的时候页面会有一个 new 版本和一个 old 版本。
    页面最初时 old 会版本覆盖在 new 版本之上,在执行完主题设置操作后,让 old 版本的高度逐渐缩小,缩小后自然就会把下方的 new 显示出来。
    这个过程就是我们看到的流畅切换动画。

    首先添加一个页面切换的动画CSS,animation-theme-toggle 为切换动画的名称。

    /*** 这里的 view-transition-new 和 view-transition-old ***/
    ::view-transition-new(animation-theme-toggle) {
        animation: reveal 0.5s cubic-bezier(0.4, 0, 0.2, 1);
        clip-path: inset(0 0 0 0);
        z-index: 99;
    }
    
    ::view-transition-old(animation-theme-toggle) {
        animation: none;
        z-index: -1;
    }
    
    @keyframes reveal {
        0% {
            clip-path: inset(var(--from));
        }
    }
    
    

    接着在主题切换的js实现做一些修改,让主题切换按钮调用此处定义的changeTheme即可,我这里是点击按钮时传入需要切换的主题名称,可以根据自己的主题实现来。

    // theme为需要切换的主题名称
        const changeTheme = (theme) => {
            // 兼容性支持
            if (document.documentElement.classList.contains('reduce-motion')) {
                updateTheme();
                return;
            }
    
            // 因为我的侧边栏是没有启用动画效果的
            // 但是主题切换时需要启用动画效果,所以这里把原来的设置清空。
            const menuContent = document.querySelector('.book-menu-content[data-astro-transition-scope]');
            const menuanmi = document.querySelector('.book-menu-content[data-astro-transition-scope]').dataset.astroTransitionScope;
    
            // 临时移除侧边栏的transition:animate="none"属性
            if (menuContent) {
                menuContent.dataset.astroTransitionScope = '';
            }
    
            // 给html设置我们需要的使用的动画类
            document.documentElement.style.setProperty('view-transition-name', 'animation-theme-toggle');
            document.documentElement.setAttribute('data-theme-changing', '');
    
            // 开始执行动画并且修改主题
            const themeTransition = document.startViewTransition(() => {
                // 存储主题设置&修改页面主题
                localStorage.setItem('name', theme.desc);
                localStorage.setItem('theme', theme.name);
                localStorage.setItem('themetype', theme.type);
                document.documentElement.setAttribute('class', theme.name);
                setTheme(theme.desc);
    
                // 切换主题时「自上而下」的效果和「自下而上」的效果轮换着来。
                const root = document.documentElement;
                const currentFrom = getComputedStyle(root).getPropertyValue('--from').trim();
                if (currentFrom === '100% 0 0 0') {
                    root.style.setProperty('--from', '0 0 100% 0');
                } else {
                    root.style.setProperty('--from', '100% 0 0 0');
                }
    
                document.dispatchEvent(new Event('theme-changed'));
            });
    
            // 动画执行完毕后执行清理操作
            themeTransition.finished.then(() => {
                document.documentElement.style.removeProperty('view-transition-name');
                document.documentElement.removeAttribute('data-theme-changing');
                // 关闭主题列表
                document.getElementById('theme').checked = false;
    
                // 恢复侧边栏的transition:animate="none"属性
                if (menuContent) {
                    menuContent.dataset.astroTransitionScope = menuanmi;
                }
            });
        };
    

    看起来可能有点复杂,其实在 const themeTransition = document.startViewTransition(() => { 后面加入你的主题切换函数应该就可以了。

    这个函数里面前面5行都是我的主题切换操作,替换这部分,保留其他的就可以了。

    想折腾的小伙伴可以试试。



沪ICP备19023445号-2号
友情链接