给博客主题切换加个动画

给博客主题切换加个动画

<span style="white-space: pre-wrap;">Photo by </span><a href="https://unsplash.com/@jontyson?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"><span style="white-space: pre-wrap;">Jon Tyson</span></a><span style="white-space: pre-wrap;"> / </span><a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"><span style="white-space: pre-wrap;">Unsplash</span></a>
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行都是我的主题切换操作,替换这部分,保留其他的就可以了。

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

来自联邦宇宙的回应

加入评论