前言

相信很多前端同学都或多或少和动画打过交道。有的时候是产品想要的过度效果;有的时候是UI想要的酷炫动画。但是有没有人考虑过,是不是我们的页面上面的每一次变化,都可以像是自然而然的变化;是不是每一次用户点击所产生的交互,都可以在页面上活过来呢?

欢迎你打开了新的前端动画世界——《Framer Motion》

效果体验

这里,我在framer官网上面给大家录制了一下大概的使用效果。

在我们的常规认知中,实现这样的效果其实需要很多的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)

如果我们自己来实现的话,可能要考虑它的keyframe,它的初始状态与最终的css样式。那么如果用framer-motion来如何实现呢?

首先我们来设计一个会动的按钮Icon:

import * as React from "react";
import { motion } from "framer-motion"; const Path = props => (
<motion.path
fill="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">
<Path
variants={{
closed: { d: "M 2 2.5 L 20 2.5" },
open: { d: "M 3 16.5 L 17 2.5" }
}}
/>
<Path
d="M 2 9.423 L 20 9.423"
variants={{
closed: { opacity: 1 },
open: { opacity: 0 }
}}
transition={{ duration: 0.1 }}
/>
<Path
variants={{
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.nav
initial={false}
animate={isOpen ? "open" : "closed"}
custom={height}
ref={containerRef}
>
<motion.div className="background" variants={sidebar} />
<MenuToggle toggle={() => toggleOpen()} />
</motion.nav>
);
};

也就是说,其实我们更多需要做的事情,从思考如何设计各元素之间的css联动与keyframe书写变成了如何按照文档写好framer-motion的配置。哪个更轻松相信大家一目了然。

列表

侧边栏一般都是带有菜单的,那么我们是不是可以让这个侧边栏也有一个逐次出现的效果呢?就像这样:

这里我们是不是已经开始肌肉记忆般的计算延迟时间,思考如何进行整体效果的分配。那么如果这里我们使用frame motion,它的实现方式应该是怎么样的呢?

首先我们先来进行单个Item的封装:

import * as React from "react";
import { motion } from "framer-motion"; const variants = {
open: {
y: 0,
opacity: 1,
transition: {
y: { stiffness: 1000, velocity: -100 }
}
},
closed: {
y: 50,
opacity: 0,
transition: {
y: { stiffness: 1000 }
}
}
}; const colors = ["#FF008C", "#D309E1", "#9C1AFF", "#7700FF", "#4400FF"]; export const MenuItem = ({ i }) => {
const style = { border: `2px solid ${colors[i]}` };
return (
<motion.li
variants={variants}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
<div className="icon-placeholder" style={style} />
<div className="text-placeholder" style={style} />
</motion.li>
);
};

然后我们在已封装Item的基础上,再进行整个菜单的封装:

import * as React from "react";
import { motion } from "framer-motion"; const itemIds = [0, 1, 2, 3, 4]; const variants = {
open: {
transition: { staggerChildren: 0.07, delayChildren: 0.2 }
},
closed: {
transition: { staggerChildren: 0.05, staggerDirection: -1 }
}
}; export const Navigation = () => (
<motion.ul variants={variants}>
{itemIds.map(i => (
<MenuItem i={i} key={i} />
))}
</motion.ul>
);

没错,动画!就是这么简单!

更多API

更详细、更具体的功能大家可以参考下官方的使用文档,我就不在这里一一列举了。

美中不足

其实不难看出,不论是实现的效果,还是使用方式,对于前端的同学来说framer-motion都是非常友好的工具。这一点从npmWeekly Downloads以及githubstar上面都不难看出。

但是目前也有一个问题,那就是包的体积问题。

这个包的大小对于部分的系统来说,还是不够友好。这也是很多人不选择使用它的原因。

最新文章

  1. Unity3D 之脚本架构,优雅地管理你的代码
  2. Comparable和Comparator的区别
  3. C#多线程学习笔记
  4. mysql innodb 奔溃问题
  5. java1.7集合源码阅读: Stack
  6. BlueStacks 设置代理服务器 Proxifier指定任意程序的代理服务器
  7. HTTP - Cookie 机制
  8. ssh框架简单搭建
  9. Windows Azure Web Role 的 IIS 重置
  10. C++学习之路—继承与派生(三):多重继承与虚基类
  11. 15_Python模块化编程_Python编程之路
  12. Eclipse导入已有的项目后项目报错的解决办法
  13. 图解SSH原理及两种登录方法
  14. Arcmap连接数据库需管理员获取许可——创建ArcSDE连接文件
  15. NOIP 普及组 2014 比例简化
  16. vijos 清点人数
  17. gradle构建工具入门
  18. VB 调用动态链接库
  19. rails 过滤掉所有的html标签 strip_tags
  20. C#比较时分秒大小,终止分钟默认加十分钟,解决跨天、跨月、跨年的情况

热门文章

  1. Redis 04 列表
  2. Jenkins初始化界面一直显示Please wait while Jenkins is getting ready to work ...
  3. Vmware 虚拟机连接外网和设置固定IP
  4. CEOI 2019 Day2 T2 魔法树 Magic Tree (LOJ#3166、CF1993B、and JOI2021 3.20 T3) (启发式合并平衡树,线段树合并)
  5. Android同屏、摄像头RTMP推送常用的数据接口设计探讨
  6. KingbaseES ALTER TABLE 中 USING 子句的用法
  7. C++ 由快排学习到的的随机数等知识
  8. 引擎之旅 Chapter.1 高分辨率时钟
  9. python一招完美搞定Chromedriver的自动更新
  10. Windows Server体验之升级安装