如何在Next.js的onClick事件中启停GSAP动画并操作tl.current
问题核心原因
你代码中最大的问题是连续三次给tl.current重新赋值,每次新建GSAP时间线都会覆盖掉上一次存储的实例,最终tl.current仅保留了最后一个.singleProject:nth-child(4)的动画实例,所以你操作tl.current的时候只能控制最后一个项目的动画,甚至出现不符合预期的表现。
修复方案
- 仅初始化一次主时间线,后续所有子动画都通过时间线的
add()方法加入主时间线统一管理 - 点击第一个
.imgWrapper时,调用GSAP内置的paused()方法实现状态切换:该方法不传参时返回当前是否为暂停状态,传入布尔值即可直接设置状态,取反即可实现暂停/播放切换
修正后代码
import { useRef, useEffect, useState } from 'react'; import gsap from 'gsap'; export const Projects = () => { const projects = useRef(); const tl = useRef(); const q = gsap.utils.selector(projects); useEffect(() => { // 仅初始化一次主时间线,不要重复赋值覆盖 tl.current = gsap.timeline({ paused: false }); // 把每个项目的动画作为子时间线加入主时间线 tl.current.add( gsap .timeline() .from(q(".singleProject:nth-child(2)"), { y: 2000, ease: "none", opacity: 1, duration: 20, repeat: -1, }) .to(q(".singleProject:nth-child(2)"), { y: -2000, ease: "none", opacity: 1, duration: 20, repeat: -1, }) ); tl.current.add( gsap .timeline() .from(q(".singleProject:nth-child(3)"), { y: 2000, ease: "none", opacity: 1, duration: 20, repeat: -1, delay: 1, }) .to(q(".singleProject:nth-child(3)"), { y: -2000, ease: "none", opacity: 1, duration: 20, repeat: -1, delay: 1, }) ); tl.current.add( gsap .timeline() .from(q(".singleProject:nth-child(4)"), { y: 2000, ease: "none", opacity: 1, duration: 20, repeat: -1, delay: 2, }) .to(q(".singleProject:nth-child(4)"), { y: -2000, ease: "none", opacity: 1, duration: 20, repeat: -1, delay: 2, }) ); // 组件卸载时清空动画避免内存泄漏 return () => { tl.current.kill(); }; }, []); const [animate_1, setAnimate_1] = useState(false); const [animate_2, setAnimate_2] = useState(false); const [animate_3, setAnimate_3] = useState(false); // 动画切换函数 const toggleAnimation = () => { if (!tl.current) return; // 切换暂停状态:取反当前paused值即可 tl.current.paused(!tl.current.paused()); }; return ( <section ref={projects} id="projects" className={`${animate_1 && "animated_1"} ${animate_2 && "animated_2"} ${ animate_3 && "animated_3" } w_100 h_100vh flex flexColumn flexAlignCenter flexCenter p4em`} > <div className="projectsLoading flex flexCenter flexAlignCenter w_100 h_100"> <h2>LOADING PROJECTS</h2> </div> <div className={`singleProject w_100 flex flexStart`}> {/* 绑定切换函数 */} <div className="imgWrapper" onClick={toggleAnimation}> <img src={"https://www.aimanalmureish.com/img/lego.jpg"} alt="lego" /> </div> </div> <div className="singleProject w_100 flex flexEnd"> <div className="imgWrapper"> <img onClick={() => { setAnimate_2(!animate_2); }} src={"https://www.aimanalmureish.com/img/jordan.png"} alt="jordan" /> </div> </div> <div className="singleProject w_100 flex flexStart"> <div className="imgWrapper"> <img onClick={() => setAnimate_3(!animate_3)} src={"https://www.aimanalmureish.com/img/ferrari.png"} alt="ferrari" /> </div> </div> </section> ); };
额外说明
如果你后续需要单独控制每个项目的动画,可以将三个时间线存储在数组类型的ref中,例如const tls = useRef([]),创建每个子时间线时存入对应索引,操作时直接调取对应索引的实例即可。
内容的提问来源于stack exchange,提问作者Aiman Almureish
相关产品推荐
相关产品推荐

