使用 GreenSock 为 React 组件制作动画
已发表: 2022-03-10在万维网的早期,事情相当静态和无聊。 在引入动画之前,网页主要基于印刷界的图形设计和布局。 动画可以比静态网页更长时间地吸引和吸引人们的注意力,并且可以更清晰有效地传达想法或概念。
但是,如果处理不当,动画可能会阻碍用户与您的产品的交互并对牵引力产生负面影响。 GreenSock 动画平台 AKA (GSAP) 是一个功能强大的 JavaScript 库,使前端开发人员、动画师和设计师能够创建基于时间轴的高性能动画。 它允许动画爱好者精确控制他们的动画序列,而不是 CSS 提供的有时约束的keyframe
和animation
属性。
在本文中,我将向您介绍 GSAP 的一些功能,例如scrollTriggers
、 Timelines
、 Easing
等,最后我们将通过使用这些功能为 React 应用程序制作动画来构建一个直观的用户界面。 在代码沙箱上查看完成的项目。
如果出现以下情况,本文将对您有用:
- 您一直在使用 HTML、CSS 和 JavaScript 在 Web 应用程序上构建动画。
- 您已经在使用 animate.css、React-motion、Framer-motion 和 React-Spring 等软件包在 React 应用程序中构建动画网页,此外您还想查看替代方案。
- 您是 React 爱好者,并且希望在基于 React 的 Web 应用程序上构建复杂的动画。
我们将研究如何从现有的 Web 项目构建各种动画。 让我们开始吧!
注意:本文假设您熟悉 HTML、CSS、JavaScript 和 React.js。
什么是 GSAP?
GreenSock 动画平台也称为 GSAP,是一款适用于现代 Web 的超高性能、专业级动画,允许开发人员以模块化、声明性和可重用的方式为他们的应用程序制作动画。 它与框架无关,可以在任何基于 JavaScript 的项目中使用,它的包大小非常小,不会使您的应用程序膨胀。
GSAP 可以执行画布动画,用于创建 WebGL 体验,创建动态 SVG 动画并作为出色的浏览器支持。
为什么使用 GSAP?
也许你还没有准备好背叛其他框架,或者你还没有被说服接受 GSAP 带来的好处。 请允许我给您一些您可能要考虑 GSAP 的理由。
您可以构建复杂的动画
GSAP JavaScript 库使开发人员可以构建从简单到非常复杂的基于物理的动画,就像这些网站一样,它允许开发人员和设计人员对运动进行排序并动态控制动画。 它有很多插件,例如 DrawSVGPlugin、MorphSVGPlugin 等,这使得创建基于 SVG 的动画和 2D/3D 动画成为现实。 除了在 DOM 元素上集成 GSAP,您还可以在 WebGL/Canvas/ Three.js 基于上下文的动画中使用它们。
此外,GSAP 的缓动能力非常复杂,因此与常规 CSS 动画相比,可以创建具有多个贝塞尔曲线的高级效果。
表现
GSAP 在不同的浏览器中具有令人印象深刻的高性能。
根据 GSAP 团队的说法,在他们的网站上,“GSAP 比 jQuery快 20 倍,而且 GSAP 是这个星球上最快的全功能脚本动画工具。 在许多情况下,它甚至比 CSS3 动画和过渡还要快。” 自己确认速度比较。
此外,GSAP 动画在台式电脑、平板电脑和智能手机上都可以毫不费力地执行。 不需要添加一长串前缀,这一切都由 GSAP 处理。
您可以在 GSAP 上查看更多好处,或者在这里查看 Sarah Drasner 对此有何评论。
GSAP 的缺点
你是说我应该在每个项目中都使用 GSAP 吗? 当然不是! 我觉得,你可能不想使用 GSAP 的原因只有一个。 让我们来了解一下!
- GSAP 只是一个基于 JavaScript 的动画库,因此它需要一些 JavaScript 和 DOM 操作知识才能有效地利用其方法和 API。 这种学习曲线的缺点为刚开始使用 JavaScript 的初学者留下了更大的复杂空间。
- GSAP 不支持基于 CSS 的动画,因此如果您正在寻找这样的库,您不妨在 CSS 动画中使用
keyframes
。
如果您还有其他原因,请随时在评论部分分享。
好吧,既然你的疑虑已经消除,让我们跳到 GSAP 中的一些细节。
GSAP 基础知识
在我们使用 React 创建动画之前,让我们熟悉一些 GSAP 的方法和构建块。
如果您已经了解 GSAP 的基础知识,则可以跳过此部分并直接跳到项目部分,我们将在滚动时使登录页面倾斜。
吐温
补间是动画中的单个动作。 在 GSAP 中,补间具有以下语法:
TweenMax.method(element, duration, vars)
我们来看看这个语法代表什么;
-
method
是指您想要补间的 GSAP 方法。 -
element
是您要设置动画的元素。 如果要同时为多个元素创建补间,可以将元素数组传递给element
。 -
duration
是补间的持续时间。 它是以秒为单位的整数(没有s
后缀!)。 -
vars
是您要设置动画的属性的对象。 稍后再谈。
GSAP 方法
GSAP 提供了许多创建动画的方法。 在本文中,我们只提及gsap.to
、 gsap.from
、 gsap.fromTo
等少数几个。 您可以在他们的文档中查看其他很酷的方法。 本节讨论的方法将在本教程后面的构建项目中使用。
-
gsap.to()
对象应该被动画化的值,即动画对象的最终属性值 - 如下所示:gsap.to('.ball', {x:250, duration: 5})
为了演示to
方法,下面的 codepen 演示显示,当组件安装时,具有250px
类球的元素将在 5 秒内跨x-axis
移动。 如果没有给出持续时间,将使用默认的 500 毫秒。
注意: x
和y-axis
轴分别表示水平轴和垂直轴,在 CSS 变换属性(例如translateX
和translateY
)中,它们表示为x
和y
用于pixel-measured
变换, xPercent
和yPercent
表示基于百分比的变换。
要查看完整的代码片段,请查看 codepen 游乐场。
-
gsap.from()
- 定义一个对象应该被动画化的值 - 即动画的起始值:gsap.from('.square', {duration:3, scale: 4})
3seconds
演示展示了当组件安装时,具有square
类的元素如何在 3 秒内从 4 的比例调整大小。 检查此 codepen 上的完整代码片段。
-
gsap.fromTo()
— 让您定义动画的开始和结束值。 它是from()
和to()
方法的组合。
这是它的外观;
gsap.fromTo('.ball',{opacity:0 }, {opacity: 1 , x: 200 , duration: 3 }); gsap.fromTo('.square', {opacity:0, x:200}, { opacity:1, x: 1 , duration: 3 });
此代码将在 3 秒内将具有类ball
的元素在x-axis
从不透明度 0 变为不透明度1
,并且在3 seconds
3 seconds
将square
类从不透明度0
变为1
在x-axis
仅当组件安装时x-axis
。 要查看fromTo
方法的工作原理和完整的代码片段,请查看下面 CodePen 上的演示。
注意:每当我们为位置属性设置动画时,例如left
和top
,我们必须确保相关元素的 CSS 位置属性必须是relative
、 absolute
或fixed
。
缓和
GSAP 官方文档将缓动定义为更改 Tweens 时间的主要方式。 它确定对象如何在不同点改变位置。 Ease 控制 GSAP 中动画的变化率,用于设置对象动画的样式。
GSAP 提供了不同类型的缓动和选项,让您可以更好地控制动画的行为方式。 它还提供了一个缓动可视化工具来帮助您选择您喜欢的缓动设置。
缓动共有三种类型,它们的操作各不相同。
-
in()
— 运动开始缓慢,然后在动画结束时加快步伐。 -
out()
— 动画快速开始,然后在动画结束时减速。 -
inOut()
— 动画开始缓慢,中途加快步伐,然后缓慢结束。
在这些缓动示例中,我们链接了显示三种缓动类型的补间bounce.in
、 bounce.out
和bounce.inOut
,并设置动画在开始下一个动画之前完成的秒数的延迟,仅当该组件是安装。 这种模式是重复的,在下一节中,我们将看到如何使用时间线更好地做到这一点。
时间线
时间轴充当多个补间的容器。 它按顺序对补间进行动画处理,并且不依赖于前一个补间的持续时间。 Timeline 可以轻松控制整个补间并精确管理它们的时间。
时间线可以通过创建时间线的实例来编写,如下所示:
gsap.timeline();
您还可以在以下代码中以两种不同的方式将多个补间链接到时间线:
##Method 1 const tl = gsap.timeline(); // create an instance and assign it a variable tl.add(); // add tween to timeline tl.to('element', {}); tl.from('element', {}); ##Method 2 gsap.timeline() .add() // add tween to timeline .to('element', {}) .from('element', {})
让我们用时间线重新创建前面的示例:
const { useRef, useEffect } = React; const Balls = () => { useEffect(() => { const tl = gsap.timeline(); tl.to('#ball1', {x:1000, ease:"bounce.in", duration: 3}) tl.to('#ball2', {x:1000, ease:"bounce.out", duration: 3, delay:3 }) tl.to('#ball3', {x:1000, ease:"bounce.inOut", duration: 3, delay:6 }) }, []); } ReactDOM.render( , document.getElementById('app'));
const { useRef, useEffect } = React; const Balls = () => { useEffect(() => { const tl = gsap.timeline(); tl.to('#ball1', {x:1000, ease:"bounce.in", duration: 3}) tl.to('#ball2', {x:1000, ease:"bounce.out", duration: 3, delay:3 }) tl.to('#ball3', {x:1000, ease:"bounce.inOut", duration: 3, delay:6 }) }, []); } ReactDOM.render( , document.getElementById('app'));
在useEffect
钩子中,我们创建了一个变量(tl)
来保存时间线的一个实例,接下来我们使用tl
变量按顺序对我们的补间进行动画处理,而不依赖于之前的补间进行动画处理,传递与在前面的例子。 有关此演示的完整代码片段,请查看下面的 codepen 游乐场。
现在我们已经了解了 GSAP 的一些基本构建块,让我们看看如何在下一节中在典型的 React 应用程序中构建完整的动画。 让我们开始飞行吧!
使用 React 和 GSAP 构建动画着陆页
让我们为 React App 制作动画。 确保在开始之前克隆存储库并运行npm install
以安装依赖项。
我们在建造什么?
目前,我们的登录页面包含一些白色背景的文本,一个不下拉菜单,实际上没有动画。 以下是我们将添加到登录页面的内容;
- 为主页上的文本和徽标设置动画,以便在安装组件时缓和下来。
- 为菜单设置动画,以便在单击菜单时下拉。
- 当页面滚动时,使画廊页面中的图像倾斜
20deg
。
查看代码沙盒上的演示。
我们将登陆页面的流程分解成组件,这样很容易掌握。 这是过程;
- 定义动画方法,
- 动画文本和徽标,
- 切换菜单,
- 使图像在页面滚动时倾斜
20deg
。
成分
Animate.js
— 定义了所有动画方法,-
Image.js
— 导入厨房图像, -
Menu.js
— 包含菜单切换功能, -
Header.js
- 包含导航链接。
定义动画方法
在src
目录中创建一个component
文件夹,并创建一个animate.js
文件。 将以下代码复制并粘贴到其中。
import gsap from "gsap" import { ScrollTrigger } from "gsap/ScrollTrigger"; //Animate text export const textIntro = elem => { gsap.from(elem, { xPercent: -20, opacity: 0, stagger: 0.2, duration: 2, scale: -1, ease: "back", }); };
在这里,我们导入gsap
。 我们编写了一个导出的箭头函数,它可以为登录页面上的文本设置动画。 请记住, gsap.from()
方法定义了对象应该被动画化的值。 该函数有一个elem
参数,表示需要动画的类。 它需要一些属性并分配值,例如xPercent: -20
(将对象转换 -20%),使对象不透明,使对象scale
-1
,使对象在2sec
内ease
返回。
要查看这是否有效,请转到App.js
并包含以下代码。
... //import textIntro import {textIntro} from "./components/Animate" ... //using useRef hook to access the textIntro DOM let intro = useRef(null) useEffect(() => { textIntro(intro) }, []) function Home() { return ( <div className='container'> <div className='wrapper'> <h5 className="intro" ref={(el) => (intro = el)}></h5> The <b>SHOPPER</b>, is a worldclass, innovative, global online ecommerce platform, that meets your everyday daily needs. </h5> </div> </div> ); }
在这里,我们从Aminate
组件中导入textIntro
方法。 要访问我们过去使用的 DOM,请使用useRef
Hook。 我们创建了一个变量intro
,其值设置为null
。 接下来,在useEffect
挂钩中,我们调用了textIntro
方法和intro
变量。 在我们的 home 组件中,在h5
标记中,我们定义了ref
属性并传入了intro
变量。
接下来,我们有一个菜单,但单击它时它不会下拉。 让它发挥作用吧! 在Header.js
组件中,添加以下代码。
import React, { useState, useEffect, useRef } from "react"; import { withRouter, Link, useHistory } from "react-router-dom"; import Menu from "./Menu"; const Header = () => { const history = useHistory() let logo = useRef(null); //State of our Menu const [state, setState] = useState({ initial: false, clicked: null, menuName: "Menu", }); // State of our button const [disabled, setDisabled] = useState(false); //When the component mounts useEffect(() => { textIntro(logo); //Listening for page changes. history.listen(() => { setState({ clicked: false, menuName: "Menu" }); }); }, [history]); //toggle menu const toggleMenu = () => { disableMenu(); if (state.initial === false) { setState({ initial: null, clicked: true, menuName: "Close", }); } else if (state.clicked === true) { setState({ clicked: !state.clicked, menuName: "Menu", }); } else if (state.clicked === false) { setState({ clicked: !state.clicked, menuName: "Close", }); } }; // check if out button is disabled const disableMenu = () => { setDisabled(!disabled); setTimeout(() => { setDisabled(false); }, 1200); }; return ( <header> <div className="container"> <div className="wrapper"> <div className="inner-header"> <div className="logo" ref={(el) => (logo = el)}> <Link to="/">SHOPPER.</Link> </div> <div className="menu"> <button disabled={disabled} onClick={toggleMenu}> {state.menuName} </button> </div> </div> </div> </div> <Menu state={state} /> </header> ); }; export default withRouter(Header);
在这个组件中,我们定义了菜单和按钮状态,在useEffect
挂钩中,我们使用useHistory
挂钩监听页面变化,如果页面发生变化,我们将clicked
和menuName
状态值分别设置为false
和Menu
。
为了处理我们的菜单,我们检查了初始状态的值是否为 false,如果为 true,我们将initial
、 clicked
和menuName
的值更改为null
、 true
和Close
。 否则,我们检查按钮是否被单击,如果为真,我们menuName
更改为Menu
。 接下来,我们有一个disabledMenu
函数,当它被点击时,它会禁用我们的按钮1sec
。
最后,在我们的button
中,我们将disabled
分配给disabled
,这是一个布尔值,当其值为true
时将禁用该按钮。 并且按钮的onClick
处理程序与toggleMenu
函数相关联。 我们在这里所做的只是切换menu
文本并将状态传递给Menu
组件,我们将尽快创建该组件。 让我们在创建实际的Menu
组件之前编写使我们的菜单下拉菜单的方法。 前往Animate.js
并将此代码粘贴到其中。
.... //Open menu export const menuShow = (elem1, elem2) => { gsap.from([elem1, elem2], { duration: 0.7, height: 0, transformOrigin: "right top", skewY: 2, ease: "power4.inOut", stagger: { amount: 0.2, }, }); }; //Close menu export const menuHide = (elem1, elem2) => { gsap.to([elem1, elem2], { duration: 0.8, height: 0, ease: "power4.inOut", stagger: { amount: 0.07, }, }); };
在这里,我们有一个名为menuShow
的函数,它将菜单水平倾斜 2 度,使菜单缓和,使用stagger
属性偏移动画,并在0.7sec
内right to top
变换菜单, 2degrees
函数的属性menuHide
。 要使用这些功能,请在components
内创建Menu.js
文件并将此代码粘贴到其中。
import React, {useEffect, useRef} from 'react' import { gsap } from "gsap" import { Link } from "react-router-dom" import { menuShow, menuHide, textIntro, } from './Animate' const Menu = ({ state }) => { //create refs for our DOM elements let menuWrapper = useRef(null) let show1 = useRef(null) let show2 = useRef(null) let info = useRef(null) useEffect(() => { // If the menu is open and we click the menu button to close it. if (state.clicked === false) { // If menu is closed and we want to open it. menuHide(show2, show1); // Set menu to display none gsap.to(menuWrapper, { duration: 1, css: { display: "none" } }); } else if ( state.clicked === true || (state.clicked === true && state.initial === null) ) { // Set menu to display block gsap.to(menuWrapper, { duration: 0, css: { display: "block" } }); //Allow menu to have height of 100% gsap.to([show1, show2], { duration: 0, opacity: 1, height: "100%" }); menuShow(show1, show2); textIntro(info); } }, [state]) return ( <div ref={(el) => (menuWrapper = el)} className="hamburger-menu"> <div ref={(el) => (show1 = el)} className="menu-secondary-background-color" ></div> <div ref={(el) => (show2 = el)} className="menu-layer"> <div className="container"> <div className="wrapper"> <div className="menu-links"> <nav> <ul> <li> <Link ref={(el) => (line1 = el)} to="/about-us" > About </Link> </li> <li> <Link ref={(el) => (line2 = el)} to="/gallery" > Gallery </Link> </li> <li> <Link ref={(el) => (line3 = el)} to="/contact-us" > Contact us </Link> </li> </ul> </nav> <div ref={(el) => (info = el)} className="info"> <h3>Our Vision</h3> <p> Lorem ipsum dolor sit amet consectetur adipisicing elit.... </p> </div> </div> </div> </div> </div> </div> ); } export default Menu
我们在Menu
组件中所做的是导入动画函数,它们是menuShow
、 menuHide
和textIntro
。 接下来,我们使用refs
钩子为我们的DOM
元素创建的每个引用分配变量, useRef
null
作为它们的值传递。 在useEffect
挂钩中,我们检查menu
的状态,如果clicked
为false
,我们调用menuHide
函数,否则,如果clicked
状态为 true ,我们调用menuShow
函数。 最后,我们确保相关的DOM
元素被传递了它们的特定refs
,它们是menuWrapper
、 show1
、 show2
。 有了这个,我们的菜单就变成了动画。
让我们看看它的外观。
我们要实现的最后一个动画是让我们画廊中的图像在滚动时skew
。 现在让我们看看我们画廊的状态。
要在我们的画廊中实现倾斜动画,让我们转到Animate.js
并为其添加一些代码。
.... //Skew gallery Images export const skewGallery = elem1 => { //register ScrollTrigger gsap.registerPlugin(ScrollTrigger); // make the right edge "stick" to the scroll bar. force3D: true improves performance gsap.set(elem1, { transformOrigin: "right center", force3D: true }); let clamp = gsap.utils.clamp(-20, 20) // don't let the skew go beyond 20 degrees. ScrollTrigger.create({ trigger: elem1, onUpdate: (self) => { const velocity = clamp(Math.round(self.getVelocity() / 300)); gsap.to(elem1, { skew: 0, skewY: velocity, ease: "power3", duration: 0.8, }); }, }); }
我们创建了一个名为skewGallery
的函数,将elem1
作为参数传递,并注册ScrollTrigger
。
ScrollTrigger是 GSAP 中的一个插件,它使我们能够触发基于滚动的动画,例如在页面滚动时倾斜图像的情况。
为了使右边缘粘在滚动条上,我们将right center
值传递给transformOrigin
属性,我们还在force3D
属性设置为 true 以提高性能。
我们声明了一个clamp
变量来计算我们的偏斜并确保它不超过20degs
。 在ScrollTrigger
对象中,我们将trigger
属性分配给elem1
参数,这将是我们调用此函数时需要触发的元素。 我们有一个onUpdate
回调函数,里面是一个velocity
变量,用于计算当前速度并将其除以300
。
最后,我们通过设置其他值来根据当前值对元素进行动画处理。 我们将skew
初始设置为0
并将skewY
设置为0.8
处的velocity
变量。
接下来,我们必须在App.js
文件中调用这个函数。
.... import { skewGallery } from "./components/Animate" function Gallery() { let skewImage = useRef(null); useEffect(() => { skewGallery(skewImage) }, []); return ( <div ref={(el) => (skewImage = el)}> <Image/> </div> ) } ....
在这里,我们从skewGalley
导入了./components/Animate
,创建了一个针对图像元素的skewImage
ref。 在useEffect
钩子中,我们调用了skewGallery
函数并将skewImage
ref 作为参数传递。 最后,我们将skewImage
传递给ref
属性。
你会同意我的看法,到目前为止,这是一段非常酷的旅程。 这是 CodeSanbox 上的预览
本文的支持 repo 可在Github 上找到。
结论
我们已经在一个 React 项目中探索了 GSAP 的潜力,在本文中我们只触及了皮毛,您可以使用 GSAP 做的事情没有限制,因为它涉及动画。 GSAP 的官方网站提供了额外的提示,以帮助您彻底了解方法和插件。 有很多演示会让您对人们使用 GSAP 所做的事情大吃一惊。 我很想在评论部分听听您对 GSAP 的体验。
资源
- GSAP 文档,GreenSock
- “GreenSock 动画平台初学者指南”,Nicholas Kramer,freeCodeCamp
- “Greensock Animation API (GSAP) 动画简介”,Zell Liew