使用 GSAP + React 实现动画
这次我们将学习如何使用 GSAP 库与 React JS 一起制作动画。
注意:这不是 gsap 教程,因此您必须对这个库有一些概念。
目的是让您知道如何通过良好的实践来补充 React JS 生态系统中的动画。
目录。
📌需要用到的技术。📌 创建项目。📌
首次使用 GSAP。📌了解 gsap.context()。 📌使用时间轴。📌 动画交互。📌 避免尚未设置样式的内容闪烁 。📌总结 。
☄️ 要使用的技术。
- ▶️ React JS(v.18)
- ▶ ️GSAP 3
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ CSS vanilla(您可以在本文末尾的存储库中找到样式)
☄️创建项目。
我们将为该项目命名:(gsap-react
可选,您可以随意命名)。
npm init vite@latest
我们使用 Vite JS 创建项目,并选择 React with TypeScript。
然后运行以下命令导航到刚刚创建的目录。
cd gsap-react
然后我们安装依赖项。
npm install
然后我们在代码编辑器中打开项目(在我的情况下是 VS 代码)。
code .
☄️ 第一次使用 GSAP。
在src/App.tsx文件中,我们删除所有内容并创建一个显示带有 的 div 的组件hello world
。
标题部分并不是那么重要。
const App = () => {
return (
<>
<Title />
<div className="square">Hello World</div>
</>
)
}
export default App
它看起来是这样的。
现在让我们安装 GSAP。
npm install gsap
要使用gsap并为某些元素制作动画,我们可以按照以下方式进行...
首先我们导入gsap。
然后,gsap 有几个函数可以执行动画。
- to:将从元素的当前状态开始,并“朝着”插值中定义的值进行动画处理。
- from:就像反向的 .to(),它从插值中定义的值“开始”进行动画处理,并以元素的当前状态结束。
- fromTo :定义初始值和最终值。
- set *:立即设置属性(无动画)。
在这种情况下,我们将使用最常见的to函数,因为所有函数都以相同的方式使用两个参数(fromTo 除外,它接收 3 个)。
-
第一个参数是要动画的元素,该参数可以是字符串(HTML 元素的 id 或甚至是复杂的 CSS 选择器),也可以是 HTML 元素或 HTML 元素数组。
-
第二个参数是一个对象,其中包含您想要动画的变量以及您想要赋予它们的值。
import { gsap } from "gsap";
const App = () => {
gsap.to( ".square", { rotate: 360 })
return (
<>
<div className="square">Hello World</div>
</>
)
}
export default App
但是停下来,你刚才看到的,是 React JS 中的一个坏习惯😞......
首先,动画等副作用应该在useEffect钩子内执行,这是一种避免意外行为的良好做法。
其次,请注意,第一个参数我们放入了“ .square”,这并不坏,它甚至可以工作,但是,React 建议我们不要以这种方式访问 DOM 元素,为此我们会使用另一个钩子,即useRef。
那么这将是我们第一个动画的代码,当应用程序启动时,动画将被执行,它应该将带有 Hello World 的 div 旋转 360 度。
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useEffect(() => {
gsap.to(app.current, { rotate: 360, duration: 5 })
}, [])
return (
<>
<div ref={app}>Hello World</div>
</>
)
}
export default App
☄️了解 gsap.context()。
好了,我们已经了解了如何使用最佳实践来制作动画。但是,如果我们想为不同的元素制作其他动画,是否需要创建更多引用呢?其实,这完全没有必要,多亏了gsap.context。
基本上,您只需创建一个引用,它将充当父级,包含您想要动画的元素。
函数 gsap.context 有两个参数。
-
回调,您将基本上执行动画(请记住,您将只选择后代元素)。
-
充当容器的元素。
但现在让我们一步一步来:
1 – 我们已经定义了这个组件
const App = () => {
return (
<div>
<div className="square">Hello World</div>
</div>
)
}
export default App
2 – 我们创建对包含所有其他元素的父元素的新引用。
import { useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
return (
<div ref={app}>
<div className="square">Hello World</div>
</div>
)
}
export default App
3——创建效果。
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useEffect(() => {
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
</div>
)
}
export default App
4 - 在效果内部,我们创建一个新变量,并将 gsap 的上下文函数赋值给它。这是因为最后我们需要清理动画,确保它们不会继续执行,因此我们将创建稍后会用到的变量ctx 。
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useEffect(() => {
let ctx = gsap.context()
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
</div>
)
}
export default App
5-上下文函数接收两个参数,回调(我们将在其中设置其他动画)和元素(此元素必须包含后代元素)。
请注意,第二个参数我们传递的是所有引用,而不是 app.current。
我们立即执行 useEffect 清理函数。我们使用之前声明的变量 ctx,并执行方法revert()来清理动画,这样它们就不会被执行。
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useEffect(() => {
let ctx = gsap.context(() => {}, app);
return () => ctx.revert();
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
</div>
)
}
export default App
6——现在我们可以在上下文方法中定义的回调中执行动画。
让我们制作一个动画,就像我之前展示的那样
如果您注意到上下文回调中的动画,它与我之前提到的最佳实践有些矛盾,因为我们通过其类获取元素。🤔
但优点是你不必为想要动画的每个元素创建引用😉
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useEffect(() => {
let ctx = gsap.context(() => {
gsap.to(".square", { rotate: 360, duration: 5 });
//gsap.to(".square2", { rotate: 360, duration: 5 });
//gsap.to(".square3", { rotate: 360, duration: 5 });
}, app);
return () => ctx.revert();
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
</div>
)
}
export default App
请注意,如果您想要为一个不是所引用父元素的后代的元素(.square2 )添加动画,那么您将无法在上下文中为其添加动画,除非您将该元素移动到所引用的元素中,使其成为子元素。
具有 .square2 类的元素不会进行动画处理,因为它不是具有ref的元素的子元素。
useEffect(() => {
let ctx = gsap.context(() => {
gsap.to(".square", { rotate: 360, duration: 5 });
gsap.to(".square2", { rotate: 360, duration: 5 }); // Don't work ❌
}, app);
return () => ctx.revert();
}, [])
<div ref={app}>
<div className="square">Hello World</div>
</div>
<div className="square2">Hello World</div>
☄️使用时间线。
时间线将帮助我们按顺序创建动画。
为了创建时间线,我们将按照以下方式进行。
我们几乎继承了之前的所有代码,其中使用了 gsap 的 context 函数。
只是目前的回调函数为空,我们添加了一个类名为square2的新 div 元素,并在其中添加了“Hello World 2”。
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useEffect(() => {
let ctx = gsap.context(() => {}, app);
return () => ctx.revert();
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
<div className="square2">Hello World 2</div>
</div>
)
}
export default App
为了创建时间线,我们将使用一个新的参考。
const tl = useRef<GSAPTimeline>()
现在,在上下文的回调中,我们将把 gsap 的函数时间线分配给 ref tl 。
这将帮助我们避免每次渲染组件时创建新的时间线。
let ctx = gsap.context(() => {
tl.current = gsap.timeline()
}, app);
因此,要向元素添加动画,我们只需使用其所需的参数执行动画类型。
具体如下:
let ctx = gsap.context(() => {
tl.current = gsap.timeline().to(".square", { rotate: 360 }).to(".square2", { x: 200});
}, app);
为了更好地阅读代码,我们可以将代码放置如下:
let ctx = gsap.context(() => {
tl.current = gsap.timeline()
.to(".square", { rotate: 360 })
.to(".square2", { x: 200 })
}, app);
这意味着首先对具有.square类的元素执行动画,直到该动画完成为止,将对具有.square2类的元素执行下一个动画。
当然,可以配置动画的持续时间,以便它们同时执行或类似操作。
代码如下:
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
const tl = useRef<GSAPTimeline>()
useEffect(() => {
let ctx = gsap.context(() => {
tl.current = gsap.timeline()
.to(".square", { rotate: 360 })
.to(".square2", { x: 200 });
}, app);
return () => ctx.revert()
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
<div className="square2">Hello World 2</div>
</div>
)
}
export default App
☄️ 具有交互的动画。
您还可以通过与元素进行的交互来执行动画,不仅是在启动应用程序时和在 useEffect 中。
例如,通过单击元素的事件可以触发一些动画。
注意,函数 handleClick 接收该事件。通过该事件,我们可以访问被点击的元素。
而这个元素(e.target )就是我们将传递给gsap的动画“to”的元素。
import { gsap } from "gsap";
const App = () => {
const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
gsap.to(e.target, { rotation: '50', yoyo: true, repeat: 1 })
}
return (
<>
<div onClick={handleClick} >Hello World</div>
</>
)
}
export default App
您不仅可以通过单击事件来执行此操作,还可以通过其他几个事件(例如 onMouseEnter 或 onMouseLeave)来执行此操作。
import { gsap } from "gsap";
const App = () => {
const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
gsap.to(e.target, { rotation: '50', yoyo: true, repeat: 1 })
}
const onEnter = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
gsap.to(e.target, { scale: 1.2 });
};
const onLeave = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
gsap.to(e.target, { scale: 1 });
};
return (
<>
<div
onMouseEnter={onEnter}
onMouseLeave={onLeave}
onClick={handleClick}
>Hello World</div>
</>
)
}
export default App
☄️ 避免尚未设置样式的内容闪烁。
您肯定已经注意到,当您启动应用程序并且必须在开始时执行动画时,您会注意到您想要动画的内容会闪一下,但没有 CSS 样式,之后相应的动画就已经执行了。
沿着这条路径,您已经看到我们在应用程序启动时使用 useEffect 来执行动画。但是 useEffect是在 DOM 绘制完成后执行的,这就是为什么会出现那个不必要的闪烁。
为了避免闪光,我们将使用useLayoutEffect而不是 useEffect ,其工作原理与 useEffect 完全相同,但不同之处在于 useLayoutEffect是在绘制 DOM 之前执行的。
这是从 gsap 文档中摘取的一个示例,这正是我们想要避免的。
我们通过使用useLayoutEffect来避免这种情况
import { gsap } from "gsap";
import { useLayoutEffect, useRef } from "react";
const App = () => {
const app = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
let ctx = gsap.context(() => {
gsap.to(".square", { rotate: 360, duration: 5 });
}, app);
return () => ctx.revert();
}, [])
return (
<div ref={app}>
<div className="square">Hello World</div>
</div>
)
}
export default App
☄️结论。
GSAP 库确实非常有趣,所以我希望我已经帮助您理解如何按照良好的实践和技巧将它与 React JS 一起使用。🙌
顺便提一下,另一个建议是不要为所有内容添加动画。此外,你应该只为 transforms 或 opacity 等属性添加动画,并避免为 filter 或 boxShadow 等属性添加动画,因为它们会消耗浏览器的大量 CPU 资源。
确保动画可以在低端设备上运行。
如果您知道使用此库或其他库制作动画的任何其他不同或更好的方法,请随时发表评论。
如果您有兴趣就某个项目与我联系,欢迎查看我的投资组合!富兰克林·马丁内斯·卢卡斯
🔵 别忘了在推特上关注我:@Frankomtz361
文章来源:https://dev.to/franklin030601/animations-with-gsap-react-1nok