从 GSAP 迁移到 Motion
GSAP 是一个令人难以置信的动画库。但是,您可以使用 Motion 实现大部分相同的效果,并具有硬件加速的性能,而且通常包大小更小。
与 GSAP 不同,Motion 不需要昂贵的年度许可证即可在商业网站上运行,因为它由企业赞助商和可选的Motion+ 会员资格.
在本指南结束时,我们将了解迁移的优点和缺点,以及如何迁移基本动画、时间线序列、滚动链接和滚动触发的动画以及 React 动画。
优点
Motion 构建于现代浏览器 API 之上,例如Web Animations API(WAAPI) 和Scroll Timeline,这使其能够为常见的动画(如 transform
、filter
和 opacity
)提供硬件加速。
还有其他优化,例如使用Intersection Observer API进行滚动触发的动画,而不是每帧测量滚动位置(这可能会触发样式重新计算)。
同样,当您使用 animate
函数启动动画并且需要从 DOM 读取初始样式时,该过程是批量处理和优化的,从而减少了布局抖动和样式重新计算。
Motion 的 API 通常也比 GSAP 小,我们的 scroll
函数只有其 GSAP 等效项的 75% 大小,而迷你 animate
函数仅为 2.3kb,小 90%。即使是全尺寸的 animate
函数(提供时间线序列、独立的 transform 动画等)也只有 18kb,小于 GSAP 动画函数。
最后,由于 Motion 是使用 ES 模块构建的,因此它是可 tree-shakable 的。这意味着如果您只导入 scroll
函数,那么只有这段代码最终会被交付给您的用户。这是 SEO 的直接好处,可以提高几个 Lighthouse 性能评分。
缺点
有关与 GSAP 的强大功能比较,请参阅我们的功能对比指南,但 Motion JavaScript API 中最大的缺失功能是布局动画.
Motion for React 的布局动画远远超出了传统的“FLIP”技术,每次动画都使用 transform、子元素的完整比例校正和 border-radius
等。因此,如果您是 GSAP FLIP 功能的忠实用户,那么 Motion 尚未提供可比较的 API。
GSAP 还直接面向高级用户,其 API 我们认为大多数用户不会使用,例如在动画开始后获取/设置 delay
的能力。Motion 的理念是倾向于更易于访问、更小的 API,这在相对文件大小中得到了体现。
最后,animate
的 onUpdate
回调目前仅适用于动画单个值,但这将在未来发生变化。
迁移
在本指南中,我们将查看GSAP 文档中给出的示例,并了解如何在 Motion 中重写它们。
基本动画
JavaScript 动画的“Hello world”,一个旋转的盒子。在 GSAP 中,这将使用 gsap.to
编写
gsap.to("#animate-anything-css", { duration: 10, ease: "none", repeat: -1, rotation: 360, })
Motion 的基本动画函数是 animate
animate( "#animate-anything-css", { rotate: 360 }, { ease: "linear", duration: 10, repeat: Infinity } )
您可以在这里看到它看起来大致相似,但有一些关键差异。
rotate
而不是rotation
repeat: Infinity
而不是-1
用于无限重复的动画ease: "linear"
而不是ease: "none"
还需要注意的另一件事是,在 GSAP 中,选项和动画值都捆绑在一起。而在 Motion 中,这些是单独的对象。这在实际应用中并不重要,但当动画普通对象时,这意味着该对象不能具有与 GSAP 选项同名的属性。
GSAP 还有另外两种动画方法,fromTo
和 from
。
fromTo
允许您指定开始和结束关键帧
gsap.fromTo(".box", { opacity: 0 }, { opacity: 0.5, duration: 1 })
使用 Motion,您只需使用关键帧语法
animate(".box", { opacity: [0, 0.5] }, { duration: 1 })
这种类型的语法(或等效语法也存在于 GSAP 中,但 fromTo
更像是一个遗留 API。
from
允许您定义要从 from 动画的值,目标值从 DOM 中读取。
gsap.from(".box", { opacity: 0 })
Motion 没有可比的 API,但这部分是因为我们不推荐它。实际上,这里必须发生的是 GSAP 从 DOM 读取现有值,将其设置为目标值,然后从给定值进行动画处理。除非用户编写他们的 JavaScript 以进行渲染阻塞(不鼓励),否则这种“不正确”的样式将可见一帧或更长时间,这很少是我们想要的。
动画控制
GSAP 和 Motion 动画都返回动画控件。GSAP 在这里提供了更多功能。例如,每个动画选项都有一个方法来获取/设置该选项,而 Motion 则倾向于选项的不可变性。
const animation = gsap.to() animation.delay(0.5) // No Motion equivalent
但是,有一些 Motion 等效项需要了解。
.timeScale()
是.speed
.time()
是.time
.kill()
是.stop()
.revert()
是.cancel()
.progress(1)
是.complete()
.resume()
是.play()
时间线序列
Motion 和 GSAP 都提供时间线序列。根本的区别在于 GSAP 具有更命令式的 API,具有 .timeline()
构造函数和 .to
、.add()
和 .addLabel()
方法,用于组合/修改时间线
const timeline = gsap.timeline(options) timeline.to("#id", { x: 100, duration: 1 }) timeline.addLabel("My label") timeline.to("#id", { y: 50, duration: 1 })
而 Motion 使用声明式数组语法
const timeline = [ ["#id", { x: 100, duration: 1 }], "My label", ["#id", { y: 100, duration: 1 }] ] animate(timeline, options)
GSAP 方法的优点是更容易动态更改正在进行的时间线。而使用 Motion,组合长动画的样板代码会少一些。
在每个库中,组合多个时间线的方式都不同,就像上面一样
// GSAP timeline.add(timelineA) timeline.add(timelineB) // Motion const timeline = [...timelineA, ...timelineB]
滚动触发的动画
滚动触发的动画是正常的基于时间的动画,当元素进入视口时触发。
GSAP 具有 ScrollTrigger
插件,而 Motion 使用 inView
函数。
// GSAP gsap.to('.box', { scrollTrigger: '.box', x: 500 }) // Motion inView(".box", ({ target }) => { animate(target, { x: 500 }) })
两者之间根本的技术差异在于 inView
基于浏览器的 Intersection Observer API,这是一种检测元素何时进入视口的超高性能方法。而 ScrollTrigger
测量元素,然后每帧跟踪其相对于滚动条的位置。这些读取/写入操作会导致样式重新计算。
此外,由于 inView
仅在跟踪的元素进入视口时触发,这意味着滚动触发的动画是延迟初始化的。结合 Motion 的延迟关键帧解析,这可以显著缩短使用许多滚动触发动画时的启动时间。
滚动固定
GSAP 有一个名为 pin
的选项。如果设置,这将在滚动动画期间将元素 pin
固定到视口。出于性能原因,我们建议改用 CSS position: sticky
。
滚动链接的动画
通过将 scrub: true
传递给 scrollTrigger
,GSAP 可以创建滚动链接的动画。这些动画在根本上是不同的,因为动画不是由时间驱动,而是由滚动进度驱动。
gsap.to('.box', { scrollTrigger: { trigger: '.box', scrub: true } x: 500 });
const animation = animate(element, { x: 500 }) scroll(animation, { target: element })
驱动。scroll
的不同之处在于,就像 animate
可以使用 Web Animations API 进行硬件加速性能一样,scroll
可以使用 Scroll Timeline API 获得两个性能优势
启用硬件加速的滚动动画
可以在不轮询滚动位置的情况下测量回调的滚动进度(消除样式重新计算)
与 start
和 end
偏移选项不同,scroll
接受单个 offset
数组,其选项与 GSAP 中的选项非常相似。
scroll(callback, { target: element, offset: ["start start", "end start"] // Exits the viewport top })
您可以在这里看到,Motion 使用轴无关的 "start"
和 "end"
关键字,而不是使用 "top"
/"bottom"
或 "left"
/"right"
。
单个 offset
选项的好处是我们可以将两个以上的偏移量映射到两个以上的动画关键帧。这是一个元素淡入和淡出视口的动画
const animation = animate(element, { opacity: [0, 1, 1, 0] }) scroll(animation, { target: element, offset: [ // When the target starts entering the bottom of the viewport, opacity = 0 "start end", // When the target is fully in the bottom of the viewport, opacity = 1 "end end", // When the target starts exiting the top of the viewport, opacity = 1 "start start", // When the target is fully off the top of the viewport, opacity = 0 "end start" ] })
SplitText
Motion 为 Club GSAP API SplitText
提供了一个等效项,用于Motion+名为 splitText
的成员。
它的工作方式与 SplitText
非常相似,无需注册插件
animate( splitText("h1").chars, { opacity: [0, 1] } )
与 GSAP 的 SplitText
不同,Motion 的 splitText
正确地将 aria-label
属性应用于具有原始文本的原始元素,以确保屏幕阅读器可以正确读取它。
与 GSAP 相比,splitText
的主要缺点是它尚不支持嵌套标签,例如 a
标签。GSAP 的 SplitText
大约为 4kb,而 Motion 的 splitText
为 0.7kb,因此处理这种情况可能会增加显著的开销。可以通过在其自己的 span
中包装标签之前/之后的文本并分别拆分这些文本来解决此问题
<h2> <span class="before">Before</span> <a href="#">Link</a> <span class="after">After</span> </h2> <script> const chars = [ ...splitText(".before").chars, ...splitText("a").chars, ...splitText(".after").chars, ] </script>
React
Motion 最初是一个 React 动画库:Framer Motion。因此,它的React API套件远远超出了 GSAP 的 useGSAP
函数。
也就是说,您可以使用 Motion 的 useAnimate
hook.
以更小的包大小实现类似的模式。以 GSAP 文档中的这个旋转立方体示例为例
const RotatingCube = () => { const boxRef = useRef() useGSAP(() => { gsap.to(boxRef.current, { duration: 10, repeat: -1, rotation: 360, }) }) return <div ref={boxRef} /> }
我们可以使用 Motion 的迷你 useAnimate
重写它,它为 2.3kb animate
函数提供了一个 React 接口。
import { useAnimate } from "motion/react-mini" const RotatingCube = () => { const [scope, animate] = useAnimate() useEffect(() => { const animation = animate( scope.current, { transform: "rotate(360deg)" }, { duration: 10, repeat: Infinity } ) return () => animation.stop() }, []) return <div ref={scope} /> }
现在,我们以减少 90% 代码包含在包大小中的情况下运行相同的效果,并且动画正在硬件加速下运行,这意味着更少的卡顿(尤其是在 React 重新渲染期间)。
如果您想使用 { rotate: 360 }
就像在 GSAP 示例中一样,那么也可以通过使用混合 animate
函数来实现
import { useAnimate } from "motion/react"
结论
尽管 Motion 和 GSAP 的功能集并未完全重叠,但由于现代实践和新的浏览器 API,我们认为大多数用户通过迁移到 Motion 将获得更好的性能和更小的文件大小。
您是否希望在本指南中看到更多 GSAP 功能?或者您希望在 Motion 中看到 GSAP 的哪些功能?请告诉我!
GSAP 是一个令人难以置信的动画库。但是,您可以使用 Motion 实现大部分相同的效果,并具有硬件加速的性能,而且通常包大小更小。
与 GSAP 不同,Motion 不需要昂贵的年度许可证即可在商业网站上运行,因为它由企业赞助商和可选的Motion+ 会员资格.
在本指南结束时,我们将了解迁移的优点和缺点,以及如何迁移基本动画、时间线序列、滚动链接和滚动触发的动画以及 React 动画。
优点
Motion 构建于现代浏览器 API 之上,例如Web Animations API(WAAPI) 和Scroll Timeline,这使其能够为常见的动画(如 transform
、filter
和 opacity
)提供硬件加速。
还有其他优化,例如使用Intersection Observer API进行滚动触发的动画,而不是每帧测量滚动位置(这可能会触发样式重新计算)。
同样,当您使用 animate
函数启动动画并且需要从 DOM 读取初始样式时,该过程是批量处理和优化的,从而减少了布局抖动和样式重新计算。
Motion 的 API 通常也比 GSAP 小,我们的 scroll
函数只有其 GSAP 等效项的 75% 大小,而迷你 animate
函数仅为 2.3kb,小 90%。即使是全尺寸的 animate
函数(提供时间线序列、独立的 transform 动画等)也只有 18kb,小于 GSAP 动画函数。
最后,由于 Motion 是使用 ES 模块构建的,因此它是可 tree-shakable 的。这意味着如果您只导入 scroll
函数,那么只有这段代码最终会被交付给您的用户。这是 SEO 的直接好处,可以提高几个 Lighthouse 性能评分。
缺点
有关与 GSAP 的强大功能比较,请参阅我们的功能对比指南,但 Motion JavaScript API 中最大的缺失功能是布局动画.
Motion for React 的布局动画远远超出了传统的“FLIP”技术,每次动画都使用 transform、子元素的完整比例校正和 border-radius
等。因此,如果您是 GSAP FLIP 功能的忠实用户,那么 Motion 尚未提供可比较的 API。
GSAP 还直接面向高级用户,其 API 我们认为大多数用户不会使用,例如在动画开始后获取/设置 delay
的能力。Motion 的理念是倾向于更易于访问、更小的 API,这在相对文件大小中得到了体现。
最后,animate
的 onUpdate
回调目前仅适用于动画单个值,但这将在未来发生变化。
迁移
在本指南中,我们将查看GSAP 文档中给出的示例,并了解如何在 Motion 中重写它们。
基本动画
JavaScript 动画的“Hello world”,一个旋转的盒子。在 GSAP 中,这将使用 gsap.to
编写
gsap.to("#animate-anything-css", { duration: 10, ease: "none", repeat: -1, rotation: 360, })
Motion 的基本动画函数是 animate
animate( "#animate-anything-css", { rotate: 360 }, { ease: "linear", duration: 10, repeat: Infinity } )
您可以在这里看到它看起来大致相似,但有一些关键差异。
rotate
而不是rotation
repeat: Infinity
而不是-1
用于无限重复的动画ease: "linear"
而不是ease: "none"
还需要注意的另一件事是,在 GSAP 中,选项和动画值都捆绑在一起。而在 Motion 中,这些是单独的对象。这在实际应用中并不重要,但当动画普通对象时,这意味着该对象不能具有与 GSAP 选项同名的属性。
GSAP 还有另外两种动画方法,fromTo
和 from
。
fromTo
允许您指定开始和结束关键帧
gsap.fromTo(".box", { opacity: 0 }, { opacity: 0.5, duration: 1 })
使用 Motion,您只需使用关键帧语法
animate(".box", { opacity: [0, 0.5] }, { duration: 1 })
这种类型的语法(或等效语法也存在于 GSAP 中,但 fromTo
更像是一个遗留 API。
from
允许您定义要从 from 动画的值,目标值从 DOM 中读取。
gsap.from(".box", { opacity: 0 })
Motion 没有可比的 API,但这部分是因为我们不推荐它。实际上,这里必须发生的是 GSAP 从 DOM 读取现有值,将其设置为目标值,然后从给定值进行动画处理。除非用户编写他们的 JavaScript 以进行渲染阻塞(不鼓励),否则这种“不正确”的样式将可见一帧或更长时间,这很少是我们想要的。
动画控制
GSAP 和 Motion 动画都返回动画控件。GSAP 在这里提供了更多功能。例如,每个动画选项都有一个方法来获取/设置该选项,而 Motion 则倾向于选项的不可变性。
const animation = gsap.to() animation.delay(0.5) // No Motion equivalent
但是,有一些 Motion 等效项需要了解。
.timeScale()
是.speed
.time()
是.time
.kill()
是.stop()
.revert()
是.cancel()
.progress(1)
是.complete()
.resume()
是.play()
时间线序列
Motion 和 GSAP 都提供时间线序列。根本的区别在于 GSAP 具有更命令式的 API,具有 .timeline()
构造函数和 .to
、.add()
和 .addLabel()
方法,用于组合/修改时间线
const timeline = gsap.timeline(options) timeline.to("#id", { x: 100, duration: 1 }) timeline.addLabel("My label") timeline.to("#id", { y: 50, duration: 1 })
而 Motion 使用声明式数组语法
const timeline = [ ["#id", { x: 100, duration: 1 }], "My label", ["#id", { y: 100, duration: 1 }] ] animate(timeline, options)
GSAP 方法的优点是更容易动态更改正在进行的时间线。而使用 Motion,组合长动画的样板代码会少一些。
在每个库中,组合多个时间线的方式都不同,就像上面一样
// GSAP timeline.add(timelineA) timeline.add(timelineB) // Motion const timeline = [...timelineA, ...timelineB]
滚动触发的动画
滚动触发的动画是正常的基于时间的动画,当元素进入视口时触发。
GSAP 具有 ScrollTrigger
插件,而 Motion 使用 inView
函数。
// GSAP gsap.to('.box', { scrollTrigger: '.box', x: 500 }) // Motion inView(".box", ({ target }) => { animate(target, { x: 500 }) })
两者之间根本的技术差异在于 inView
基于浏览器的 Intersection Observer API,这是一种检测元素何时进入视口的超高性能方法。而 ScrollTrigger
测量元素,然后每帧跟踪其相对于滚动条的位置。这些读取/写入操作会导致样式重新计算。
此外,由于 inView
仅在跟踪的元素进入视口时触发,这意味着滚动触发的动画是延迟初始化的。结合 Motion 的延迟关键帧解析,这可以显著缩短使用许多滚动触发动画时的启动时间。
滚动固定
GSAP 有一个名为 pin
的选项。如果设置,这将在滚动动画期间将元素 pin
固定到视口。出于性能原因,我们建议改用 CSS position: sticky
。
滚动链接的动画
通过将 scrub: true
传递给 scrollTrigger
,GSAP 可以创建滚动链接的动画。这些动画在根本上是不同的,因为动画不是由时间驱动,而是由滚动进度驱动。
gsap.to('.box', { scrollTrigger: { trigger: '.box', scrub: true } x: 500 });
const animation = animate(element, { x: 500 }) scroll(animation, { target: element })
驱动。scroll
的不同之处在于,就像 animate
可以使用 Web Animations API 进行硬件加速性能一样,scroll
可以使用 Scroll Timeline API 获得两个性能优势
启用硬件加速的滚动动画
可以在不轮询滚动位置的情况下测量回调的滚动进度(消除样式重新计算)
与 start
和 end
偏移选项不同,scroll
接受单个 offset
数组,其选项与 GSAP 中的选项非常相似。
scroll(callback, { target: element, offset: ["start start", "end start"] // Exits the viewport top })
您可以在这里看到,Motion 使用轴无关的 "start"
和 "end"
关键字,而不是使用 "top"
/"bottom"
或 "left"
/"right"
。
单个 offset
选项的好处是我们可以将两个以上的偏移量映射到两个以上的动画关键帧。这是一个元素淡入和淡出视口的动画
const animation = animate(element, { opacity: [0, 1, 1, 0] }) scroll(animation, { target: element, offset: [ // When the target starts entering the bottom of the viewport, opacity = 0 "start end", // When the target is fully in the bottom of the viewport, opacity = 1 "end end", // When the target starts exiting the top of the viewport, opacity = 1 "start start", // When the target is fully off the top of the viewport, opacity = 0 "end start" ] })
SplitText
Motion 为 Club GSAP API SplitText
提供了一个等效项,用于Motion+名为 splitText
的成员。
它的工作方式与 SplitText
非常相似,无需注册插件
animate( splitText("h1").chars, { opacity: [0, 1] } )
与 GSAP 的 SplitText
不同,Motion 的 splitText
正确地将 aria-label
属性应用于具有原始文本的原始元素,以确保屏幕阅读器可以正确读取它。
与 GSAP 相比,splitText
的主要缺点是它尚不支持嵌套标签,例如 a
标签。GSAP 的 SplitText
大约为 4kb,而 Motion 的 splitText
为 0.7kb,因此处理这种情况可能会增加显著的开销。可以通过在其自己的 span
中包装标签之前/之后的文本并分别拆分这些文本来解决此问题
<h2> <span class="before">Before</span> <a href="#">Link</a> <span class="after">After</span> </h2> <script> const chars = [ ...splitText(".before").chars, ...splitText("a").chars, ...splitText(".after").chars, ] </script>
React
Motion 最初是一个 React 动画库:Framer Motion。因此,它的React API套件远远超出了 GSAP 的 useGSAP
函数。
也就是说,您可以使用 Motion 的 useAnimate
hook.
以更小的包大小实现类似的模式。以 GSAP 文档中的这个旋转立方体示例为例
const RotatingCube = () => { const boxRef = useRef() useGSAP(() => { gsap.to(boxRef.current, { duration: 10, repeat: -1, rotation: 360, }) }) return <div ref={boxRef} /> }
我们可以使用 Motion 的迷你 useAnimate
重写它,它为 2.3kb animate
函数提供了一个 React 接口。
import { useAnimate } from "motion/react-mini" const RotatingCube = () => { const [scope, animate] = useAnimate() useEffect(() => { const animation = animate( scope.current, { transform: "rotate(360deg)" }, { duration: 10, repeat: Infinity } ) return () => animation.stop() }, []) return <div ref={scope} /> }
现在,我们以减少 90% 代码包含在包大小中的情况下运行相同的效果,并且动画正在硬件加速下运行,这意味着更少的卡顿(尤其是在 React 重新渲染期间)。
如果您想使用 { rotate: 360 }
就像在 GSAP 示例中一样,那么也可以通过使用混合 animate
函数来实现
import { useAnimate } from "motion/react"
结论
尽管 Motion 和 GSAP 的功能集并未完全重叠,但由于现代实践和新的浏览器 API,我们认为大多数用户通过迁移到 Motion 将获得更好的性能和更小的文件大小。
您是否希望在本指南中看到更多 GSAP 功能?或者您希望在 Motion 中看到 GSAP 的哪些功能?请告诉我!
GSAP 是一个令人难以置信的动画库。但是,您可以使用 Motion 实现大部分相同的效果,并具有硬件加速的性能,而且通常包大小更小。
与 GSAP 不同,Motion 不需要昂贵的年度许可证即可在商业网站上运行,因为它由企业赞助商和可选的Motion+ 会员资格.
在本指南结束时,我们将了解迁移的优点和缺点,以及如何迁移基本动画、时间线序列、滚动链接和滚动触发的动画以及 React 动画。
优点
Motion 构建于现代浏览器 API 之上,例如Web Animations API(WAAPI) 和Scroll Timeline,这使其能够为常见的动画(如 transform
、filter
和 opacity
)提供硬件加速。
还有其他优化,例如使用Intersection Observer API进行滚动触发的动画,而不是每帧测量滚动位置(这可能会触发样式重新计算)。
同样,当您使用 animate
函数启动动画并且需要从 DOM 读取初始样式时,该过程是批量处理和优化的,从而减少了布局抖动和样式重新计算。
Motion 的 API 通常也比 GSAP 小,我们的 scroll
函数只有其 GSAP 等效项的 75% 大小,而迷你 animate
函数仅为 2.3kb,小 90%。即使是全尺寸的 animate
函数(提供时间线序列、独立的 transform 动画等)也只有 18kb,小于 GSAP 动画函数。
最后,由于 Motion 是使用 ES 模块构建的,因此它是可 tree-shakable 的。这意味着如果您只导入 scroll
函数,那么只有这段代码最终会被交付给您的用户。这是 SEO 的直接好处,可以提高几个 Lighthouse 性能评分。
缺点
有关与 GSAP 的强大功能比较,请参阅我们的功能对比指南,但 Motion JavaScript API 中最大的缺失功能是布局动画.
Motion for React 的布局动画远远超出了传统的“FLIP”技术,每次动画都使用 transform、子元素的完整比例校正和 border-radius
等。因此,如果您是 GSAP FLIP 功能的忠实用户,那么 Motion 尚未提供可比较的 API。
GSAP 还直接面向高级用户,其 API 我们认为大多数用户不会使用,例如在动画开始后获取/设置 delay
的能力。Motion 的理念是倾向于更易于访问、更小的 API,这在相对文件大小中得到了体现。
最后,animate
的 onUpdate
回调目前仅适用于动画单个值,但这将在未来发生变化。
迁移
在本指南中,我们将查看GSAP 文档中给出的示例,并了解如何在 Motion 中重写它们。
基本动画
JavaScript 动画的“Hello world”,一个旋转的盒子。在 GSAP 中,这将使用 gsap.to
编写
gsap.to("#animate-anything-css", { duration: 10, ease: "none", repeat: -1, rotation: 360, })
Motion 的基本动画函数是 animate
animate( "#animate-anything-css", { rotate: 360 }, { ease: "linear", duration: 10, repeat: Infinity } )
您可以在这里看到它看起来大致相似,但有一些关键差异。
rotate
而不是rotation
repeat: Infinity
而不是-1
用于无限重复的动画ease: "linear"
而不是ease: "none"
还需要注意的另一件事是,在 GSAP 中,选项和动画值都捆绑在一起。而在 Motion 中,这些是单独的对象。这在实际应用中并不重要,但当动画普通对象时,这意味着该对象不能具有与 GSAP 选项同名的属性。
GSAP 还有另外两种动画方法,fromTo
和 from
。
fromTo
允许您指定开始和结束关键帧
gsap.fromTo(".box", { opacity: 0 }, { opacity: 0.5, duration: 1 })
使用 Motion,您只需使用关键帧语法
animate(".box", { opacity: [0, 0.5] }, { duration: 1 })
这种类型的语法(或等效语法也存在于 GSAP 中,但 fromTo
更像是一个遗留 API。
from
允许您定义要从 from 动画的值,目标值从 DOM 中读取。
gsap.from(".box", { opacity: 0 })
Motion 没有可比的 API,但这部分是因为我们不推荐它。实际上,这里必须发生的是 GSAP 从 DOM 读取现有值,将其设置为目标值,然后从给定值进行动画处理。除非用户编写他们的 JavaScript 以进行渲染阻塞(不鼓励),否则这种“不正确”的样式将可见一帧或更长时间,这很少是我们想要的。
动画控制
GSAP 和 Motion 动画都返回动画控件。GSAP 在这里提供了更多功能。例如,每个动画选项都有一个方法来获取/设置该选项,而 Motion 则倾向于选项的不可变性。
const animation = gsap.to() animation.delay(0.5) // No Motion equivalent
但是,有一些 Motion 等效项需要了解。
.timeScale()
是.speed
.time()
是.time
.kill()
是.stop()
.revert()
是.cancel()
.progress(1)
是.complete()
.resume()
是.play()
时间线序列
Motion 和 GSAP 都提供时间线序列。根本的区别在于 GSAP 具有更命令式的 API,具有 .timeline()
构造函数和 .to
、.add()
和 .addLabel()
方法,用于组合/修改时间线
const timeline = gsap.timeline(options) timeline.to("#id", { x: 100, duration: 1 }) timeline.addLabel("My label") timeline.to("#id", { y: 50, duration: 1 })
而 Motion 使用声明式数组语法
const timeline = [ ["#id", { x: 100, duration: 1 }], "My label", ["#id", { y: 100, duration: 1 }] ] animate(timeline, options)
GSAP 方法的优点是更容易动态更改正在进行的时间线。而使用 Motion,组合长动画的样板代码会少一些。
在每个库中,组合多个时间线的方式都不同,就像上面一样
// GSAP timeline.add(timelineA) timeline.add(timelineB) // Motion const timeline = [...timelineA, ...timelineB]
滚动触发的动画
滚动触发的动画是正常的基于时间的动画,当元素进入视口时触发。
GSAP 具有 ScrollTrigger
插件,而 Motion 使用 inView
函数。
// GSAP gsap.to('.box', { scrollTrigger: '.box', x: 500 }) // Motion inView(".box", ({ target }) => { animate(target, { x: 500 }) })
两者之间根本的技术差异在于 inView
基于浏览器的 Intersection Observer API,这是一种检测元素何时进入视口的超高性能方法。而 ScrollTrigger
测量元素,然后每帧跟踪其相对于滚动条的位置。这些读取/写入操作会导致样式重新计算。
此外,由于 inView
仅在跟踪的元素进入视口时触发,这意味着滚动触发的动画是延迟初始化的。结合 Motion 的延迟关键帧解析,这可以显著缩短使用许多滚动触发动画时的启动时间。
滚动固定
GSAP 有一个名为 pin
的选项。如果设置,这将在滚动动画期间将元素 pin
固定到视口。出于性能原因,我们建议改用 CSS position: sticky
。
滚动链接的动画
通过将 scrub: true
传递给 scrollTrigger
,GSAP 可以创建滚动链接的动画。这些动画在根本上是不同的,因为动画不是由时间驱动,而是由滚动进度驱动。
gsap.to('.box', { scrollTrigger: { trigger: '.box', scrub: true } x: 500 });
const animation = animate(element, { x: 500 }) scroll(animation, { target: element })
驱动。scroll
的不同之处在于,就像 animate
可以使用 Web Animations API 进行硬件加速性能一样,scroll
可以使用 Scroll Timeline API 获得两个性能优势
启用硬件加速的滚动动画
可以在不轮询滚动位置的情况下测量回调的滚动进度(消除样式重新计算)
与 start
和 end
偏移选项不同,scroll
接受单个 offset
数组,其选项与 GSAP 中的选项非常相似。
scroll(callback, { target: element, offset: ["start start", "end start"] // Exits the viewport top })
您可以在这里看到,Motion 使用轴无关的 "start"
和 "end"
关键字,而不是使用 "top"
/"bottom"
或 "left"
/"right"
。
单个 offset
选项的好处是我们可以将两个以上的偏移量映射到两个以上的动画关键帧。这是一个元素淡入和淡出视口的动画
const animation = animate(element, { opacity: [0, 1, 1, 0] }) scroll(animation, { target: element, offset: [ // When the target starts entering the bottom of the viewport, opacity = 0 "start end", // When the target is fully in the bottom of the viewport, opacity = 1 "end end", // When the target starts exiting the top of the viewport, opacity = 1 "start start", // When the target is fully off the top of the viewport, opacity = 0 "end start" ] })
SplitText
Motion 为 Club GSAP API SplitText
提供了一个等效项,用于Motion+名为 splitText
的成员。
它的工作方式与 SplitText
非常相似,无需注册插件
animate( splitText("h1").chars, { opacity: [0, 1] } )
与 GSAP 的 SplitText
不同,Motion 的 splitText
正确地将 aria-label
属性应用于具有原始文本的原始元素,以确保屏幕阅读器可以正确读取它。
与 GSAP 相比,splitText
的主要缺点是它尚不支持嵌套标签,例如 a
标签。GSAP 的 SplitText
大约为 4kb,而 Motion 的 splitText
为 0.7kb,因此处理这种情况可能会增加显著的开销。可以通过在其自己的 span
中包装标签之前/之后的文本并分别拆分这些文本来解决此问题
<h2> <span class="before">Before</span> <a href="#">Link</a> <span class="after">After</span> </h2> <script> const chars = [ ...splitText(".before").chars, ...splitText("a").chars, ...splitText(".after").chars, ] </script>
React
Motion 最初是一个 React 动画库:Framer Motion。因此,它的React API套件远远超出了 GSAP 的 useGSAP
函数。
也就是说,您可以使用 Motion 的 useAnimate
hook.
以更小的包大小实现类似的模式。以 GSAP 文档中的这个旋转立方体示例为例
const RotatingCube = () => { const boxRef = useRef() useGSAP(() => { gsap.to(boxRef.current, { duration: 10, repeat: -1, rotation: 360, }) }) return <div ref={boxRef} /> }
我们可以使用 Motion 的迷你 useAnimate
重写它,它为 2.3kb animate
函数提供了一个 React 接口。
import { useAnimate } from "motion/react-mini" const RotatingCube = () => { const [scope, animate] = useAnimate() useEffect(() => { const animation = animate( scope.current, { transform: "rotate(360deg)" }, { duration: 10, repeat: Infinity } ) return () => animation.stop() }, []) return <div ref={scope} /> }
现在,我们以减少 90% 代码包含在包大小中的情况下运行相同的效果,并且动画正在硬件加速下运行,这意味着更少的卡顿(尤其是在 React 重新渲染期间)。
如果您想使用 { rotate: 360 }
就像在 GSAP 示例中一样,那么也可以通过使用混合 animate
函数来实现
import { useAnimate } from "motion/react"
结论
尽管 Motion 和 GSAP 的功能集并未完全重叠,但由于现代实践和新的浏览器 API,我们认为大多数用户通过迁移到 Motion 将获得更好的性能和更小的文件大小。
您是否希望在本指南中看到更多 GSAP 功能?或者您希望在 Motion 中看到 GSAP 的哪些功能?请告诉我!