介紹 Framer Motion

已發表: 2022-03-10
快速總結↬動畫,如果做得好,是強大的。 但是,使用 CSS 創建引人注目的動畫可能會很棘手。 進來了 Framer Motion。 使用 Framer Motion,您無需成為 CSS 專家即可製作精美的動畫。 Framer Motion 為我們提供了生產就緒的動畫和一個低級 API,我們可以與之交互以將這些動畫集成到我們的應用程序中。

在本文中,我們將詳細了解 Framer Motion 如何幫助我們創建出色的動畫。 我們將學習運動組件是如何工作的,並學習如何將動畫鏈接在一起。 我們將研究如何使用 Framer 動作製作手勢觸發、定時和滾動動畫。 在此過程中,我們將使用我們學到的東西來構建我設置的五個演示應用程序,以向我們展示如何將 Framer Motion 集成到實際應用程序中。

本教程將對有興趣在其 React 應用程序中集成動畫感興趣的讀者有所幫助。

注意:本文需要對 React 和 CSS 有基本的了解。

什麼是成幀器運動?

Framer Motion 是一個動畫庫,可以輕鬆創建動畫。 其簡化的 API 幫助我們抽像出動畫背後的複雜性,並允許我們輕鬆地創建動畫。

運動組件

這些是 Framer 運動的構建塊。 運動組件是通過在常規 HTML 和 SVG 元素(例如motion.h1 )前添加motion前綴來創建的。 運動組件可以接受多個道具,基本的一個是animate道具。 這個道具接受一個對象,我們在其中定義我們想要動畫的組件的屬性。 當組件安裝在 DOM 中時,我們定義的屬性將被動畫化。

讓我們使用 Framer Motion 為 h1 文本設置動畫。 首先,我們安裝 framer-motion 庫並導入motion

 npm i framer-motion import { motion } from 'framer-motion';

然後我們將 h1 轉換為運動分量。

 <motion.h1 animate={{x: 20, y: -20}}> This is a motion component </motion.h1>

這將導致h1在加載時向右滑動 20px 並向上移動 20px。 如果不添加單位,則使用像素進行計算。 但是,您可以明確設置您希望計算所基於的單位animate={{x: "20rem", y: "-20rem"}}>

跳躍後更多! 繼續往下看↓

默認情況下,運動組件將從其樣式定義的狀態動畫到animate道具中的狀態。 但是,如果我們願意,我們可以使用initial屬性劫持並定義組件的初始動畫狀態。 雖然animate道具用於定義組件在安裝時的行為,但initial道具在它們安裝之前定義它們的行為。

如果我們希望我們的 h1 從左側進入,我們使用初始屬性來控制它。

 <motion.h1 initial={{x: -1000}} animate={{x: 20}}> This is a motion component </motion.h1>

現在,當h1掛載時,它會從左側滑入。

我們不僅限於單個動畫。 我們可以在一組值中定義一系列稱為keyframes的動畫。 每個值將按順序進行動畫處理。

 <motion.h1 initial={{x: -1000}} animate={{x: [20, 50, 0, -70, 40] }}> This is a motion component </motion.h1>

transition道具允許我們定義動畫是如何發生的。 有了它,我們定義了值如何從一種狀態變為另一種狀態。 除此之外,我們可以使用這個道具定義動畫的durationdelaytype

 <motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion component </motion.h1>

假設我們要同時為多個運動組件設置動畫,如下面的代碼片段所示。

 <div className="App"> <motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion h1 </motion.h1> <motion.h2 initial={{ y: -1000 }} animate={{ y: 0 }} transition={{ type: "tween", duration: "1", delay: ".4" }}>This is a motion h2 </motion.h2> <motion.h3 initial={{ x: 100, opacity: 0 }} animate={{ x: 0, opacity: 1 }}> This is a motion h3 </motion.h3> <motion.h4 initial={{ scale: 0.7 }} animate={{ scale: 1.7 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion h4 </motion.h4> </div>

雖然這可行,但 Framer Motion 中的 variables 屬性使我們能夠將動畫定義提取到variants對像中。 variants不僅使我們的代碼更清晰,而且使我們能夠創建更強大和更複雜的動畫。

將我們的動畫定義提取到變體對像中,我們有:

 const H1Variants = { initial: { x: -1000 }, animate: { x: 0 }, transition: { type: "tween", duration: 2, delay: 1 } } const H2Variants = { initial: { y: -1000 }, animate: { y: 0 }, transition: { type: "tween", duration: 1, delay: .4 } } const H3Variants = { initial:{ x: 100, opacity: 0 }, animate:{ x: 0, opacity: 1 } } const H4Variants = { initial:{ scale: 0.7 }, animate:{ scale: 1.7 }, transition:{ type: "tween", duration: "2", delay: "1" } }

我們沒有將動畫定義直接傳遞到組件的initialanimate道具中,而是將這些定義提取到獨立的變體對像中。 在變體對像中,我們定義了將每個動畫名稱描述為變體的變體名稱。

 <div className="App"> <motion.h1 variants={H1Variants} initial='initial' animate='animate' > This is a motion h1 </motion.h1> <motion.h2 variants={H2Variants} initial='initial' animate='animate' > This is a motion h2 </motion.h2> <motion.h3 variants={H3Variants} initial='initial' animate='animate' > This is a motion h3 </motion.h3> <motion.h4 variants={H4Variants} initial='initial' animate='animate' > This is a motion h4 </motion.h4> </div>

variants道具中,我們為每個運動組件傳入變體對象的名稱,然後將動畫傳遞給initial道具和animate道具。

我們可以將當前設置與變體一起進一步減少重複。 使用變體,我們可以通過 DOM 從父運動組件向下傳播動畫屬性。 為此,我們為父motion.div創建變體,其變體對像中的動畫名稱與其子對象相似。 通過這樣做,我們不必將動畫名稱傳遞給每個子組件。 在幕後,父元素為我們處理。

 const ContainerVariants = { initial: {}, animate: {} }; const H1Variants = { initial: { x: -1000 }, animate: { x: 0 }, transition: { type: "tween", duration: 2, delay: 1 } }; //more variants below <motion.div className="App" variants={ContainerVariants} initial="initial" animate="animate" > <motion.h1 variants={H1Variants}>This is a motion h1</motion.h1> <motion.h2 variants={H2Variants}>This is a motion h2</motion.h2> <motion.h3 variants={H3Variants}>This is a motion h3</motion.h3> <motion.h4 variants={H4Variants}>This is a motion h4</motion.h4> </motion.div>

現在我們有了一個沒有重複的更簡潔的代碼。 我們將容器 div 轉換為一個運動組件,這樣我們就可以傳入我們定義的ContainerVariants對象。 由於我們沒有在容器上定義任何動畫,我們將空對像傳遞給initialanimate 。 每個變體對像中的動畫名稱必須相同,才能使傳播工作。

現在我們了解了 Framer Motion 的基礎知識。 讓我們開始構建我們的 5 個演示應用程序。

圖標店

我們可以根據手勢創建交互式動畫。 運動組件目前能夠偵聽懸停、點擊、平移和拖動手勢檢測。 我們將使用whileHover構建這個 Icon Shop 應用程序。

成分

  • App.js :這包含標題文本。
  • Card.jsx :在這裡,我們定義了圖標卡片的動畫。
  • CardContainer.jsx :我們導入並循環遍歷圖標。
  • styles.js :創建、設置和導出運動組件。 我使用 styled-components 來設置組件的樣式。

讓我們從App.js開始。

 import { H1, H2 } from "./Styles"; import CardContainer from "./CardContainer"; return ( <div> <H1 initial={{ y: -100 }} animate={{ y: 0, transition: { delay: 1 } }}> Icon Shop </H1> <H2 initial={{ x: -1000 }} animate={{ x: 0, transition: { delay: 1 } }}> Hover over the cards to see the motion magic </H2> <CardContainer /> </div> );

我們導入在Styles.js文件中創建的H1H2運動組件。 由於它們是運動組件,我們使用initialanimate道具來定義它們在安裝之前和安裝時的行為。 在這裡,我們還導入並顯示CardContiner組件。

現在, CardContainer.js

 import { Container } from "./Styles"; import Card from "./Card"; import { ReactComponent as AddIcon } from "./assets/add.svg"; import { ReactComponent as AirplaneIcon } from "./assets/airplane.svg"; import { ReactComponent as AlarmIcon } from "./assets/alarm.svg"; //more svg imports below... const icons = [ <AddIcon />, <AirplaneIcon />, <AlarmIcon />, //more icons below ]; const CardContainer = () => { return ( <Container initial={{ x: -1000 }} animate={{ x: 0 }}> {icons.map((icon) => ( <Card icon={icon} /> ))} </Container> ); };

在這裡,我們導入 SVG、 Container運動組件和Card組件。

App.js中的H1H2類似,我們使用initialanimate屬性定義Container的動畫。 當它加載時,它會產生一種從瀏覽器左側滑入的酷炫效果。

現在, Card.js

 import { CardBox, IconBox } from "./Styles"; const CardVariants = { beforeHover: {}, onHover: { scale: 1.1 } }; const IconVariants = { beforeHover: { opacity: 0, y: -50 }, onHover: { opacity: 1, y: 0, scale: 1.5, transition: { type: "tween" } } }; const Card = ({ icon }) => { console.log(icon); return ( <CardBox variants={CardVariants} initial="beforeHover" whileHover="onHover"> <IconBox variants={IconVariants}>{icon}</IconBox> </CardBox> ); };

在這裡,我們使用beforeHoveronHover動畫創建了兩個變體對象。 在CardVariants對像中,我們最初不想做任何事情,所以beforeHover是一個空對象。 onHover我們增加了卡片框的比例。

IconVariants對像中,我們在其beforeHover中定義了IconBox的初始狀態。 我們將其不透明度設置為 0 並將其向上推 50 像素。 然後,在onHover中,我們將不透明度設置回 1,將其推回默認位置,並將過渡類型更改為tween 。 然後我們將變量傳遞給它們各自的運動分量。 我們利用傳播,因此我們不需要顯式地將initialanimate道具設置為IconBox組件。

動畫導航欄

我們將構建一個簡單的 Navigation 組件,並了解如何在父子運動組件之間創建時序關係。

成分

  • App.js :這包含標題文本。
  • Styles.js :創建、設置和導出運動組件。 組件使用 styled-components 設置樣式。

讓我們看一下App.js文件。

 import { Header, Nav, Link, SvgBox } from "./Styles"; function App() { const [isOpen, setIsOpen] = useState(false); const iconVariants = { opened: { rotate: 135 }, closed: { rotate: 0 } }; const menuVariants = { opened: { top: 0, transition: { when: "beforeChildren", staggerChildren: 0.5 } }, closed: { top: "-90vh" } }; const linkVariants = { opened: { opacity: 1, y: 50 }, closed: { opacity: 0, y: 0 } };

我們創建一個isOpen狀態,用於檢查導航欄是否打開。 我們創建了 3 個變體對象, iconVariantsmenuVariantslinkVariants ,我們分別在其中定義了SvgBoxNavLink運動組件的動畫。 iconVariants用於在SvgBox懸停時將其旋轉 135 度。 我們不需要在值中添加“deg”。 在menuVariants中,我們控制Nav的頂部位置,就像使用 CSS 中的position屬性一樣。 我們根據isOpen狀態切換Nav的頂部位置。

通過變體,我們可以在父子運動組件之間創建時序關係。 我們使用轉換對像中的when屬性定義父Nav與其子之間的關係Link 。 在這裡,將其設置為beforeChildren ,因此父組件的動畫將在子動畫開始之前完成。

使用staggerChildren屬性,我們為每個鏈接設置時間順序。 每個鏈接一個接一個出現需要 0.5 秒。 當Nav打開時,這會創建一個很好的視覺提示。 在linkVariants中,我們為每個鏈接的不透明度和垂直位置設置動畫。

 <div className="App"> <Header> <SvgBox variants={iconVariants} animate={isOpen ? "opened" : "closed"} onClick={() => setIsOpen(!isOpen)} > <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="https://www.w3.org/2000/svg" > <path d="M12 4C11.4477 4 11 4.44772 11 5V11H5C4.44772 11 4 11.4477 4 12C4 12.5523 4.44772 13 5 13H11V19C11 19.5523 11.4477 20 12 20C12.5523 20 13 19.5523 13 19V13H19C19.5523 13 20 12.5523 20 12C20 11.4477 19.5523 11 19 11H13V5C13 4.44772 12.5523 4 12 4Z" fill="#fff" /> </svg> </SvgBox> </Header> <Nav initial={false} variants={menuVariants} animate={isOpen ? "opened" : "closed"} > <Link variants={linkVariants}>home</Link> <Link variants={linkVariants}>about</Link> <Link variants={linkVariants}>gallery</Link> </Nav> </div>

在這裡,我們將變體傳遞給它們各自的組件。 在SvgBox中,只要單擊它,我們就會切換isOpen的狀態,然後根據狀態有條件地對其進行動畫處理。 與SvgBox一樣,我們根據isOpen的狀態有條件地為NavLink設置動畫。

動畫模態

我們將構建一個模態組件並了解 Framer Motion 的AnimatePresence ,以及它如何讓我們在元素離開 DOM 時對其進行動畫處理。

成分:

  • App.js :我們在這裡設置了showModal狀態。
  • Modal.jsx :實際的動畫工作發生在這裡。
  • Styles.js :創建、設置和導出運動組件。 組件使用 styled-components 設置樣式。

讓我們看看App.js

 import { ToggleButton, Container } from "./Styles"; import Modal from "./Modal"; function App() { const [showModal, setShowModal] = useState(false); const toggleModal = () => { setShowModal(!showModal); }; return ( <Container> <ToggleButton initial={{ x: -700 }} animate={{ x: 0, transition: { duration: 0.5 } }} onClick={toggleModal} > Toggle Modal </ToggleButton> <Modal showModal={showModal} /> </Container> ); }

我們創建一個showModal狀態,用於有條件地渲染模態。 每當單擊ToggleButton時, toggleModal函數都會切換狀態。 ToggleButton是一個運動組件,所以我們可以為它定義動畫。 安裝時,它會從左側滑入。 此動畫運行 0.5 秒。 我們還通過 props 將showModal狀態傳遞給Modal

現在, Modal.jsx

 import { AnimatePresence } from "framer-motion"; import { ModalBox, ModalContent, Container } from "./Styles"; <Container> <AnimatePresence> {showModal && ( <ModalBox initial={{ opacity: 0, y: 60, scale: 0.3 }} animate={{ opacity: 1, y: 0, scale: 1, transition: { type: "spring", stiffness: 300 } }} exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.6 } }} > <ModalContent initial={{ y: -30, opacity: 0 }} animate={{ y: 0, opacity: 1, transition: { delay: 1 } }} > Modal content!!!! </ModalContent> </ModalBox> )} </AnimatePresence> </Container>

我們從framer-motion導入AnimatePresence 。 它允許我們在組件離開 DOM 時為它們設置退出動畫。 我們根據showModal狀態有條件地渲染Modal 。 我們通過它們的initialanimate道具為ModalBoxModalContent定義動畫。 這裡還有一個新道具, exitAnimatePresence作為包裝器允許我們將退出動畫添加到exit道具中的ModalBox

滾動動畫

我們將結合使用useAnimation鉤子和react-intersection-observer來創建滾動觸發的動畫。

成分

  • App.js :我們為Box組件設置動畫並在App中渲染它
  • Styles.js :創建、設置和導出運動組件。 組件使用 styled-components 設置樣式。
 import React, { useEffect } from "react"; import { useAnimation } from "framer-motion"; import { useInView } from "react-intersection-observer"; import { Container, H1,StyledBox } from "./Styles"; const BoxVariants = { visible: { opacity: 1, x: 0, transition: { duration: 1 } }, hidden: { opacity: 0, x: 300 }, }; const Box = () => { const controls = useAnimation(); const [ref, inView] = useInView(); useEffect(() => { if (inView) { controls.start("visible"); } }, [controls, inView]); return ( <StyledBox ref={ref} animate={controls} initial="hidden" variants={BoxVariants} /> ); };

useAnimation鉤子允許我們控制動畫發生的順序。 我們可以使用controls.startcontrols.stop方法來手動啟動和停止動畫。 我們將初始hidden動畫傳遞給StyledBox 。 我們將使用start方法定義的控件傳遞給StyledBox animate prop。

react-intersection-observeruseInView鉤子允許我們跟踪組件何時在視口中可見。 useInView鉤子讓我們可以訪問ref ,我們將其傳遞給我們想要觀看的組件,以及inView布爾值,它告訴我們該元素是否是inView 。 每當我們正在觀看的元素StyledBox在視圖中時,我們就使用useEffect調用controls.start 。 我們將controlsinView作為useEffect的依賴項傳入。 此外,我們將我們定義的變體BoxVariantsStyledBox

英雄動畫

我們將使用useCycle鉤子構建一個很酷的英雄橫幅動畫。 我們將了解useCycle如何讓我們循環播放動畫。

 import React, { useEffect } from "react"; import { useCycle } from "framer-motion"; import { Container, H1, HeroSection, Banner, TextBox } from "./Styles"; import { ReactComponent as BannerIllustration } from "./bighead.svg"; const H1Variants = { initial: { y: -200, opacity: 0 }, animate: { y: 0, opacity: 1, transition: { delay: 1 } }, }; const TextVariants = { initial: { x: 400 }, animate: { x: 0, transition: { duration: 0.5 } }, }; const BannerVariants = { animationOne: { x: -250, opacity: 1, transition: { duration: 0.5 } }, animationTwo: { y: [0, -20], opacity: 1, transition: { yoyo: Infinity, ease: "easeIn" }, }, };

我們定義了 3 個變體, H1VariantsTextVariantsBannerVariants 。 但是,我們的重點是BannerVariants 。 我們在BannerVariants中定義了 2 個動畫, animationOneanimationTwo 。 這些是我們傳遞給useCycle循環的動畫。

 const [animation, cycleAnimation] = useCycle("animationOne", "animationTwo"); useEffect(() => { setTimeout(() => { cycleAnimation(); }, 2000); }, []);

useCycle的工作方式類似於useState掛鉤。 在解構數組中, animation表示處於活動狀態的動畫,無論是animationOne還是animationTwo 。 在我們定義的動畫之間循環的cylceAnimation函數。 我們將想要循環的動畫傳入useCycle並在 2 秒後調用cylceAnimationuseEffect中。

 <div className="App"> <Container> <H1 variants={H1Variants} initial="initial" animate="animate"> Cool Hero Section Anmiation </H1> <HeroSection> <TextBox variants={TextVariants} initial="initial" animate="animate"> Storage shed, troughs feed bale manure, is garden wheat oats at augers. Bulls at rose garden cucumbers mice sunflower wheat in pig. Chainsaw foal hay hook, herbs at combine harvester, children is mallet. Goat goose hen horse. Pick up truck livestock, pets and storage shed, troughs feed bale manure, is garden wheat oats at augers. Lamb. </TextBox> <Banner variants={BannerVariants} animate={animation}> <BannerIllustration /> </Banner> </HeroSection> </Container> </div>

在一切結束時,我們將變體傳遞給它們各自的組件並觀察奇蹟發生。 這樣一來, Banner將根據我們在 animationOne 中定義的animationOne從右側滑入,2 秒後,將調用cycleAnimation觸發animationTwo

正如一隻聰明的豬曾經說過的那樣,“這就是所有人。”

結論

我們已經了解了 Framer Motion 的基礎知識,並查看了一些演示項目,這些項目讓我們了解了我們可以創建的動畫範圍。 但是,您可以用它做更多的事情。 我鼓勵您深入研究文檔並瘋狂。

資源

  • Framer Motion Api 文檔,Framer Motion
  • react-intersection-observer,npm
  • 用於 React、NetNin​​ja 的 Framer Motion