使用 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
)設置動畫時,我們必須確保相關元素必須具有relative
、 absolute
或fixed
的 CSS 位置屬性。
緩和
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