From 5567b12b7cfe9e5f6d0b087df3b350f7d6b20a67 Mon Sep 17 00:00:00 2001 From: dillionmegida Date: Mon, 5 Oct 2020 20:30:23 +0100 Subject: [PATCH] docs(animations/grouped-animations): add React and Angular examples --- src/pages/utilities/animations.md | 242 +++++++++++++++--------------- 1 file changed, 122 insertions(+), 120 deletions(-) diff --git a/src/pages/utilities/animations.md b/src/pages/utilities/animations.md index 43752359e4e..997f4d9ad1d 100755 --- a/src/pages/utilities/animations.md +++ b/src/pages/utilities/animations.md @@ -53,7 +53,7 @@ const animation: Animation = createAnimation('') -Developers using Angular should install the latest version of `@ionic/angular`. Animations can be created via the `AnimationController` dependency injection. +Developers using Angular should install the latest version of `@ionic/angular`. Animations can be created via the `AnimationController` dependency injection. ```typescript @@ -83,8 +83,8 @@ import { CreateAnimation, Animation } from '@ionic/react'; @@ -162,9 +162,9 @@ createAnimation() .duration(3000) .iterations(Infinity) .keyframes([ - { offset: 0, background: 'red' }, - { offset: 0.72, background: 'var(--background)' }, - { offset: 1, background: 'green' } + { offset: 0, transform: "rotate(0deg) scale(1)" }, + { offset: 0.72, transform: "rotate(180deg) scale(2)" }, + { offset: 1, transform: "rotate(360deg) scale(1)" } ]); ``` @@ -176,9 +176,9 @@ this.animationCtrl.create() .duration(3000) .iterations(Infinity) .keyframes([ - { offset: 0, background: 'red' }, - { offset: 0.72, background: 'var(--background)' }, - { offset: 1, background: 'green' } + { offset: 0, transform: "rotate(0deg) scale(1)" }, + { offset: 0.72, transform: "rotate(180deg) scale(2)" }, + { offset: 1, transform: "rotate(360deg) scale(1)" } ]); ``` @@ -189,9 +189,9 @@ this.animationCtrl.create() duration={3000} iterations={Infinity} keyframes={[ - { offset: 0, background: 'red' }, - { offset: 0.72, background: 'var(--background)' }, - { offset: 1, background: 'green' } + { offset: 0, transform: "rotate(0deg) scale(1)" }, + { offset: 0.72, transform: "rotate(180deg) scale(2)" }, + { offset: 1, transform: "rotate(360deg) scale(1)" } ]} > ... @@ -200,6 +200,8 @@ this.animationCtrl.create() +You can view a live example of this in Angular [here](https://stackblitz.com/edit/ionic-angular-animation-keyframes) and in React [here](https://stackblitz.com/edit/ionic-react-animation-keyframes). + In the example above, the `.square` element will transition from a red background color, to a background color defined by the `--background` variable, and then transition on to a green background color. Each keyframe object contains an `offset` property. `offset` is a value between 0 and 1 that defines the keyframe step. Offset values must go in ascending order and cannot repeat. @@ -223,7 +225,7 @@ const squareA = createAnimation() { 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([ @@ -231,7 +233,7 @@ const squareB = createAnimation() { 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) @@ -252,97 +254,95 @@ const parent = createAnimation() ```javascript const squareA = this.animationCtrl.create() - .addElement(this.squareA.nativeElement) + .addElement(document.querySelector(".squareA")) .keyframes([ - { offset: 0, transform: 'scale(1) rotate(0)' }, - { offset: 0.5, transform: 'scale(1.2) rotate(45deg)' }, - { offset: 1, transform: 'scale(1) rotate(45deg)' } + { 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 = this.animationCtrl.create() - .addElement(this.squareB.nativeElement) + .addElement(document.querySelector(".squareB")) .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' } + { offset: 0, transform: "scale(1)" }, + { offset: 0.5, transform: "scale(1.2) translateX(20px)" }, + { offset: 1, transform: "scale(1) translateX(0)"} ]); - + const squareC = this.animationCtrl.create() - .addElement(this.squareC.nativeElement) + .addElement(document.querySelector(".squareC")) .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' } + { offset: 0, transform: "scale(1)" }, + { offset: 0.5, transform: "scale(0.8) translateY(40px)" }, + { offset: 1, transform: "scale(1) translateY(0)" } ]); -const parent = this.animationCtrl.create() +this.parent = this.animationCtrl.create() .duration(2000) .iterations(Infinity) - .addAnimation([squareA, squareB, squareC]); + .addAnimation([squareA, squareB, squareC]).play(); ``` ```typescript -private parentRef: React.RefObject = React.createRef(); -private squareARef: React.RefObject = React.createRef(); -private squareBRef: React.RefObject = React.createRef(); -private squareCRef: React.RefObject = React.createRef(); - -... - -componentDidMount() { - const parent = this.parentRef.current!.animation; - const squareA = this.squareARef.current!.animation; - const squareB = this.squareBRef.current!.animation; - const squareC = this.squareCRef.current!.animation; - +const parentRef = useRef(null); +const squareARef = useRef(null); +const squareBRef = useRef(null); +const squareCRef = useRef(null); + +useEffect(() => { + const parent = parentRef.current!.animation; + const squareA = squareARef.current!.animation; + const squareB = squareBRef.current!.animation; + const squareC = squareCRef.current!.animation; parent.addAnimation([squareA, squareB, squareC]); -} +}); render() { return ( <> - + /> + -
+
- + -
+
- + -
+
) @@ -352,6 +352,8 @@ render() { +You can view a live example of this in Angular [here](https://stackblitz.com/edit/ionic-angular-grouped-animations?file=src%2Fapp%2Fapp.component.ts) and in React [here](https://stackblitz.com/edit/ionic-react-grouped-animations?file=src/App.css). + This example shows 3 child animations controlled by a single parent animation. Animations `squareA` and `squareB` inherit the parent animation's duration of 2000ms, but animation `squareC` has a duration of 5000ms since it was explicitly set. @@ -452,7 +454,7 @@ const squareA = createAnimation() { 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') @@ -462,7 +464,7 @@ const squareB = createAnimation() { 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') @@ -490,7 +492,7 @@ const squareA = this.animationCtrl.create() { offset: 0.5, transform: 'scale(1.2) rotate(45deg)' }, { offset: 1, transform: 'scale(1) rotate(0)' } ]); - + const squareB = this.animationCtrl.create() .addElement(this.squareB.nativeElement) .fill('none') @@ -500,7 +502,7 @@ const squareB = this.animationCtrl.create() { offset: 0.5, transform: 'scale(1.2)', opacity: '0.3' }, { offset: 1, transform: 'scale(1)', opacity: '1' } ]); - + const squareC = this.animationCtrl.create() .addElement(this.squareC.nativeElement) .fill('none') @@ -529,7 +531,7 @@ async componentDidMount() { const squareA = this.squareARef.current!.animation; const squareB = this.squareBRef.current!.animation; const squareC = this.squareCRef.current!.animation; - + await squareA.play(); await squareB.play(); await squareC.play(); @@ -550,7 +552,7 @@ render() { >
- +
- + 0.5; animation .progressEnd((shouldComplete) ? 1 : 0, step) .onFinish((): { gesture.enable(true); }); - + initialStep = (shouldComplete) ? MAX_TRANSLATE : 0; started = false; } @@ -661,12 +663,12 @@ private started: boolean = false; private initialStep: number = 0; private MAX_TRANSLATE: number = 400; -ngOnInit() { +ngOnInit() { this.animation = this.animationCtrl.create() .addElement(this.square.nativeElement) .duration(1000) .fromTo('transform', 'translateX(0)', `translateX(${this.MAX_TRANSLATE}px)`); - + this.gesture = this.gestureCtrl.create({ el: this.square.nativeElement, threshold: 0, @@ -674,7 +676,7 @@ ngOnInit() { onMove: ev: this.onMove(ev), onEnd: ev: this.onEnd(ev) }) - + this.gesture.enable(true); } @@ -683,22 +685,22 @@ private onMove(ev) { this.animation.progressStart(); this.started = true; } - + this.animation.progressStep(this.getStep(ev)); } private onEnd(ev) { if (!this.started) { return; } - + this.gesture.enable(false); - + const step = this.getStep(ev); const shouldComplete = step > 0.5; this.animation .progressEnd((shouldComplete) ? 1 : 0, step) .onFinish((): { this.gesture.enable(true); }); - + this.initialStep = (shouldComplete) ? this.MAX_TRANSLATE : 0; this.started = false; } @@ -726,10 +728,10 @@ class MyComponent extends React.Component<{}, any> { private gesture?: Gesture; private started: boolean = false; private initialStep: number = 0; - + constructor(props: any) { super(props); - + this.state = { progressStart: undefined, progressStep: undefined, @@ -737,10 +739,10 @@ class MyComponent extends React.Component<{}, any> { onFinish: undefined }; } - + componentDidMount() { const square = Array.from(this.animation.current!.nodes.values())[0]; - + this.gesture = createGesture({ el: square, gestureName: 'square-drag', @@ -748,11 +750,11 @@ class MyComponent extends React.Component<{}, any> { onMove: ev => this.onMove(ev), onEnd: ev => this.onEnd(ev) }); - + this.gesture.enable(true); } - - private onMove(ev: GestureDetail) { + + private onMove(ev: GestureDetail) { if (!this.started) { this.setState({ ...this.state, @@ -760,21 +762,21 @@ class MyComponent extends React.Component<{}, any> { }); this.started = true; } - + this.setState({ ...this.state, progressStep: { step: this.getStep(ev) } }); } - + private onEnd(ev: GestureDetail) { if (!this.started) { return; } - + this.gesture!.enable(false); - + const step = this.getStep(ev); const shouldComplete = step > 0.5; - + this.setState({ ...this.state, progressEnd: { playTo: (shouldComplete) ? 1 : 0, step }, @@ -787,20 +789,20 @@ class MyComponent extends React.Component<{}, any> { }) }, opts: { oneTimeCallback: true }} }); - + this.initialStep = (shouldComplete) ? MAX_TRANSLATE : 0; this.started = false; } - + private getStep(ev: GestureDetail) { const delta = this.initialStep + ev.deltaX; return this.clamp(0, delta / MAX_TRANSLATE, 1); } - + private clamp(min: number, n: number, max: number) { return Math.max(min, Math.min(n, max)); } - + render() { return ( <> @@ -845,7 +847,7 @@ Developers can also tailor their animations to user preferences such as `prefers opacity: 0.5; background: blue; margin: 10px; - + --background: red; } @@ -934,25 +936,25 @@ function presentModal() { const backdropAnimation = createAnimation() .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', '0.01', 'var(--backdrop-opacity)'); - + const wrapperAnimation = createAnimation() .addElement(baseEl.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'); } - + // create the modal with the `modal-page` component const modalElement = document.createElement('ion-modal'); modalElement.component = 'modal-page'; @@ -986,25 +988,25 @@ export class ModalExample { const backdropAnimation = this.animationCtrl.create() .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', '0.01', 'var(--backdrop-opacity)'); - + const wrapperAnimation = this.animationCtrl.create() .addElement(baseEl.querySelector('.modal-wrapper')!) .keyframes([ { offset: 0, opacity: '0', transform: 'scale(0)' }, { offset: 1, opacity: '0.99', transform: 'scale(1)' } ]); - + return this.animationCtrl.create() .addElement(baseEl) .easing('ease-out') .duration(500) .addAnimation([backdropAnimation, wrapperAnimation]); } - + const leaveAnimation = (baseEl: any) => { return enterAnimation(baseEl).direction('reverse'); } - + const modal = await this.modalController.create({ component: ModalPage, enterAnimation, @@ -1023,26 +1025,26 @@ import { CreateAnimation, IonModal, IonButton, IonContent } from '@ionic/react'; export const ModalExample: React.FC = () => { const [showModal, setShowModal] = useState(false); - + const enterAnimation = (baseEl: any) => { const backdropAnimation = createAnimation() .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', '0.01', 'var(--backdrop-opacity)'); - + const wrapperAnimation = createAnimation() .addElement(baseEl.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'); } @@ -1094,12 +1096,12 @@ const animation = createAnimation('my-animation-identifier') | Browser/Platform | Supported Versions | | -------------------- | ------------------ | | **Chrome** | 43+ | -| **Safari** | 9+ | -| **Firefox** | 32+ | -| **IE/Edge** | 11+ | +| **Safari** | 9+ | +| **Firefox** | 32+ | +| **IE/Edge** | 11+ | | **Opera** | 30+ | | **iOS** | 9+ | -| **Android** | 5+ | +| **Android** | 5+ | > Due to a bug in Safari versions 9-11, stepping through animations via `progressStep` is not supported. This is supported on Safari 12+. @@ -1108,7 +1110,7 @@ const animation = createAnimation('my-animation-identifier') | Name | Value | | ---------------------| ------------------------------------------------------------- | | `AnimationDirection` | `'normal' \| 'reverse' \| 'alternate' \| 'alternate-reverse'` | -| `AnimationFill` | `'auto' \| 'none' \| 'forwards' \| 'backwards' \| 'both'` | +| `AnimationFill` | `'auto' \| 'none' \| 'forwards' \| 'backwards' \| 'both'` | ## Interfaces @@ -1122,10 +1124,10 @@ interface AnimationCallbackOptions { interface AnimationPlayOptions { /** - * If true, the animation will play synchronously. + * If true, the animation will play synchronously. * This is the equivalent of running the animation * with a duration of 0ms. - */ + */ sync: boolean; } ``` @@ -1135,7 +1137,7 @@ interface AnimationPlayOptions { | Name | Description | | ------------------------------------------| ------------------------------------------------- | | `childAnimations: Animation[]` | All child animations of a given parent animation. | -| `elements: HTMLElement[]` | All elements attached to an animation. | +| `elements: HTMLElement[]` | All elements attached to an animation. | | `parentAnimation?: Animation` | The parent animation of a given animation object. | ## Methods @@ -1143,7 +1145,7 @@ interface AnimationPlayOptions { | Name | Description | | ------------------------------------------| ------------------------------------------------- | | `addAnimation(animationToAdd: Animation \| Animation[]): Animation` | Group one or more animations together to be controlled by a parent animation. | -| `addElement(el: Element \| Element[] \| Node \| Node[] \| NodeList): Animation` | Add one or more elements to the animation. | +| `addElement(el: Element \| Element[] \| Node \| Node[] \| NodeList): Animation` | Add one or more elements to the animation. | | `afterAddClass(className: string \| string[]): Animation` | Add a class or array of classes to be added to all elements in an animation after the animation ends. | | `afterAddRead(readFn: (): void): Animation` | Add a function that performs a DOM read to be run after the animation ends. | | `afterAddWrite(writeFn: (): void): Animation` | Add a function that performs a DOM write to be run after the animation ends. |