前言
在平时的开发过程中,前端或多或少都会遇到实现动画效果的场景。手写动画是一件相当麻烦的事情,调来调去不仅费时费力,可能还会被产品/UI吐槽:这动画效果也不难呀,为什么就不能实现呢?/为什么就没有还原成我想要的样子呢。
比如说产品让我们实现这样的一个开关动效
今天我们就用动画的实现方式——Lottie
,来百分百还原设计师的动画效果,并且可以大大提高我们的工作效率(摸鱼时间)。
Lottie
简介
首先我们先来看一下,平时我们实现动画都有哪些方式,它们分别有什么优缺点:
动画类型 | 优点 | 缺点 |
CSS 动画 | 使用简便,通过@keyframes 和transition 创建动画;浏览器原生支持,性能较好 |
控制有限,不适用于复杂动画;复杂动画可能需要大量 CSS 代码,冗长 |
JavaScript 动画 | 提供更高程度的控制和灵活性;适用于复杂和精细动画效果 | 引入库增加页面负担,可能需要学习曲线;使用不当容器对页面性能造成影响,产生卡顿 |
GIF 动画 | 制作和使用简单,无需额外代码;几乎所有浏览器原生支持 | 有限颜色深度,不适用于所有场景;清晰度与文件尺寸成正比,无法适应所有分辨率 |
Lottie | 支持矢量动画,保持清晰度和流畅性 ;跨平台使用,适用于 iOS、Android 和 Web | 在一些较旧或性能较低的设备上,播放较大的 Lottie 动画可能会导致性能问题;对设计师要求较高 |
Lottie
是由Airbnb
开发的一个开源库,用于在移动端和Web
上呈现矢量动画。它基于JSON
格式的Bodymovin
文件,可以将由设计师在AE中创建的动画导出为可在Lottie库中播放的文件。
相对于手写CSS/JS
动画而言,它可以大大减少前端开发的工作量,相对于GIF
文件来说,它可以在一个合理的文件体积内保证动画的清晰度以及流畅程度。下面我们就介绍一下如何播放一个Lottie
动画,并实现一个炫酷的开关效果。
Hello Lottie
假设我们现在已经有一个Lottie
的json
文件,那么现在安装一些依赖
npm i react-lottie prop-types
安装完之后我们就可以这样子来播放一个Lottie
动画:
import animationData from "../../assets/switch-lottie.json"; const LottieSwitch = () => { const playing = useRef(false); const options = { loop: true, autoplay: true, animationData: animationData, rendererSettings: { preserveAspectRatio: "xMidYMid slice", }, }; return ( <Lottie options={options} height={20} width={40} /> ); };
来解释一下上面的options
参数里面各个字段是什么意思:
loop
:是否循环播放autoplay
:是否自动播放animationData
:Lottie
动画json
资源rendererSettings.preserveAspectRatio
:指定如何在给定容器中渲染Lottie
动画
xMidYMid
: 表示在水平和垂直方向上都在中心对齐- 表示保持纵横比,但可能会裁剪超出容器的部分
正/反向播放
正常的把Lottie
动画播放出来之后,我们就可以开始实现一个开关的功能。其实就是点击的时候更换Lottie
的播放方向,这里对应的是direction
字段,direction
为1
时正向播放,direction
为-1
时反向播放。
我们就要实现下面的功能:
- 点击时切换方向
- 播放过程中加锁,禁止切换方向
- 监听播放结束事件,解锁
loop
改为false
;autoplay
改为false
实现代码如下:
const LottieSwitch = () => { const [direction, setDirection] = useState(null); const playing = useRef(false); const options = { loop: false, autoplay: false, animationData: animationData, rendererSettings: { preserveAspectRatio: "xMidYMid slice", }, }; const handleClick = () => { if (playing.current) { return; } playing.current = true; setDirection((prevState) => (prevState === 1 ? -1 : 1)); }; return ( <div style={{ padding: 40 }}> <div onClick={handleClick} className={styles.lottieWrapper}> <Lottie direction={direction} options={options} speed={2} height={20} width={40} eventListeners={[ { eventName: "complete", callback: () => { playing.current = false; }, }, ]} /> </div> </div> ); };
这样我们就是实现了一个开关的效果 链接
持续时长
在Lottie
的json
中,有几个关键的字段跟动画的播放时长有关系:
fr
:帧率,每一秒的帧数ip
:开始帧op
:结束帧
假如说有下面的一个描述:
{ "fr": 30, "ip": 0, "op": 60, }
则表示帧率是30
帧,从第0
帧开始,60
帧结束,那这个动画的持续时长是 (op-ip)/fr
,为2s
。那如果我们希望整个动画的播放时长是500ms
,则只需要把Lottie
的倍速调整为4
。对应的就是speed
字段:
<Lottie direction={direction} options={options} speed={4} height={20} width={40} eventListeners={[ { eventName: "complete", callback: () => { playing.current = false; }, }, ]} />
修改Lottie
在Lottie json
中,描述整个动画的过程以及效果其实对应的就是某个值。在实现的过程中,其实开发是可以去修改这些值的。比如说我们可以修改上面开关的边框颜色以及小球的颜色。
首先在页面中找到小球对应的颜色是rgb(99, 102, 241)
在Lottie JSON
文件中,颜色信息通常出现在表示图层样式的字段中。常见的字段是 "c"(color)
。 "c"
字段表示颜色,通常以RGBA
格式(红绿蓝透明度)存储。例如:
"c": {"a":0,"k":[1,0,0,1]}
这表示红色,RGBA
值为 [1, 0, 0, 1]
。
rgb(99, 102, 241)
转成上面的写法那就是"c": {"a":0,"k":[99/255,102/255,241/255,1]}
。以99/255
为例,结果是0.38823529411764707
,那么就拿这个结果去json
文件中找到对应的节点。
作对应有2
个结果,就是小球的颜色以及边框的颜色。当我们找到这个值的时候,如果我们想修改这个值,就必须知道这个值的路径,在一个Lottie
中,想肉眼找到这个值的路径是一件很难的事情。所以我们写一个辅助函数:
const updateJsonValue = (json, targetValue, newValue) => { const find = (json, targetValue, currentPath = []) => { for (const key in json) { if (json[key] === targetValue) { return [...currentPath, key]; } else if (typeof json[key] === "object" && json[key] !== null) { const path = find(json[key], targetValue, [...currentPath, key]); if (path) { return path; } } } }; const res = JSON.parse(JSON.stringify(json)); const path = find(res, targetValue); let current = res; for (let i = 0; i < path.length - 1; i++) { const key = path[i]; current = current[key]; } const lastKey = path[path.length - 1]; current[lastKey] = newValue; return json; };
上面的辅助函数就帮助我们找到这个值的路径,并修改目标值。比如说我们想把目前的颜色改成绿色(rgb(25, 195, 125)
),就可以找到对应的路径,并修改。别忘了替换的时候把rgb
对应的值除以255
。
let newAnimationData = updateJsonValue(animationData, 0.388235300779, 0.09803921568627451) newAnimationData = updateJsonValue(newAnimationData, 0.388235300779, 0.09803921568627451) newAnimationData = updateJsonValue(newAnimationData, 0.40000000596, 0.7647058823529411) newAnimationData = updateJsonValue(newAnimationData, 0.40000000596, 0.7647058823529411) newAnimationData = updateJsonValue(newAnimationData, 0.945098042488, 0.49019607843137253) newAnimationData = updateJsonValue(newAnimationData, 0.945098042488, 0.49019607843137253)
掌握了这种方式之后,我们就能修改Lottie
里面的大部分内容,包括文案、资源图片、颜色等等。
最后
以上就是一些Lottie
的使用以及修改的介绍,下次再遇到比较麻烦的动画需求。就可以跟产品说:可以做,让UI
给我导出一个Lottie
。
如果你有一些别的想法,欢迎评论区交流~如果你觉得有意思的话,点点关注点点赞吧~