You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止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中添加水合完成的全局类,然后在样式中针对该类禁用动画:

  1. 修改gatsby-browser.js:
export const onClientEntry = () => {
  window.addEventListener('load', () => {
    document.body.classList.add('hydrated');
  });
};
  1. 更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 00:42:52