使用 GreenSock 为 React 组件制作动画

已发表: 2022-03-10
快速总结 ↬ GreenSock 动画平台 (GSAP) 是一组 JavaScript 函数,可让您随时间对值/属性/CSS 属性进行补间,并将这些补间插入到时间轴中以实现更复杂的动画。 在本文中,Blessing 解释了 GSAP 如何通过将其功能集成到 React 组件中来构建具有各种动画的示例登录页面,从而很好地与 React 库配合使用。

在万维网的早期,事情相当静态和无聊。 在引入动画之前,网页主要基于印刷界的图形设计和布局。 动画可以比静态网页更长时间地吸引和吸引人们的注意力,并且可以更清晰有效地传达想法或概念。

但是,如果处理不当,动画可能会阻碍用户与您的产品的交互并对牵引力产生负面影响。 GreenSock 动画平台 AKA (GSAP) 是一个功能强大的 JavaScript 库,使前端开发人员、动画师和设计师能够创建基于时间轴的高性能动画。 它允许动画爱好者精确控制他们的动画序列,而不是 CSS 提供的有时约束的keyframeanimation属性。

在本文中,我将向您介绍 GSAP 的一些功能,例如scrollTriggersTimelinesEasing等,最后我们将通过使用这些功能为 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)

我们来看看这个语法代表什么;

  1. method是指您想要补间的 GSAP 方法。
  2. element是您要设置动画的元素。 如果要同时为多个元素创建补间,可以将元素数组传递给element
  3. duration是补间的持续时间。 它是以秒为单位的整数(没有s后缀!)。
  4. vars是您要设置动画的属性的对象。 稍后再谈。

GSAP 方法

GSAP 提供了许多创建动画的方法。 在本文中,我们只提及gsap.togsap.fromgsap.fromTo等少数几个。 您可以在他们的文档中查看其他很酷的方法。 本节讨论的方法将在本教程后面的构建项目中使用。

  • gsap.to()对象应该被动画化的值,即动画对象的最终属性值 - 如下所示:
     gsap.to('.ball', {x:250, duration: 5})

为了演示to方法,下面的 codepen 演示显示,当组件安装时,具有250px类球的元素将在 5 秒内跨x-axis移动。 如果没有给出持续时间,将使用默认的 500 毫秒。

请参阅 Blessing Krofegha 的 Pen [GSAP REACT DEMO1](https://codepen.io/smashingmag/pen/LYNrzMB)。

请参阅 Blessing Krofegha 的 Pen GSAP REACT DEMO1。

注意xy-axis轴分别表示水平轴和垂直轴,在 CSS 变换属性(例如translateXtranslateY )中,它们表示为xy用于pixel-measured变换, xPercentyPercent表示基于百分比的变换。

要查看完整的代码片段,请查看 codepen 游乐场。

  • gsap.from() - 定义一个对象应该被动画化的值 - 即动画的起始值:
     gsap.from('.square', {duration:3, scale: 4})

3seconds演示展示了当组件安装时,具有square类的元素如何在 3 秒内从 4 的比例调整大小。 检查此 codepen 上的完整代码片段。

请参阅 Blessing Krofegha 的 Pen [GSAP REACT DEMO2](https://codepen.io/smashingmag/pen/bGpKoPV)。

请参阅 Blessing Krofegha 的 Pen GSAP REACT DEMO2。
  • 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 secondssquare类从不透明度0变为1x-axis仅当组件安装时x-axis 。 要查看fromTo方法的工作原理和完整的代码片段,请查看下面 CodePen 上的演示。

请参阅 Blessing Krofegha 的 Pen [React GSAP FromTo 演示](https://codepen.io/smashingmag/pen/WNwyXex)。

请参阅 Blessing Krofegha 的 Pen React GSAP FromTo 演示。

注意:每当我们为位置属性设置动画时,例如lefttop ,我们必须确保相关元素的 CSS 位置属性必须是relativeabsolutefixed

缓和

GSAP 官方文档将缓动定义为更改 Tweens 时间的主要方式。 它确定对象如何在不同点改变位置。 Ease 控制 GSAP 中动画的变化率,用于设置对象动画的样式。

GSAP 提供了不同类型的缓动和选项,让您可以更好地控制动画的行为方式。 它还提供了一个缓动可视化工具来帮助您选择您喜欢的缓动设置。

缓动共有三种类型,它们的操作各不相同。

  1. in() — 运动开始缓慢,然后在动画结束时加快步伐。
  2. out() — 动画快速开始,然后在动画结束时减速。
  3. inOut() — 动画开始缓慢,中途加快步伐,然后缓慢结束。

请参阅 Blessing Krofegha 的 Pen [React GSAP Easing 演示](https://codepen.io/smashingmag/pen/abNKLaE)。

请参阅 Blessing Krofegha 的 Pen React GSAP Easing 演示。

在这些缓动示例中,我们链接了显示三种缓动类型的补间bounce.inbounce.outbounce.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 游乐场。

请参阅 Blessing Krofegha 的 Pen [React GSAP(使用时间线缓和)演示](https://codepen.io/smashingmag/pen/zYqaEmE)。

请参阅 Blessing Krofegha 的 Pen React GSAP(Easing with Timeline)演示。

现在我们已经了解了 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 ,使对象在2secease返回。

要查看这是否有效,请转到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挂钩监听页面变化,如果页面发生变化,我们将clickedmenuName状态值分别设置为falseMenu

为了处理我们的菜单,我们检查了初始状态的值是否为 false,如果为 true,我们将initialclickedmenuName的值更改为nulltrueClose 。 否则,我们检查按钮是否被单击,如果为真,我们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.7secright 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组件中所做的是导入动画函数,它们是menuShowmenuHidetextIntro 。 接下来,我们使用refs钩子为我们的DOM元素创建的每个引用分配变量, useRef null作为它们的值传递。 在useEffect挂钩中,我们检查menu的状态,如果clickedfalse ,我们调用menuHide函数,否则,如果clicked状态为 true ,我们调用menuShow函数。 最后,我们确保相关的DOM元素被传递了它们的特定refs ,它们是menuWrappershow1show2 。 有了这个,我们的菜单就变成了动画。

让我们看看它的外观。

动画菜单。
动画菜单。

我们要实现的最后一个动画是让我们画廊中的图像在滚动时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 的体验。

资源

  1. GSAP 文档,GreenSock
  2. “GreenSock 动画平台初学者指南”,Nicholas Kramer,freeCodeCamp
  3. “Greensock Animation API (GSAP) 动画简介”,Zell Liew