Gatsby生产环境useEffect未执行致GSAP动画失效,求解决方案
我之前在Gatsby项目里用GSAP做动画时也踩过一模一样的坑——开发环境跑起来贼顺,生产环境直接没动画。结合你的代码来看,核心问题大概率是Gatsby生产构建的资源优化导致DOM元素获取失败,咱们一步步来解决:
问题根源分析
Gatsby在生产环境会对静态资源(比如你导入的SVG)做压缩和优化,其中就包括重命名SVG内部的ID来避免全局冲突。你代码里用getElementById('What')、getElementById('Ever')去拿SVG里的元素,生产环境下这些ID已经被改掉了,自然找不到元素,导致useEffect里的GSAP代码直接中断,动画就没了。
另外,你用document.getElementById获取按钮和图标,这种全局查找的方式也容易因为生产环境的代码压缩、ID冲突出问题。
具体解决方案
1. 替换SVG的ID为data属性
打开你的whatever.svg文件,把内部的id="What"改成data-id="What",id="Ever"改成data-id="Ever"。比如:
<!-- 修改前 --> <g id="What">...</g> <!-- 修改后 --> <g data-id="What">...</g>
Gatsby不会修改data-*属性,这样就能稳定找到SVG内部元素了。
2. 用useRef代替全局DOM查找
把按钮和图标的获取从document.getElementById改成React的useRef,既避免全局冲突,又更符合React的组件化思想:
// 在组件里添加ref const buttonRef = useRef(null) // JSX里绑定ref <HeaderButton ref={buttonRef} onClick={() => scrollTo('#poznaj-nas')}> <FontAwesomeIcon icon={faArrowDown}/> </HeaderButton>
3. 修改useEffect里的代码逻辑
增加空值检查防止组件未挂载完成就执行代码,同时替换元素查找方式:
useEffect(() => { // 先确认ref都挂载好了 if (!animWrapper.current || !buttonRef.current) return; const [elements] = animWrapper.current.children // 用data属性查找SVG内部元素 const what = elements.querySelector('[data-id="What"]') const ever = elements.querySelector('[data-id="Ever"]') // 找不到元素就终止,避免报错 if (!what || !ever) return; const whatChildrens = what.children const everChildrens = ever.children const allChildrens = [...whatChildrens, ...everChildrens] gsap.set([...allChildrens, buttonRef.current], { autoAlpha: 0 }) // 初始化timeline执行动画 const timeline = gsap.timeline({ defaults: { ease: 'power3.inOut' } }) timeline .to(whatChildrens, { autoAlpha: 1, duration: 0.75 }) .to(everChildrens, { autoAlpha: 1, stagger: 0.025 }) .to(buttonRef.current, { autoAlpha: 1 }) }, [])
4. 可选:把SVG转换成React组件
如果上面的方法还没解决问题,可以把SVG转换成React组件(Gatsby默认支持用@svgr/webpack处理SVG),这样你可以直接给内部元素绑定ref,完全绕过ID的问题:
// 导入时直接作为React组件 import { WhatEver } from '../../../components/Whatever' // JSX里使用,给内部元素加ref <WhatEver style={{width: '100%'}} whatRef={whatRef} everRef={everRef}/>
(需要修改SVG组件的代码,把ref传递给内部的g元素)
修改后的完整代码
import React, { useRef, useEffect } from "react" import styled from "styled-components" import gsap from "gsap" import WhatEver from "../../../static/whatever.svg" import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faArrowDown } from '@fortawesome/free-solid-svg-icons' import scrollTo from 'gatsby-plugin-smoothscroll'; const HeaderWrapper = styled.header` width: 100%; height: 100vh; min-height: 150px; padding: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(255, 216, 41); ` const HeaderButton = styled.button` display: block; margin: 40px auto; border: 2px solid #000000; ` const HeaderComponent = () => { const animWrapper = useRef(null) const buttonRef = useRef(null) useEffect(() => { if (!animWrapper.current || !buttonRef.current) return; const [elements] = animWrapper.current.children const what = elements.querySelector('[data-id="What"]') const ever = elements.querySelector('[data-id="Ever"]') if (!what || !ever) return; const whatChildrens = what.children const everChildrens = ever.children const allChildrens = [...whatChildrens, ...everChildrens] gsap.set([...allChildrens, buttonRef.current], { autoAlpha: 0 }) const timeline = gsap.timeline({ defaults: { ease: 'power3.inOut' } }) timeline .to(whatChildrens, { autoAlpha: 1, duration: 0.75 }) .to(everChildrens, { autoAlpha: 1, stagger: 0.025 }) .to(buttonRef.current, { autoAlpha: 1 }) }, []) return ( <HeaderWrapper className="header" id="main-header"> <div ref={animWrapper} id="header-logo-wrapper"> <WhatEver style={{width: '100%'}}/> <HeaderButton ref={buttonRef} onClick={() => scrollTo('#poznaj-nas')}> <FontAwesomeIcon icon={faArrowDown}/> </HeaderButton> </div> </HeaderWrapper> ) } export default HeaderComponent
按这个方法改完,生产环境的动画应该就能正常运行了。
内容的提问来源于stack exchange,提问作者Łukasz Strzeboński

