文档

文档

JavaScript

GSAP 迁移

从 GSAP 迁移到 Motion

GSAP 是一个令人难以置信的动画库。但是,您可以使用 Motion 实现大部分相同的效果,并具有硬件加速的性能,而且通常包大小更小。

与 GSAP 不同,Motion 不需要昂贵的年度许可证即可在商业网站上运行,因为它由企业赞助商和可选的Motion+ 会员资格.

在本指南结束时,我们将了解迁移的优点和缺点,以及如何迁移基本动画、时间线序列、滚动链接和滚动触发的动画以及 React 动画。

优点

Motion 构建于现代浏览器 API 之上,例如Web Animations API(WAAPI) 和Scroll Timeline,这使其能够为常见的动画(如 transformfilteropacity)提供硬件加速。

还有其他优化,例如使用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,这在相对文件大小中得到了体现。

最后,animateonUpdate 回调目前仅适用于动画单个值,但这将在未来发生变化。

迁移

在本指南中,我们将查看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 }
)

您可以在这里看到它看起来大致相似,但有一些关键差异。

  1. rotate 而不是 rotation

  2. repeat: Infinity 而不是 -1 用于无限重复的动画

  3. ease: "linear" 而不是 ease: "none"

还需要注意的另一件事是,在 GSAP 中,选项和动画值都捆绑在一起。而在 Motion 中,这些是单独的对象。这在实际应用中并不重要,但当动画普通对象时,这意味着该对象不能具有与 GSAP 选项同名的属性。

GSAP 还有另外两种动画方法,fromTofrom

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

在 Motion 中,这些类型的动画由 scroll函数.

const animation = animate(element, { x: 500 })
scroll(animation, { target: element })

驱动。scroll 的不同之处在于,就像 animate 可以使用 Web Animations API 进行硬件加速性能一样,scroll 可以使用 Scroll Timeline API 获得两个性能优势

  • 启用硬件加速的滚动动画

  • 可以在不轮询滚动位置的情况下测量回调的滚动进度(消除样式重新计算)

startend 偏移选项不同,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 的 useAnimatehook.

以更小的包大小实现类似的模式。以 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,这使其能够为常见的动画(如 transformfilteropacity)提供硬件加速。

还有其他优化,例如使用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,这在相对文件大小中得到了体现。

最后,animateonUpdate 回调目前仅适用于动画单个值,但这将在未来发生变化。

迁移

在本指南中,我们将查看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 }
)

您可以在这里看到它看起来大致相似,但有一些关键差异。

  1. rotate 而不是 rotation

  2. repeat: Infinity 而不是 -1 用于无限重复的动画

  3. ease: "linear" 而不是 ease: "none"

还需要注意的另一件事是,在 GSAP 中,选项和动画值都捆绑在一起。而在 Motion 中,这些是单独的对象。这在实际应用中并不重要,但当动画普通对象时,这意味着该对象不能具有与 GSAP 选项同名的属性。

GSAP 还有另外两种动画方法,fromTofrom

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

在 Motion 中,这些类型的动画由 scroll函数.

const animation = animate(element, { x: 500 })
scroll(animation, { target: element })

驱动。scroll 的不同之处在于,就像 animate 可以使用 Web Animations API 进行硬件加速性能一样,scroll 可以使用 Scroll Timeline API 获得两个性能优势

  • 启用硬件加速的滚动动画

  • 可以在不轮询滚动位置的情况下测量回调的滚动进度(消除样式重新计算)

startend 偏移选项不同,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 的 useAnimatehook.

以更小的包大小实现类似的模式。以 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,这使其能够为常见的动画(如 transformfilteropacity)提供硬件加速。

还有其他优化,例如使用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,这在相对文件大小中得到了体现。

最后,animateonUpdate 回调目前仅适用于动画单个值,但这将在未来发生变化。

迁移

在本指南中,我们将查看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 }
)

您可以在这里看到它看起来大致相似,但有一些关键差异。

  1. rotate 而不是 rotation

  2. repeat: Infinity 而不是 -1 用于无限重复的动画

  3. ease: "linear" 而不是 ease: "none"

还需要注意的另一件事是,在 GSAP 中,选项和动画值都捆绑在一起。而在 Motion 中,这些是单独的对象。这在实际应用中并不重要,但当动画普通对象时,这意味着该对象不能具有与 GSAP 选项同名的属性。

GSAP 还有另外两种动画方法,fromTofrom

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

在 Motion 中,这些类型的动画由 scroll函数.

const animation = animate(element, { x: 500 })
scroll(animation, { target: element })

驱动。scroll 的不同之处在于,就像 animate 可以使用 Web Animations API 进行硬件加速性能一样,scroll 可以使用 Scroll Timeline API 获得两个性能优势

  • 启用硬件加速的滚动动画

  • 可以在不轮询滚动位置的情况下测量回调的滚动进度(消除样式重新计算)

startend 偏移选项不同,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 的 useAnimatehook.

以更小的包大小实现类似的模式。以 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 的哪些功能?请告诉我!

保持关注

订阅以获取最新消息和更新。

保持关注

订阅以获取最新消息和更新。