Представляем Framer Motion

Опубликовано: 2022-03-10
Краткий обзор ↬ Анимации, если они сделаны правильно, очень сильны. Однако создание привлекательных анимаций с помощью CSS может оказаться сложной задачей. На помощь приходит Framer Motion. С Framer Motion вам не нужно быть экспертом в CSS, чтобы создавать красивые анимации. Framer Motion предоставляет нам готовые анимации и низкоуровневый API, с которым мы можем взаимодействовать для интеграции этих анимаций в наши приложения.

В этой статье мы более подробно рассмотрим, как Framer Motion помогает нам в создании потрясающих анимаций. Мы узнаем, как работают компоненты движения, и узнаем, как связать анимацию вместе. Мы рассмотрим, как создавать анимацию, запускаемую жестами, анимацию по времени и прокрутку с помощью Framer motion. Попутно мы будем использовать полученные знания для создания пяти демонстрационных приложений, которые я настроил, чтобы показать нам, как мы можем интегрировать Framer Motion в реальные приложения.

Этот учебник будет полезен читателям, которые заинтересованы в интеграции анимации в свое приложение React.

Примечание. Эта статья требует базового понимания React и CSS.

Что такое Framer Motion?

Framer Motion — это библиотека анимации, которая упрощает создание анимации. Его упрощенный API помогает нам абстрагироваться от сложностей, стоящих за анимацией, и позволяет с легкостью создавать анимации.

Компоненты движения

Это строительные блоки движения Framer. Компоненты движения создаются путем добавления префикса motion к вашему обычному элементу HTML и SVG (например, motion.h1 ). Компоненты движения могут принимать несколько реквизитов, основным из которых является animate реквизит. Этот реквизит принимает объект, в котором мы определяем свойства того компонента, который хотим анимировать. Определяемые нами свойства будут анимированы при монтировании компонента в DOM.

Давайте анимируем текст h1 с помощью Framer Motion. Во-первых, мы устанавливаем библиотеку 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 сдвинется на 20 пикселей вправо и переместится на 20 пикселей вверх при загрузке. Когда единицы измерения не добавляются, расчеты выполняются с использованием пикселей. Однако вы можете явно указать единицы, на которых должны основываться вычисления, 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 позволяет нам определить, как происходит анимация. С его помощью мы определяем, как значения анимируются из одного состояния в другое. Среди прочего, мы можем определить duration , delay и type анимации, используя этот реквизит.

 <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>

Пока это работает, поддержка variants в Framer Motion позволяет нам извлекать определения анимации в объект вариантов. 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" } }

Вместо того, чтобы передавать определения animate в initial и анимационные реквизиты компонента напрямую, мы извлекаем эти определения в автономные варианты объектов. В объектах вариантов мы определяем имена вариантов, которые описывают имя каждой анимации как варианты.

 <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 , который мы определили. Поскольку мы не определяем анимацию для контейнера, мы передаем пустые объекты в initial и animate . Имена ваших анимаций должны быть одинаковыми в каждом объекте варианта, чтобы распространение работало.

Теперь мы понимаем основы Framer Motion. Давайте начнем создавать наш список из 5 демонстрационных приложений.

Магазин иконок

Мы можем создавать интерактивные анимации на основе жестов. Компоненты движения в настоящее время могут прослушивать обнаружение наведения, касания, панорамирования и перетаскивания. Мы будем создавать это приложение Icon Shop, используя whileHover .

Компоненты

  • 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> );

Мы импортируем созданные нами компоненты движения H1 и H2 в файл Styles.js . Поскольку они являются компонентами движения, мы используем initial и animate , чтобы определить их поведение до и во время их монтирования. Здесь мы также импортируем и отображаем компонент 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 .

Подобно H1 и H2 в App.js , мы определяем анимацию Container , используя свойства initial и animate . Когда он загрузится, он создаст классный эффект скольжения слева в браузере.

Теперь 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> ); };

Здесь мы создаем два варианта объекта с beforeHover и onHover . В объекте CardVariants мы изначально ничего делать не хотим, поэтому beforeHover — это пустой объект. onHover мы увеличиваем масштаб коробки карты.

В объекте IconVariants мы определяем начальное состояние IconBox в его beforeHover . Мы устанавливаем его непрозрачность на 0 и смещаем его вверх на 50px. Затем в onHover мы устанавливаем непрозрачность обратно на 1, возвращаем ее в положение по умолчанию и меняем тип перехода на tween . Затем мы переходим в вариантах к их соответствующим компонентам движения. Мы используем распространение, поэтому нам не нужно явно устанавливать initial и IconBox animate

Анимированная панель навигации

Мы создадим простой компонент навигации и посмотрим, как можно создать временные отношения между родительским и дочерним компонентами движения.

Компоненты

  • 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 варианта объекта, iconVariants , menuVariants и linkVariants , где мы определяем анимацию для компонентов движения SvgBox , Nav и Link соответственно. iconVariants используется для поворота SvgBox на 135 градусов при наведении на него курсора. Нам не нужно добавлять «градус» к значению. В menuVariants мы управляем верхней позицией Nav , как если бы вы использовали свойство position в CSS. Мы переключаем верхнее положение Nav в зависимости от состояния isOpen .

С помощью вариантов мы можем создавать временные отношения между родительским и дочерним компонентами движения. Мы определяем отношения между родительским Nav и его дочерним Link , используя свойство when в объекте перехода. Здесь установите для него значение 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 , мы условно анимируем Nav и Link на основе isOpen .

Анимированный модальный

Мы создадим модальный компонент и узнаем об AnimatePresence AnimatePresence Motion и о том, как он позволяет нам анимировать элементы, когда они покидают 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 , которое будет использоваться для условного рендеринга модального окна. Функция toggleModal будет переключать состояние всякий раз, когда нажимается ToggleButton . ToggleButton — это компонент движения, поэтому мы можем определить для него анимацию. Когда он монтируется, он скользит слева. Эта анимация длится 0,5 секунды. Мы также передаем состояние 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>

Мы импортируем AnimatePresence из framer framer-motion . Это позволяет нам установить анимацию выхода для компонентов, когда они покидают DOM. Мы условно визуализируем Modal на основе состояния showModal . Мы определяем анимацию для ModalBox и ModalContent через их initial и animate свойства. Здесь также есть новая опора, exit . Наличие AnimatePresence в качестве оболочки позволяет нам добавлять анимации выхода в ModalBox в свойстве exit .

Анимация прокрутки

Мы будем использовать комбинацию хука useAnimation и react 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.start controls.stop , которые мы можем использовать для ручного запуска и остановки анимации. Мы передаем начальный hidden аниматон в StyledBox . Мы передаем элементы управления, которые мы определили с помощью метода start , в анимацию StyledBox .

Хук useInView в useInView react-intersection-observer позволяет нам отслеживать, когда компонент виден в области просмотра. useInView дает нам доступ к ref , который мы передаем компоненту, который мы хотим наблюдать, и логическому inView , которое говорит нам, является ли этот элемент inView или нет. Мы используем useEffect для вызова controls.start всякий раз, когда наблюдаемый нами элемент StyledBox находится в поле зрения. Мы передаем controls и inView как зависимости useEffect . Кроме того, мы передаем определенные нами варианты, BoxVariants в StyledBox .

Анимация героя

Мы создадим классную анимацию баннера героя, используя хук 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 варианта: H1Variants , TextVariants и BannerVariants . Однако наше внимание сосредоточено на BannerVariants . Мы определяем 2 анимации, animationOne и animationTwo в BannerVariants . Это анимации, которые мы передаем в useCycle для циклического прохождения.

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

useCycle работает аналогично useState . В деструктурированном массиве animation представляет активную анимацию, будь то animationOne или animationTwo . Функция cylceAnimation , которая циклически переключается между определенной нами анимацией. Мы передаем анимацию, которую хотим циклически повторять, в useCycle и вызываем cylceAnimation через 2 секунды в useEffect .

 <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 , а через 2 секунды будет вызван cycleAnimation , который вызовет animationTwo .

Как однажды сказал мудрый Свин, «это все люди».

Заключение

Мы ознакомились с основами Framer Motion и ознакомились с некоторыми демонстрационными проектами, которые дают нам представление о диапазоне анимаций, которые мы можем создавать. Тем не менее, вы можете сделать гораздо больше с ним. Я призываю вас погрузиться в документы и сходить с ума.

Ресурсы

  • Документы API Framer Motion, Framer Motion
  • наблюдатель за реакцией на пересечении, npm
  • Framer Motion для React, NetNinja