React動畫實現方案之 Framer Motion,讓你的頁面“自己”動起來

前言相信很多前端同學都或多或少和動畫打過交道 。有的時候是產品想要的過度效果;有的時候是UI想要的酷炫動畫 。但是有沒有人考慮過,是不是我們的頁面上面的每一次變化,都可以像是自然而然的變化;是不是每一次用戶點擊所產生的交互,都可以在頁面上活過來呢?
歡迎你打開了新的前端動畫世界——《Framer Motion》
效果體驗這里,我在framer官網上面給大家錄制了一下大概的使用效果 。

React動畫實現方案之 Framer Motion,讓你的頁面“自己”動起來

文章插圖
在我們的常規認知中,實現這樣的效果其實需要很多的css來實現,或者說需要我們進行大量的定制化邏輯編寫 。但是如果我們使用framer motion的話,只需要如下代碼:
import { AnimatePresence, motion } from 'framer-motion';const [selectedId, setSelectedId] = useState(null);{items.map(item => (<motion.div layoutId={item.id} onClick={() => setSelectedId(item.id)}><motion.h5>{item.subtitle}</motion.h5><motion.h2>{item.title}</motion.h2></motion.div>))}<AnimatePresence>{selectedId && (<motion.div layoutId={selectedId}><motion.h5>{item.subtitle}</motion.h5><motion.h2>{item.title}</motion.h2><motion.button onClick={() => setSelectedId(null)} /></motion.div>)}</AnimatePresence>從上面的實現我們可以看出,framer-motion可以說是我們在用react動效開發過程中的必備利器 。那么接下來 , 我給大家簡單介紹一些framer motion的基礎用法 。
快速開始Framer Motion 需要 React 18 或更高版本 。
安裝從npm安裝framer-motion
npm install framer-motion輸入安裝后,您可以通過framer-motion引入Framer Motion
import { motion } from "framer-motion"export const MyComponent = ({ isVisible }) => (<motion.div animate={{ opacity: isVisible ? 1 : 0 }} />)使用方式Framer motion的核心API是motion的組件 。每個HTMLSVG標簽都有對應的motion組件 。
他們渲染的結果與對應的原生組件完全一致,并在其之上增加了一些動畫和手勢相關的props 。
比如:
<motion.div /><motion.span /><motion.h1 /><motion.svg />...示例比如我們現在想要實現一個側邊欄效果 。
節點的掛載與卸載(mount、unmount)
React動畫實現方案之 Framer Motion,讓你的頁面“自己”動起來

文章插圖
如果我們自己來實現的話,可能要考慮它的keyframe,它的初始狀態與最終的css樣式 。那么如果用framer-motion來如何實現呢?
首先我們來設計一個會動的按鈕Icon:
import * as React from "react";import { motion } from "framer-motion";const Path = props => (<motion.pathfill="transparent"strokeWidth="3"stroke="hsl(0, 0%, 18%)"strokeLinecap="round"{...props}/>);const MenuToggle = ({ toggle }) => (<button onClick={toggle}><svg width="23" height="23" viewBox="0 0 23 23"><Pathvariants={{closed: { d: "M 2 2.5 L 20 2.5" },open: { d: "M 3 16.5 L 17 2.5" }}}/><Pathd="M 2 9.423 L 20 9.423"variants={{closed: { opacity: 1 },open: { opacity: 0 }}}transition={{ duration: 0.1 }}/><Pathvariants={{closed: { d: "M 2 16.346 L 20 16.346" },open: { d: "M 3 2.5 L 17 16.346" }}}/></svg></button>);接下來,就由這個按鈕來控制側邊欄的展示(mount)隱藏(unmount):
import * as React from "react";import { useRef } from "react";import { motion, useCycle } from "framer-motion";import { useDimensions } from "./use-dimensions";const sidebar = {open: (height = 1000) => ({clipPath: `circle(${height * 2 + 200}px at 40px 40px)`,transition: {type: "spring",stiffness: 20,restDelta: 2}}),closed: {clipPath: "circle(30px at 40px 40px)",transition: {delay: 0.5,type: "spring",stiffness: 400,damping: 40}}};export const Example = () => {const [isOpen, toggleOpen] = useCycle(false, true);const containerRef = useRef(null);const { height } = useDimensions(containerRef);return (<motion.navinitial={false}animate={isOpen ? "open" : "closed"}custom={height}ref={containerRef}><motion.div className="background" variants={sidebar} /><MenuToggle toggle={() => toggleOpen()} /></motion.nav>);};也就是說,其實我們更多需要做的事情,從思考如何設計各元素之間的css聯動與keyframe書寫變成了如何按照文檔寫好framer-motion的配置

推薦閱讀