跳到主要内容
版本:v6

动画

概述

Ionic Animations 是一个工具,使开发者能够以平台无关的方式创建复杂动画,无需特定的框架或 Ionic 应用。

创建高效的动画可能具有挑战性,因为它受到可用库和设备硬件资源的限制。此外,许多动画库使用 JavaScript 驱动的方法,这可能会降低动画的可扩展性并占用 CPU 时间。

相比之下,Ionic Animations 使用 Web Animations API,将所有动画的计算和运行卸载到浏览器。这种方法允许浏览器优化动画并确保其平滑执行。在不支持 Web Animations 的情况下,Ionic Animations 将回退到 CSS Animations,性能差异可以忽略不计。

安装

使用 Ionic Core 和 JavaScript 的开发者应安装最新版本的 @ionic/core

import { createAnimation } from 'https://cdn.jsdelivr.net/npm/@ionic/core@latest/dist/esm/index.mjs';

...

const animation = createAnimation()
.addElement(myElementRef)
.duration(1000)
.fromTo('opacity', '1', '0.5');
}

基本动画

用法

createAnimation()
.addElement(document.querySelector('.square'))
.duration(1500)
.iterations(Infinity)
.fromTo('transform', 'translateX(0px)', 'translateX(100px)')
.fromTo('opacity', '1', '0.2');

在上面的示例中,创建了一个动画,该动画会改变 .square 元素的不透明度并使其沿 X 轴从左向右移动。此动画将无限次运行,每次迭代持续 1500ms。

默认情况下,所有 Ionic Animations 都处于暂停状态,直到调用 play 方法。

关键帧动画

Ionic Animations 允许您使用关键帧控制动画中的中间步骤。这里可以使用任何有效的 CSS 属性,您甚至可以使用 CSS 变量作为值。

在编写关键帧时,连字符形式的 CSS 属性应使用驼峰形式书写。例如,border-radius 应写为 borderRadius。这也适用于 fromTo()from()to() 方法。

用法

createAnimation()
.addElement(document.querySelector('.square'))
.duration(3000)
.iterations(Infinity)
.keyframes([
{ offset: 0, background: 'red' },
{ offset: 0.72, background: 'var(--background)' },
{ offset: 1, background: 'green' }
]);

在上面的示例中,.square 元素将从红色背景过渡到由 --background 变量定义的背景色,然后过渡到绿色背景。

每个关键帧对象都包含一个 offset 属性。offset 是 0 到 1 之间的值,用于定义关键帧步骤。偏移值必须按升序排列且不能重复。

分组动画

多个元素可以同时进行动画,并通过单个父动画对象进行控制。除非另有指定,子动画会继承持续时间、缓动和迭代次数等属性。父动画的 onFinish 回调在所有子动画完成之前不会被调用。

用法

const squareA = createAnimation()
.addElement(document.querySelector('.square-a'))
.keyframes([
{ offset: 0, transform: 'scale(1) rotate(0)' },
{ offset: 0.5, transform: 'scale(1.2) rotate(45deg)' },
{ offset: 1, transform: 'scale(1) rotate(45deg)' }
]);

const squareB = createAnimation()
.addElement(document.querySelector('.square-b'))
.keyframes([
{ offset: 0, transform: 'scale(1))', opacity: '1' },
{ offset: 0.5, transform: 'scale(1.2)', opacity: '0.3' },
{ offset: 1, transform: 'scale(1)', opacity: '1' }
]);

const squareC = createAnimation()
.addElement(document.querySelector('.square-c'))
.duration(5000)
.keyframes([
{ offset: 0, transform: 'scale(1))', opacity: '0.5' },
{ offset: 0.5, transform: 'scale(0.8)', opacity: '1' },
{ offset: 1, transform: 'scale(1)', opacity: '0.5' }
]);

const parent = createAnimation()
.duration(2000)
.iterations(Infinity)
.addAnimation([squareA, squareB, squareC]);

此示例展示了由单个父动画控制的 3 个子动画。动画 squareAsquareB 继承了父动画的 2000ms 持续时间,但动画 squareC 由于显式设置了持续时间,所以为 5000ms。

前后钩子

Ionic Animations 提供了钩子,让您可以在动画运行之前和动画完成之后改变元素。这些钩子可用于执行 DOM 读写操作,以及添加或删除类和内联样式。

用法

createAnimation()
.addElement(document.querySelector('.square'))
.duration(2000)
.beforeStyles({
opacity: 0.2
})
.afterStyles({
background: 'rgba(0, 255, 0, 0.5)'
})
.afterClearStyles(['opacity'])
.keyframes([
{ offset: 0, transform: 'scale(1)' },
{ offset: 0.5, transform: 'scale(1.5)' },
{ offset: 1, transform: 'scale(1)' }
])

在此示例中,在动画开始之前,为 .square 元素设置了 0.2 的内联不透明度。动画完成后,元素的背景色被设置为 rgba(0, 255, 0, 0.5),并且内联不透明度被清除。

参见方法部分获取完整的钩子列表。

链式动画

动画可以链接起来依次运行。play 方法返回一个 Promise,该 Promise 在动画完成时解析。

用法

const squareA = createAnimation()
.addElement(document.querySelector('.square-a'))
.fill('none')
.duration(1000)
.keyframes([
{ offset: 0, transform: 'scale(1) rotate(0)' },
{ offset: 0.5, transform: 'scale(1.2) rotate(45deg)' },
{ offset: 1, transform: 'scale(1) rotate(0)' }
]);

const squareB = createAnimation()
.addElement(document.querySelector('.square-b'))
.fill('none')
.duration(1000)
.keyframes([
{ offset: 0, transform: 'scale(1)', opacity: '1' },
{ offset: 0.5, transform: 'scale(1.2)', opacity: '0.3' },
{ offset: 1, transform: 'scale(1)', opacity: '1' }
]);

const squareC = createAnimation()
.addElement(document.querySelector('.square-c'))
.fill('none')
.duration(1000)
.keyframes([
{ offset: 0, transform: 'scale(1)', opacity: '0.5' },
{ offset: 0.5, transform: 'scale(0.8)', opacity: '1' },
{ offset: 1, transform: 'scale(1)', opacity: '0.5' }
]);

await squareA.play();
await squareB.play();
await squareC.play();

手势动画

Ionic Animations 使开发者能够通过与 Ionic Gestures 无缝集成来创建强大的基于手势的动画。

用法

let initialStep = 0;
let started = false;

const square = document.querySelector('.square');
const MAX_TRANSLATE = 400;

const animation = createAnimation()
.addElement(square)
.duration(1000)
.fromTo('transform', 'translateX(0)', `translateX(${MAX_TRANSLATE}px)`);

const gesture = createGesture({
el: square,
threshold: 0,
gestureName: 'square-drag',
onMove: ev => onMove(ev),
onEnd: ev => onEnd(ev)
})

gesture.enable(true);

const onMove = (ev): {
if (!started) {
animation.progressStart();
started = true;
}

animation.progressStep(getStep(ev));
}

const onEnd = (ev): {
if (!started) { return; }

gesture.enable(false);

const step = getStep(ev);
const shouldComplete = step > 0.5;

animation
.progressEnd((shouldComplete) ? 1 : 0, step)
.onFinish((): { gesture.enable(true); });

initialStep = (shouldComplete) ? MAX_TRANSLATE : 0;
started = false;
}

const clamp = (min, n, max): {
return Math.max(min, Math.min(n, max));
};

const getStep = (ev): {
const delta = initialStep + ev.deltaX;
return clamp(0, delta / MAX_TRANSLATE, 1);
}

在此示例中,我们创建了一个轨道,可以沿着它拖动 .square 元素。我们的 animation 对象将负责向左或向右移动 .square 元素,而 gesture 对象将指示 animation 对象向哪个方向移动。

基于偏好的动画

开发者还可以使用 CSS 变量根据用户偏好(如 prefers-reduced-motionprefers-color-scheme)来定制动画。

用法

.square {
width: 100px;
height: 100px;
opacity: 0.5;
background: blue;
margin: 10px;

--background: red;
}

@media (prefers-color-scheme: dark) {
.square {
--background: green;
}
}
createAnimation()
.addElement(document.querySelector('.square'))
.duration(1500)
.iterations(Infinity)
.direction('alternate')
.fromTo('background', 'blue', 'var(--background)');

此方法在首次创建动画时适用于所有支持的浏览器。大多数浏览器还能够随着 CSS 变量的变化动态更新关键帧动画。

Safari 目前不支持动态更新关键帧动画。对于需要在 Safari 中获得此类支持的开发者,可以使用 MediaQueryList.addListener()

覆盖 Ionic 组件动画

某些 Ionic 组件允许开发者提供自定义动画。所有动画都作为组件上的属性提供或通过全局配置设置。

模态框

customElements.define('modal-page', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Modal Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
Modal Content
</ion-content>
`;
}
});

function presentModal() {
const enterAnimation = (baseEl: any) => {
const root = baseEl.shadowRoot;

const backdropAnimation = createAnimation()
.addElement(root.querySelector('ion-backdrop')!)
.fromTo('opacity', '0.01', 'var(--backdrop-opacity)');

const wrapperAnimation = createAnimation()
.addElement(root.querySelector('.modal-wrapper')!)
.keyframes([
{ offset: 0, opacity: '0', transform: 'scale(0)' },
{ offset: 1, opacity: '0.99', transform: 'scale(1)' }
]);

return createAnimation()
.addElement(baseEl)
.easing('ease-out')
.duration(500)
.addAnimation([backdropAnimation, wrapperAnimation]);
}

const leaveAnimation = (baseEl: any) => {
return enterAnimation(baseEl).direction('reverse');
}

// 使用 `modal-page` 组件创建模态框
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
modalElement.enterAnimation = enterAnimation;
modalElement.leaveAnimation = leaveAnimation;

// 呈现模态框
document.body.appendChild(modalElement);
return modalElement.present();
}

性能考虑

CSS 和 Web 动画通常在合成线程上处理。这不同于执行布局、绘制、样式和 JavaScript 的主线程。建议优先使用可以在合成线程上处理的属性,以获得最佳动画性能。

对诸如 heightwidth 等属性进行动画会导致额外的布局和绘制,这可能导致卡顿并降低动画性能。另一方面,对诸如 transformopacity 等属性进行动画可以由浏览器高度优化,通常不会引起太多卡顿。

有关哪些 CSS 属性会导致布局或绘制的信息,请参阅 CSS Triggers

调试

要在 Chrome 中调试动画,有一篇关于使用 Chrome DevTools 检查动画的优秀博客文章:https://developers.google.com/web/tools/chrome-devtools/inspect-styles/animations。

还建议为您的动画分配唯一的标识符。这些标识符将显示在 Chrome 的动画检查器中,并使调试更容易:

/**
* .square 元素的动画应在 Chrome DevTools 中
* 显示 "my-animation-identifier"。
*/
const animation = createAnimation('my-animation-identifier')
.addElement(document.querySelector('.square'))
.duration(1000)
.fromTo('opacity', '1', '0');

API

本节提供了 Animation 类上可用的所有方法和属性的列表。

接口

AnimationDirection

type AnimationDirection = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';

AnimationFill

type AnimationFill = 'auto' | 'none' | 'forwards' | 'backwards' | 'both';

AnimationBuilder

type AnimationBuilder = (baseEl: any, opts?: any) => Animation;
备注

opts 是特定于自定义动画的附加选项。例如,sheet 模态框的进入动画包含当前断点的信息。

AnimationCallbackOptions

interface AnimationCallbackOptions {
/**
* 如果为 true,相关回调将只触发一次。
*/
oneTimeCallback: boolean;
}

AnimationPlayOptions

interface AnimationPlayOptions {
/**
* 如果为 true,动画将同步播放。
* 这相当于以 0ms 的持续时间运行动画。
*/
sync: boolean;
}

属性

名称描述
childAnimations: Animation[]给定父动画的所有子动画。
elements: HTMLElement[]附加到动画的所有元素。
parentAnimation?: Animation给定动画对象的父动画。

方法

名称描述
addAnimation(animationToAdd: Animation | Animation[]): Animation将一个或多个动画组合在一起,由父动画控制。
addElement(el: Element | Element[] | Node | Node[] | NodeList): Animation向动画添加一个或多个元素。
afterAddClass(className: string | string[]): Animation添加一个类或类数组,在动画结束后添加到动画中的所有元素。
afterAddRead(readFn: (): void): Animation添加一个执行 DOM 读取的函数,在动画结束后运行。
afterAddWrite(writeFn: (): void): Animation添加一个执行 DOM 写入的函数,在动画结束后运行。
afterClearStyles(propertyNames: string[]): Animation添加属性名称数组,在动画结束后从动画中所有元素的内联样式中清除。
afterRemoveClass(className: string | string[]): Animation添加一个类或类数组,在动画结束后从动画中的所有元素中移除。
afterStyles(styles: { [property: string]: any }): Animation添加样式对象,在动画结束后应用于动画中的所有元素。
beforeAddClass(className: string | string[]): Animation添加一个类或类数组,在动画开始前添加到动画中的所有元素。
beforeAddRead(readFn: (): void): Animation添加一个执行 DOM 读取的函数,在动画开始前运行。
beforeAddWrite(writeFn: (): void): Animation添加一个执行 DOM 写入的函数,在动画开始前运行。
beforeClearStyles(propertyNames: string[]): Animation添加属性名称数组,在动画开始前从动画中所有元素的内联样式中清除。
beforeRemoveClass(className: string | string[]): Animation添加一个类或类数组,在动画开始前从动画中的所有元素中移除。
beforeStyles(styles: { [property: string]: any }): Animation添加样式对象,在动画开始前应用于动画中的所有元素。
direction(direction?: AnimationDirection): Animation设置动画播放的方向。
delay(delay?: number): Animation设置动画开始的延迟时间(毫秒)。
destroy(clearStyleSheets?: boolean): Animation销毁动画并清除所有元素、子动画和关键帧。
duration(duration?: number): Animation设置动画的持续时间(毫秒)。
easing(easing?: string): Animation设置动画的缓动效果。参见 Easing Effects 获取可接受的缓动值列表。
from(property: string, value: any): Animation设置动画的起始样式。
fromTo(property: string, fromValue: any, toValue: any): Animation设置动画的起始和结束样式。
fill(fill?: AnimationFill): Animation设置动画在执行前后如何将样式应用于其元素。
iterations(iterations: number): Animation设置动画循环在停止前应播放的次数。
keyframes(keyframes: any[]): Animation设置动画的关键帧。
onFinish(callback: (didComplete: boolean, animation: Animation): void, opts?: AnimationCallbackOptions): Animation添加动画结束时运行的回调。
pause(): Animation暂停动画。
play(opts?: AnimationPlayOptions): Promise<void>播放动画。
progressEnd(playTo?: 0 | 1, step: number, dur?: number): Animation停止在动画中搜索。
progressStart(forceLinearEasing?: boolean, step?: number): Animation开始在动画中搜索。
progressStep(step: number): Animation在动画中搜索。
stop(): Animation停止动画并将所有元素重置为初始状态。
to(property: string, value: any): Animation设置动画的结束样式。