mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 01:52:19 +08:00
206 lines
5.5 KiB
TypeScript
206 lines
5.5 KiB
TypeScript
import type {
|
|
Animation,
|
|
AnimationCallbackOptions,
|
|
AnimationDirection,
|
|
AnimationFill,
|
|
AnimationKeyFrames,
|
|
AnimationLifecycle,
|
|
} from '@ionic/core/components';
|
|
import { createAnimation } from '@ionic/core/components';
|
|
import type { PropsWithChildren } from 'react';
|
|
import React from 'react';
|
|
|
|
// TODO(FW-2959): types
|
|
|
|
interface PartialPropertyValue {
|
|
property: string;
|
|
value: any;
|
|
}
|
|
interface PropertyValue {
|
|
property: string;
|
|
fromValue: any;
|
|
toValue: any;
|
|
}
|
|
|
|
export interface CreateAnimationProps {
|
|
delay?: number;
|
|
direction?: AnimationDirection;
|
|
duration?: number;
|
|
easing?: string;
|
|
fill?: AnimationFill;
|
|
iterations?: number;
|
|
id?: string;
|
|
|
|
afterAddRead?: () => void;
|
|
afterAddWrite?: () => void;
|
|
afterClearStyles?: string[];
|
|
afterStyles?: { [property: string]: any };
|
|
afterAddClass?: string | string[];
|
|
afterRemoveClass?: string | string[];
|
|
|
|
beforeAddRead?: () => void;
|
|
beforeAddWrite?: () => void;
|
|
beforeClearStyles?: string[];
|
|
beforeStyles?: { [property: string]: any };
|
|
beforeAddClass?: string | string[];
|
|
beforeRemoveClass?: string | string[];
|
|
|
|
onFinish?: { callback: AnimationLifecycle; opts?: AnimationCallbackOptions };
|
|
|
|
keyframes?: AnimationKeyFrames;
|
|
from?: PartialPropertyValue[] | PartialPropertyValue;
|
|
to?: PartialPropertyValue[] | PartialPropertyValue;
|
|
fromTo?: PropertyValue[] | PropertyValue;
|
|
|
|
play?: boolean;
|
|
pause?: boolean;
|
|
stop?: boolean;
|
|
destroy?: boolean;
|
|
|
|
progressStart?: { forceLinearEasing: boolean; step?: number };
|
|
progressStep?: { step: number };
|
|
progressEnd?: { playTo: 0 | 1 | undefined; step: number; dur?: number };
|
|
}
|
|
|
|
export class CreateAnimation extends React.PureComponent<PropsWithChildren<CreateAnimationProps>> {
|
|
nodes: Map<number, HTMLElement> = new Map();
|
|
animation: Animation;
|
|
|
|
constructor(props: any) {
|
|
super(props);
|
|
|
|
this.animation = createAnimation(props.id);
|
|
}
|
|
|
|
setupAnimation(props: any) {
|
|
const animation = this.animation;
|
|
|
|
if (this.nodes.size > 0) {
|
|
animation.addElement(Array.from(this.nodes.values()));
|
|
}
|
|
|
|
checkConfig(animation, props);
|
|
checkPlayback(animation, props);
|
|
}
|
|
|
|
componentDidMount() {
|
|
const props = this.props;
|
|
this.setupAnimation(props);
|
|
}
|
|
|
|
componentDidUpdate(prevProps: any) {
|
|
const animation = this.animation;
|
|
|
|
const props = this.props;
|
|
|
|
checkConfig(animation, props, prevProps);
|
|
checkProgress(animation, props, prevProps);
|
|
checkPlayback(animation, props, prevProps);
|
|
}
|
|
|
|
render() {
|
|
const { children } = this.props;
|
|
return (
|
|
<>
|
|
{React.Children.map(children, (child, id) =>
|
|
React.cloneElement(child as any, { ref: (el: any) => this.nodes.set(id, el) })
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
const checkConfig = (animation: Animation, currentProps: any = {}, prevProps: any = {}) => {
|
|
const reservedProps = [
|
|
'children',
|
|
'progressStart',
|
|
'progressStep',
|
|
'progressEnd',
|
|
'pause',
|
|
'stop',
|
|
'destroy',
|
|
'play',
|
|
'from',
|
|
'to',
|
|
'fromTo',
|
|
'onFinish',
|
|
];
|
|
for (const key in currentProps) {
|
|
if (
|
|
// eslint-disable-next-line no-prototype-builtins
|
|
currentProps.hasOwnProperty(key) &&
|
|
!reservedProps.includes(key) &&
|
|
currentProps[key] !== prevProps[key]
|
|
) {
|
|
(animation as any)[key]((currentProps as any)[key]);
|
|
}
|
|
}
|
|
|
|
const fromValues = currentProps.from;
|
|
if (fromValues && fromValues !== prevProps.from) {
|
|
const values = Array.isArray(fromValues) ? fromValues : [fromValues];
|
|
values.forEach((val) => animation.from(val.property, val.value));
|
|
}
|
|
|
|
const toValues = currentProps.to;
|
|
if (toValues && toValues !== prevProps.to) {
|
|
const values = Array.isArray(toValues) ? toValues : [toValues];
|
|
values.forEach((val) => animation.to(val.property, val.value));
|
|
}
|
|
|
|
const fromToValues = currentProps.fromTo;
|
|
if (fromToValues && fromToValues !== prevProps.fromTo) {
|
|
const values = Array.isArray(fromToValues) ? fromToValues : [fromToValues];
|
|
values.forEach((val) => animation.fromTo(val.property, val.fromValue, val.toValue));
|
|
}
|
|
|
|
const onFinishValues = currentProps.onFinish;
|
|
if (onFinishValues && onFinishValues !== prevProps.onFinish) {
|
|
const values = Array.isArray(onFinishValues) ? onFinishValues : [onFinishValues];
|
|
values.forEach((val) => animation.onFinish(val.callback, val.opts));
|
|
}
|
|
};
|
|
|
|
const checkProgress = (animation: Animation, currentProps: any = {}, prevProps: any = {}) => {
|
|
const { progressStart, progressStep, progressEnd } = currentProps;
|
|
|
|
if (
|
|
progressStart &&
|
|
(prevProps.progressStart?.forceLinearEasing !== progressStart?.forceLinearEasing ||
|
|
prevProps.progressStart?.step !== progressStart?.step)
|
|
) {
|
|
animation.progressStart(progressStart.forceLinearEasing, progressStart.step);
|
|
}
|
|
|
|
if (progressStep && prevProps.progressStep?.step !== progressStep?.step) {
|
|
animation.progressStep(progressStep.step);
|
|
}
|
|
|
|
if (
|
|
progressEnd &&
|
|
(prevProps.progressEnd?.playTo !== progressEnd?.playTo ||
|
|
prevProps.progressEnd?.step !== progressEnd?.step ||
|
|
prevProps?.dur !== progressEnd?.dur)
|
|
) {
|
|
animation.progressEnd(progressEnd.playTo, progressEnd.step, progressEnd.dur);
|
|
}
|
|
};
|
|
|
|
const checkPlayback = (animation: Animation, currentProps: any = {}, prevProps: any = {}) => {
|
|
if (!prevProps.play && currentProps.play) {
|
|
animation.play();
|
|
}
|
|
|
|
if (!prevProps.pause && currentProps.pause) {
|
|
animation.pause();
|
|
}
|
|
|
|
if (!prevProps.stop && currentProps.stop) {
|
|
animation.stop();
|
|
}
|
|
|
|
if (!prevProps.destroy && currentProps.destroy) {
|
|
animation.destroy();
|
|
}
|
|
};
|