如何防止Gatsby水合过程中CSS动画重复播放?
解决Gatsby水合后CSS动画重复触发的问题
Gatsby静态渲染时动画正常,但水合完成后重复触发,本质是React重新渲染元素时,styled-components会重新应用包含动画的样式,导致动画再次执行。以下是几种可行的解决方法:
方法一:用React状态控制动画的应用
通过状态标记水合完成状态,水合前应用动画,水合后直接设置最终样式:
import { useEffect, useState } from 'react'; import styled from 'styled-components'; const Container = styled.main<{ fontSize: number; isHydrated: boolean }>` @keyframes fadeIn { from { opacity: 0; background-position-y: -100px; } to { opacity: 1; background-position-y: 0px; } } text-align: center; padding-top: 2em; min-height: 330px; box-sizing: content-box; position: relative; font-size: ${props => props.fontSize + 'px'}; background: ${props => props.theme.backgroundImage}; background-position-x: center; background-repeat: no-repeat; background-size: 715px; ${media.mobile` background-size: 100vw; `} /* 水合前执行动画 */ ${props => !props.isHydrated ? ` opacity: 0; background-position-y: -100px; animation: fadeIn 1s forwards; ` : ` /* 水合后直接保持最终状态 */ opacity: 1; background-position-y: 0px; `} `; export default function YourComponent({ fontSize }) { const [isHydrated, setIsHydrated] = useState(false); // useEffect 在水合完成后才会执行 useEffect(() => { setIsHydrated(true); }, []); return <Container fontSize={fontSize} isHydrated={isHydrated} />; }
方法二:通过全局类控制动画
在gatsby-browser.js中添加水合完成的全局类,然后在样式中针对该类禁用动画:
- 修改
gatsby-browser.js:
export const onClientEntry = () => { window.addEventListener('load', () => { document.body.classList.add('hydrated'); }); };
- 更新styled-components样式:
const Container = styled.main<{ fontSize: number }>` @keyframes fadeIn { from { opacity: 0; background-position-y: -100px; } to { opacity: 1; background-position-y: 0px; } } text-align: center; padding-top: 2em; min-height: 330px; box-sizing: content-box; position: relative; font-size: ${props => props.fontSize + 'px'}; background: ${props => props.theme.backgroundImage}; background-position-x: center; background-position-y: -100px; background-repeat: no-repeat; background-size: 715px; ${media.mobile` background-size: 100vw; `} opacity: 0; animation: fadeIn 1s forwards; /* 水合完成后禁用动画,直接保留最终状态 */ .hydrated & { animation: none; opacity: 1; background-position-y: 0px; } `;
方法三:利用animation-play-state快速修复
如果不想修改太多代码,可以在水合后暂停动画,不过这种方法依赖动画已经执行过的状态:
import { useEffect } from 'react'; import styled from 'styled-components'; const Container = styled.main<{ fontSize: number }>` /* 原有样式不变 */ @keyframes fadeIn { from { opacity: 0; background-position-y: -100px; } to { opacity: 1; background-position-y: 0px; } } text-align: center; padding-top: 2em; min-height: 330px; box-sizing: content-box; position: relative; font-size: ${props => props.fontSize + 'px'}; background: ${props => props.theme.backgroundImage}; background-position-x: center; background-position-y: -100px; background-repeat: no-repeat; background-size: 715px; ${media.mobile` background-size: 100vw; `} opacity: 0; animation: fadeIn 1s forwards; `; export default function YourComponent({ fontSize }) { useEffect(() => { const container = document.querySelector('main'); // 注意选择器要匹配你的元素 if (container) { container.style.animationPlayState = 'paused'; } }, []); return <Container fontSize={fontSize} />; }
以上三种方法都能有效阻止水合后的动画重复触发,推荐前两种方法,逻辑更清晰且更符合React的状态管理思路。
内容的提问来源于stack exchange,提问作者Grosmar
相关产品推荐
相关产品推荐

