Astro

Astro 使用 View Transitions 后 AOS 动画只播放一次怎么解决

解决 Astro 启用 ClientRouter 后 AOS 滚动动画只在首次加载时播放,页面切换后不再触发的问题

Astro 使用 View Transitions 后 AOS 动画只播放一次怎么解决

问题现象

在 Astro 博客中启用 <ClientRouter /> (View Transitions) 后,你可能会发现 AOS (Animate On Scroll) 动画只在第一次打开网页时播放

  • 首次加载页面,动画效果正常
  • 通过导航切换到其他页面,动画不再播放
  • 刷新页面后动画才会重新出现

问题原因

AOS 库的初始化配置中通常会设置 once: true,这意味着每个元素的动画只会触发一次

AOS.init({
  once: true,  // 动画只播放一次
  duration: 700,
  // ...
});

当使用 View Transitions 进行页面切换时:

  1. Astro 使用客户端导航,替换页面 DOM
  2. AOS 检测到已初始化,只调用 refresh() 刷新
  3. 但元素上已经有 aos-animate 类,标记为已播放
  4. AOS 遵守 once: true 设置,不再触发动画

简单说:元素的”已动画”状态没有被正确重置

解决方案

关键是在页面切换时重置所有 AOS 元素的状态,然后重新初始化。

修改 Layout.astro

<script>
import AOS from 'aos';

// AOS 初始化配置对象
const aosConfig = {
    easing: "ease-out-quart",
    once: true,
    offset: 60,
    duration: 700,
    delay: 0,
    anchorPlacement: 'top-bottom',
};

// AOS 初始化函数
function initAOS() {
    // 处理 Prose 内容中的媒体元素
    enhanceProseMedia();
    AOS.init(aosConfig);
}

// 重置所有 AOS 元素的动画状态
function resetAOSElements() {
    const aosElements = document.querySelectorAll('[data-aos]');
    aosElements.forEach((el) => {
        el.classList.remove('aos-animate');
        el.removeAttribute('data-aos-animated');
    });
}

// AOS 重新初始化函数(用于页面切换后)
function reinitAOS() {
    // 先重置所有元素状态
    resetAOSElements();
    // 重新初始化
    initAOS();
}

// View Transitions 页面切换处理(只注册一次)
// 注意:astro:page-load 在初始页面加载和每次页面切换后都会触发
if (!(window as any)._aosListenerAdded) {
    (window as any)._aosListenerAdded = true;
    
    // 在页面切换前重置动画状态
    document.addEventListener('astro:before-swap', resetAOSElements);
    
    // 页面切换后重新初始化 AOS
    document.addEventListener('astro:page-load', reinitAOS);
    
    // 初始页面也需要立即初始化(因为事件监听器是新添加的)
    reinitAOS();
}
</script>

优化说明:修复手机端卡顿问题

⚠️ 重要更新:在之前的版本中,我们在脚本末尾直接调用了 initAOS()。这会导致一个性能问题:astro:page-load 事件在页面首次加载时也会触发。如果你既直接调用了 initAOS(),又监听了 astro:page-load,那么 AOS 会被初始化两次

在性能较强的 PC 端可能感觉不明显,但在手机端,这会导致动画卡顿两次(元素先出现,然后闪烁一下再次播放动画)。

正确做法:移除直接调用的 initAOS(),统一使用 astro:page-load 事件来驱动初始化。这样无论是首次加载还是页面切换,都只会执行一次初始化逻辑。

关键步骤解析

时机事件操作
页面切换前astro:before-swap重置元素状态,移除 aos-animate
页面切换后astro:page-load重新初始化 AOS,触发新页面的动画

为什么需要 resetAOSElements()

AOS 通过以下方式判断元素是否已经播放过动画:

  1. aos-animate:元素动画完成后会添加此类
  2. data-aos-animated 属性:标记元素已触发动画

在页面切换前移除这些标记,AOS 就会认为这些是”新元素”,从而正确触发动画。

为什么保持 once: true

你可能会想:直接改成 once: false 不就行了?

虽然这样确实能让动画重新播放,但会带来副作用:

  • 用户上下滚动页面时,动画会反复触发
  • 这不是我们期望的行为,体验反而变差

保持 once: true + 重置元素状态的方案更加优雅。

完整工作流程

用户点击链接

astro:before-swap 事件触发

resetAOSElements() 重置动画状态

页面 DOM 切换

astro:page-load 事件触发

reinitAOS() 重新初始化 AOS

动画正常播放 ✅

注意事项

防止重复注册事件

使用 (window as any)._aosListenerAdded 标志确保事件监听器只注册一次。如果不这样做,每次脚本执行都会添加新的监听器,导致内存泄漏和重复执行。

TypeScript 类型提示

如果使用 TypeScript,可能需要扩展 Window 类型:

declare global {
    interface Window {
        _aosListenerAdded?: boolean;
    }
}

总结

在 Astro + View Transitions 环境下使用 AOS:

  1. ✅ 监听 astro:before-swap 重置元素的动画状态
  2. ✅ 监听 astro:page-load 重新初始化 AOS
  3. ✅ 保持 once: true 配置,维持原有动画体验
  4. ✅ 使用标志位防止重复注册事件监听器
  5. 仅通过事件初始化,避免重复执行导致手机端动画卡顿

这样就能让 AOS 动画在每次页面切换后都正常播放了!

相关阅读

Astro 使用 View Transitions 后 AOS 动画只播放一次怎么解决

www.jsom.top/post/astro-使用-view-transitions-后-aos-动画只播放一次怎么解决

👋

13 篇文章

38 个话题

2,157 次访问

Comments