文档

文档

JavaScript

Web Animations API 的改进

Web Animations API 的改进

Motion 是唯一具有混合引擎的动画库,这意味着它能够通过 requestAnimationFrame 或通过Web Animations API(WAAPI)动态运行动画。

这使其可以为任何渲染目标(DOM、Three.js、canvas)的任何值制作动画,同时还保留了使用硬件加速运行动画的能力。

它的 animate 函数有两种大小:mini (2.3kb) 和 hybrid (17kb)。

这两个函数都对 WAAPI 的功能集和开发者体验进行了一些改进,在本指南中,我们将了解其中的一些改进。

弹簧动画和自定义缓动函数

CSS 和 WAAPI 仅支持内置的缓动函数,如 "back-in""ease-in-out" 等。

Motion 扩展了这一点,通过在现代浏览器中自动生成 linear() CSS 缓动定义,并在旧浏览器中提供安全的回退,从而支持任何自定义缓动函数

animate(
  "li",
  { opacity: 1 },
  { ease: mirrorEasing(Math.sin) }
)

此外,它还通过将弹簧动画编译为 linear() 缓动并计算适当的 duration,从而在 animateStyle 中支持 spring 动画。而在 animate 函数中,它将预先计算真实物理动画的实际关键帧。

import { animate } from "motion/dom"
import { spring } from "motion"

animate(
  "li",
  { transform: "translateX(100px)" },
  { type: spring, stiffness: 400 }
)

默认值类型

WAAPI 始终期望各种可动画值的单位类型,这很容易忘记。

element.animate({ width: "100px" })
element.animate({ width: 100 }) // Error!

Motion 知道所有常用值的默认值类型。

animate(element, { width: 100 })

.finished Promise

作为 WAAPI 规范的较新部分,animation.finished Promise 并非在所有浏览器中都受支持。Motion 将在这些浏览器中对其进行 polyfill

const animation = animate("#box", { opacity: 0 })

// Async
await animation

// Promise
animation.then(() => {})

以秒为单位的持续时间

在 WAAPI(和一些其他 JavaScript 动画库的子集中),持续时间以毫秒为单位设置

const animation = element.animate({ x: 50 }, { duration: 2000 })
animation.currentTime = 1000

在开发Framer Motion期间,用户测试表明,我们的大多数受众认为秒是更易于理解的单位。因此,在 Motion 中,持续时间以秒为单位定义。

const animation = animate(element, { x: 50 }, { duration: 2 })
animation.currentTime = 1

持久化动画状态

在典型的动画库中,当动画完成时,元素(或其他动画对象)将保持在动画的最终状态。

但是,当您像这样调用 WAAPI 的 animate 函数时

element.animate({ opacity: 0 })

这是结果

播放

动画在其初始状态结束!

WAAPI 有一个选项可以设置来修复此行为。称为 fill,当设置为 "forwards" 时,它会将动画持续到其时间线之外。

element.animate({ opacity: 0 }, { fill: "forwards" })

但这甚至在官方规范中也不被鼓励。fill: "forwards" 并没有真正改变动画的行为,最好将其视为无限期地保持动画处于活动状态。由于 WAAPI 动画的优先级高于 element.style,因此在这些动画处于活动状态时更改元素样式的唯一方法是使用更多动画!

保留所有这些无用的动画也可能导致内存泄漏。

规范提供了两种解决方案。一种是添加一个 Promise 处理程序,手动将最终关键帧目标设置为 element.style

await element.animate({ opacity: 0 }, 200).finished
  
element.style.opacity = 0

第二种是立即将 element.style 设置为动画目标,然后从其当前值开始动画,并让浏览器自行计算最终关键帧。

const opacity = element.style.opacity
element.style.opacity = 1
element.animate({ opacity, offset: 0 }, 200)

每种方法都有优点和缺点。但它们共同的主要缺点是让用户来决定。这些是对不直观行为的不直观修复,无论选择哪种方法,都需要一个包装库,因为重复这些脆弱的模式不利于可读性和稳定性。

因此,相反,Motion 的 animate 函数实际上会动画 一个值,并在动画完成后保持在其目标状态。

animate(element, { opacity: 0 })
播放

停止动画

WAAPI 的 animate 函数返回一个 Animation,其中包含一个 cancel 方法。

const animation = element.animate({ opacity: 0 }, { duration: 1000 })
setTimeout(() => { animation.cancel()}, 500)

当调用 cancel 时,动画将停止 “移除”。就好像动画从未播放过一样

播放

Motion 添加了一个 stop 方法。这将取消动画,但也会使元素保持其当前状态

const animation = animate(element, { opacity: 0 }, { duration: 1000 })
setTimeout(() => { animation.stop()}, 500)
播放

部分/推断的关键帧

在 WAAPI 规范的早期版本中,必须定义两个或多个关键帧

element.animate({ opacity: [0.2, 1] })

但是,后来更改为允许一个关键帧。浏览器将根据元素的当前视觉状态推断初始关键帧。

element.animate({ opacity: 1 })

一些旧版浏览器,包括常见的 WAAPI polyfill,仅支持旧语法。这意味着如果您尝试使用当前文档化的 WAAPI,它会在许多旧浏览器中抛出错误。
Motion 的 animate 函数会自动检测这些浏览器,并在必要时从 window.getComputedStyle(element) 生成初始关键帧。

中断动画

WAAPI 没有“中断”现有动画的概念。因此,如果一个动画在特定值上已经播放时开始,则新动画只会“覆盖”现有动画。

如果旧动画在新动画完成时仍在运行,则动画值将看起来“跳回”到旧动画。

element.animate(
  { transform: ["none", "translateX(300px)"] },
  { duration: 2000, iterations: Infinity, direction: "alternate" }
)
  
setTimeout(() => {
  element.animate({ transform: "none" }, { duration: 500 })
}, 500)
中断

Motion 会自动中断传递给 animate 的任何值的动画,并动画到新目标

animate(
  element,
  { transform: "translateX(300px)" },
  { duration: 2, iterations: Infinity }
)
  
setTimeout(() => {
  animate(element, { transform: "none" }, { duration: 500 })
}, 500)
中断

三次贝塞尔曲线定义

在 WAAPI 中,三次贝塞尔曲线缓动定义为 CSS 字符串

element.animate(
  { transform: "translateX(50px)" },
  { easing: "cubic-bezier(0.29, -0.13, 0.18, 1.18)" }
)

这种定义在 Motion 中可以使用,但我们也允许这种简写数组语法

animate(
  element,
  { transform: "translateX(50px)" },
  { ease: [0.29, -0.13, 0.18, 1.18] }
)

独立的变换(仅限 animate

由于 CSS 不提供 xscaleX 等样式,因此您无法使用 WAAPI 为这些属性制作动画。相反,您必须为完整的 transform 字符串制作动画

element.animate({ transform: "translateX(50px) scaleX(2)" })

这不仅仅是开发者美学的问题。这意味着实际上不可能使用单独的动画或不同的动画选项为这些属性制作动画。

一些现代浏览器允许单独定义和动画 translatescalerotate,但即使这样,您也无法动画每个轴。

Motion 仍然允许动画 transform,但增加了单独动画所有变换的能力,适用于所有轴

animate(element, { x: 50, scaleX: 2 })

这意味着您还可以使用不同的选项为它们制作动画

animate(
  element,
  { x: 50, scaleX: 2 },
  { x: { duration 2 }, scaleX: { repeat: 1 } }
)

Motion 是唯一具有混合引擎的动画库,这意味着它能够通过 requestAnimationFrame 或通过Web Animations API(WAAPI)动态运行动画。

这使其可以为任何渲染目标(DOM、Three.js、canvas)的任何值制作动画,同时还保留了使用硬件加速运行动画的能力。

它的 animate 函数有两种大小:mini (2.3kb) 和 hybrid (17kb)。

这两个函数都对 WAAPI 的功能集和开发者体验进行了一些改进,在本指南中,我们将了解其中的一些改进。

弹簧动画和自定义缓动函数

CSS 和 WAAPI 仅支持内置的缓动函数,如 "back-in""ease-in-out" 等。

Motion 扩展了这一点,通过在现代浏览器中自动生成 linear() CSS 缓动定义,并在旧浏览器中提供安全的回退,从而支持任何自定义缓动函数

animate(
  "li",
  { opacity: 1 },
  { ease: mirrorEasing(Math.sin) }
)

此外,它还通过将弹簧动画编译为 linear() 缓动并计算适当的 duration,从而在 animateStyle 中支持 spring 动画。而在 animate 函数中,它将预先计算真实物理动画的实际关键帧。

import { animate } from "motion/dom"
import { spring } from "motion"

animate(
  "li",
  { transform: "translateX(100px)" },
  { type: spring, stiffness: 400 }
)

默认值类型

WAAPI 始终期望各种可动画值的单位类型,这很容易忘记。

element.animate({ width: "100px" })
element.animate({ width: 100 }) // Error!

Motion 知道所有常用值的默认值类型。

animate(element, { width: 100 })

.finished Promise

作为 WAAPI 规范的较新部分,animation.finished Promise 并非在所有浏览器中都受支持。Motion 将在这些浏览器中对其进行 polyfill

const animation = animate("#box", { opacity: 0 })

// Async
await animation

// Promise
animation.then(() => {})

以秒为单位的持续时间

在 WAAPI(和一些其他 JavaScript 动画库的子集中),持续时间以毫秒为单位设置

const animation = element.animate({ x: 50 }, { duration: 2000 })
animation.currentTime = 1000

在开发Framer Motion期间,用户测试表明,我们的大多数受众认为秒是更易于理解的单位。因此,在 Motion 中,持续时间以秒为单位定义。

const animation = animate(element, { x: 50 }, { duration: 2 })
animation.currentTime = 1

持久化动画状态

在典型的动画库中,当动画完成时,元素(或其他动画对象)将保持在动画的最终状态。

但是,当您像这样调用 WAAPI 的 animate 函数时

element.animate({ opacity: 0 })

这是结果

播放

动画在其初始状态结束!

WAAPI 有一个选项可以设置来修复此行为。称为 fill,当设置为 "forwards" 时,它会将动画持续到其时间线之外。

element.animate({ opacity: 0 }, { fill: "forwards" })

但这甚至在官方规范中也不被鼓励。fill: "forwards" 并没有真正改变动画的行为,最好将其视为无限期地保持动画处于活动状态。由于 WAAPI 动画的优先级高于 element.style,因此在这些动画处于活动状态时更改元素样式的唯一方法是使用更多动画!

保留所有这些无用的动画也可能导致内存泄漏。

规范提供了两种解决方案。一种是添加一个 Promise 处理程序,手动将最终关键帧目标设置为 element.style

await element.animate({ opacity: 0 }, 200).finished
  
element.style.opacity = 0

第二种是立即将 element.style 设置为动画目标,然后从其当前值开始动画,并让浏览器自行计算最终关键帧。

const opacity = element.style.opacity
element.style.opacity = 1
element.animate({ opacity, offset: 0 }, 200)

每种方法都有优点和缺点。但它们共同的主要缺点是让用户来决定。这些是对不直观行为的不直观修复,无论选择哪种方法,都需要一个包装库,因为重复这些脆弱的模式不利于可读性和稳定性。

因此,相反,Motion 的 animate 函数实际上会动画 一个值,并在动画完成后保持在其目标状态。

animate(element, { opacity: 0 })
播放

停止动画

WAAPI 的 animate 函数返回一个 Animation,其中包含一个 cancel 方法。

const animation = element.animate({ opacity: 0 }, { duration: 1000 })
setTimeout(() => { animation.cancel()}, 500)

当调用 cancel 时,动画将停止 “移除”。就好像动画从未播放过一样

播放

Motion 添加了一个 stop 方法。这将取消动画,但也会使元素保持其当前状态

const animation = animate(element, { opacity: 0 }, { duration: 1000 })
setTimeout(() => { animation.stop()}, 500)
播放

部分/推断的关键帧

在 WAAPI 规范的早期版本中,必须定义两个或多个关键帧

element.animate({ opacity: [0.2, 1] })

但是,后来更改为允许一个关键帧。浏览器将根据元素的当前视觉状态推断初始关键帧。

element.animate({ opacity: 1 })

一些旧版浏览器,包括常见的 WAAPI polyfill,仅支持旧语法。这意味着如果您尝试使用当前文档化的 WAAPI,它会在许多旧浏览器中抛出错误。
Motion 的 animate 函数会自动检测这些浏览器,并在必要时从 window.getComputedStyle(element) 生成初始关键帧。

中断动画

WAAPI 没有“中断”现有动画的概念。因此,如果一个动画在特定值上已经播放时开始,则新动画只会“覆盖”现有动画。

如果旧动画在新动画完成时仍在运行,则动画值将看起来“跳回”到旧动画。

element.animate(
  { transform: ["none", "translateX(300px)"] },
  { duration: 2000, iterations: Infinity, direction: "alternate" }
)
  
setTimeout(() => {
  element.animate({ transform: "none" }, { duration: 500 })
}, 500)
中断

Motion 会自动中断传递给 animate 的任何值的动画,并动画到新目标

animate(
  element,
  { transform: "translateX(300px)" },
  { duration: 2, iterations: Infinity }
)
  
setTimeout(() => {
  animate(element, { transform: "none" }, { duration: 500 })
}, 500)
中断

三次贝塞尔曲线定义

在 WAAPI 中,三次贝塞尔曲线缓动定义为 CSS 字符串

element.animate(
  { transform: "translateX(50px)" },
  { easing: "cubic-bezier(0.29, -0.13, 0.18, 1.18)" }
)

这种定义在 Motion 中可以使用,但我们也允许这种简写数组语法

animate(
  element,
  { transform: "translateX(50px)" },
  { ease: [0.29, -0.13, 0.18, 1.18] }
)

独立的变换(仅限 animate

由于 CSS 不提供 xscaleX 等样式,因此您无法使用 WAAPI 为这些属性制作动画。相反,您必须为完整的 transform 字符串制作动画

element.animate({ transform: "translateX(50px) scaleX(2)" })

这不仅仅是开发者美学的问题。这意味着实际上不可能使用单独的动画或不同的动画选项为这些属性制作动画。

一些现代浏览器允许单独定义和动画 translatescalerotate,但即使这样,您也无法动画每个轴。

Motion 仍然允许动画 transform,但增加了单独动画所有变换的能力,适用于所有轴

animate(element, { x: 50, scaleX: 2 })

这意味着您还可以使用不同的选项为它们制作动画

animate(
  element,
  { x: 50, scaleX: 2 },
  { x: { duration 2 }, scaleX: { repeat: 1 } }
)

Motion 是唯一具有混合引擎的动画库,这意味着它能够通过 requestAnimationFrame 或通过Web Animations API(WAAPI)动态运行动画。

这使其可以为任何渲染目标(DOM、Three.js、canvas)的任何值制作动画,同时还保留了使用硬件加速运行动画的能力。

它的 animate 函数有两种大小:mini (2.3kb) 和 hybrid (17kb)。

这两个函数都对 WAAPI 的功能集和开发者体验进行了一些改进,在本指南中,我们将了解其中的一些改进。

弹簧动画和自定义缓动函数

CSS 和 WAAPI 仅支持内置的缓动函数,如 "back-in""ease-in-out" 等。

Motion 扩展了这一点,通过在现代浏览器中自动生成 linear() CSS 缓动定义,并在旧浏览器中提供安全的回退,从而支持任何自定义缓动函数

animate(
  "li",
  { opacity: 1 },
  { ease: mirrorEasing(Math.sin) }
)

此外,它还通过将弹簧动画编译为 linear() 缓动并计算适当的 duration,从而在 animateStyle 中支持 spring 动画。而在 animate 函数中,它将预先计算真实物理动画的实际关键帧。

import { animate } from "motion/dom"
import { spring } from "motion"

animate(
  "li",
  { transform: "translateX(100px)" },
  { type: spring, stiffness: 400 }
)

默认值类型

WAAPI 始终期望各种可动画值的单位类型,这很容易忘记。

element.animate({ width: "100px" })
element.animate({ width: 100 }) // Error!

Motion 知道所有常用值的默认值类型。

animate(element, { width: 100 })

.finished Promise

作为 WAAPI 规范的较新部分,animation.finished Promise 并非在所有浏览器中都受支持。Motion 将在这些浏览器中对其进行 polyfill

const animation = animate("#box", { opacity: 0 })

// Async
await animation

// Promise
animation.then(() => {})

以秒为单位的持续时间

在 WAAPI(和一些其他 JavaScript 动画库的子集中),持续时间以毫秒为单位设置

const animation = element.animate({ x: 50 }, { duration: 2000 })
animation.currentTime = 1000

在开发Framer Motion期间,用户测试表明,我们的大多数受众认为秒是更易于理解的单位。因此,在 Motion 中,持续时间以秒为单位定义。

const animation = animate(element, { x: 50 }, { duration: 2 })
animation.currentTime = 1

持久化动画状态

在典型的动画库中,当动画完成时,元素(或其他动画对象)将保持在动画的最终状态。

但是,当您像这样调用 WAAPI 的 animate 函数时

element.animate({ opacity: 0 })

这是结果

播放

动画在其初始状态结束!

WAAPI 有一个选项可以设置来修复此行为。称为 fill,当设置为 "forwards" 时,它会将动画持续到其时间线之外。

element.animate({ opacity: 0 }, { fill: "forwards" })

但这甚至在官方规范中也不被鼓励。fill: "forwards" 并没有真正改变动画的行为,最好将其视为无限期地保持动画处于活动状态。由于 WAAPI 动画的优先级高于 element.style,因此在这些动画处于活动状态时更改元素样式的唯一方法是使用更多动画!

保留所有这些无用的动画也可能导致内存泄漏。

规范提供了两种解决方案。一种是添加一个 Promise 处理程序,手动将最终关键帧目标设置为 element.style

await element.animate({ opacity: 0 }, 200).finished
  
element.style.opacity = 0

第二种是立即将 element.style 设置为动画目标,然后从其当前值开始动画,并让浏览器自行计算最终关键帧。

const opacity = element.style.opacity
element.style.opacity = 1
element.animate({ opacity, offset: 0 }, 200)

每种方法都有优点和缺点。但它们共同的主要缺点是让用户来决定。这些是对不直观行为的不直观修复,无论选择哪种方法,都需要一个包装库,因为重复这些脆弱的模式不利于可读性和稳定性。

因此,相反,Motion 的 animate 函数实际上会动画 一个值,并在动画完成后保持在其目标状态。

animate(element, { opacity: 0 })
播放

停止动画

WAAPI 的 animate 函数返回一个 Animation,其中包含一个 cancel 方法。

const animation = element.animate({ opacity: 0 }, { duration: 1000 })
setTimeout(() => { animation.cancel()}, 500)

当调用 cancel 时,动画将停止 “移除”。就好像动画从未播放过一样

播放

Motion 添加了一个 stop 方法。这将取消动画,但也会使元素保持其当前状态

const animation = animate(element, { opacity: 0 }, { duration: 1000 })
setTimeout(() => { animation.stop()}, 500)
播放

部分/推断的关键帧

在 WAAPI 规范的早期版本中,必须定义两个或多个关键帧

element.animate({ opacity: [0.2, 1] })

但是,后来更改为允许一个关键帧。浏览器将根据元素的当前视觉状态推断初始关键帧。

element.animate({ opacity: 1 })

一些旧版浏览器,包括常见的 WAAPI polyfill,仅支持旧语法。这意味着如果您尝试使用当前文档化的 WAAPI,它会在许多旧浏览器中抛出错误。
Motion 的 animate 函数会自动检测这些浏览器,并在必要时从 window.getComputedStyle(element) 生成初始关键帧。

中断动画

WAAPI 没有“中断”现有动画的概念。因此,如果一个动画在特定值上已经播放时开始,则新动画只会“覆盖”现有动画。

如果旧动画在新动画完成时仍在运行,则动画值将看起来“跳回”到旧动画。

element.animate(
  { transform: ["none", "translateX(300px)"] },
  { duration: 2000, iterations: Infinity, direction: "alternate" }
)
  
setTimeout(() => {
  element.animate({ transform: "none" }, { duration: 500 })
}, 500)
中断

Motion 会自动中断传递给 animate 的任何值的动画,并动画到新目标

animate(
  element,
  { transform: "translateX(300px)" },
  { duration: 2, iterations: Infinity }
)
  
setTimeout(() => {
  animate(element, { transform: "none" }, { duration: 500 })
}, 500)
中断

三次贝塞尔曲线定义

在 WAAPI 中,三次贝塞尔曲线缓动定义为 CSS 字符串

element.animate(
  { transform: "translateX(50px)" },
  { easing: "cubic-bezier(0.29, -0.13, 0.18, 1.18)" }
)

这种定义在 Motion 中可以使用,但我们也允许这种简写数组语法

animate(
  element,
  { transform: "translateX(50px)" },
  { ease: [0.29, -0.13, 0.18, 1.18] }
)

独立的变换(仅限 animate

由于 CSS 不提供 xscaleX 等样式,因此您无法使用 WAAPI 为这些属性制作动画。相反,您必须为完整的 transform 字符串制作动画

element.animate({ transform: "translateX(50px) scaleX(2)" })

这不仅仅是开发者美学的问题。这意味着实际上不可能使用单独的动画或不同的动画选项为这些属性制作动画。

一些现代浏览器允许单独定义和动画 translatescalerotate,但即使这样,您也无法动画每个轴。

Motion 仍然允许动画 transform,但增加了单独动画所有变换的能力,适用于所有轴

animate(element, { x: 50, scaleX: 2 })

这意味着您还可以使用不同的选项为它们制作动画

animate(
  element,
  { x: 50, scaleX: 2 },
  { x: { duration 2 }, scaleX: { repeat: 1 } }
)
保持关注

订阅以获取最新新闻和更新。

保持关注

订阅以获取最新新闻和更新。