mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
refactor(transition): deduplicates animation builder
This commit is contained in:
4
core/src/components.d.ts
vendored
4
core/src/components.d.ts
vendored
@ -55,6 +55,7 @@ import {
|
|||||||
PopoverOptions,
|
PopoverOptions,
|
||||||
RangeInputChangeEvent,
|
RangeInputChangeEvent,
|
||||||
RouteID,
|
RouteID,
|
||||||
|
RouterOutletOptions,
|
||||||
RouteWrite,
|
RouteWrite,
|
||||||
SelectInputChangeEvent,
|
SelectInputChangeEvent,
|
||||||
SelectPopoverOption,
|
SelectPopoverOption,
|
||||||
@ -79,9 +80,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ViewController,
|
ViewController,
|
||||||
} from './components/nav/view-controller';
|
} from './components/nav/view-controller';
|
||||||
import {
|
|
||||||
RouterOutletOptions,
|
|
||||||
} from './components/router-outlet/route-outlet';
|
|
||||||
import {
|
import {
|
||||||
RouterDirection,
|
RouterDirection,
|
||||||
RouterEventDetail,
|
RouterEventDetail,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Animation, ComponentRef, FrameworkDelegate } from '../../interface';
|
import { Animation, AnimationBuilder, ComponentRef, FrameworkDelegate, Mode } from '../../interface';
|
||||||
import { ViewController } from './view-controller';
|
import { ViewController } from './view-controller';
|
||||||
|
|
||||||
export { Nav } from './nav';
|
export { Nav } from './nav';
|
||||||
@ -15,16 +15,20 @@ export interface NavResult {
|
|||||||
direction?: NavDirection;
|
direction?: NavDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NavOptions {
|
export interface RouterOutletOptions {
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
animation?: string;
|
animationBuilder?: AnimationBuilder;
|
||||||
direction?: NavDirection;
|
|
||||||
duration?: number;
|
duration?: number;
|
||||||
easing?: string;
|
easing?: string;
|
||||||
id?: string;
|
showGoBack?: boolean;
|
||||||
|
direction?: NavDirection;
|
||||||
|
deepWait?: boolean;
|
||||||
|
mode?: Mode;
|
||||||
keyboardClose?: boolean;
|
keyboardClose?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavOptions extends RouterOutletOptions {
|
||||||
progressAnimation?: boolean;
|
progressAnimation?: boolean;
|
||||||
ev?: any;
|
|
||||||
updateURL?: boolean;
|
updateURL?: boolean;
|
||||||
delegate?: FrameworkDelegate;
|
delegate?: FrameworkDelegate;
|
||||||
viewIsReady?: (enteringEl: HTMLElement) => Promise<any>;
|
viewIsReady?: (enteringEl: HTMLElement) => Promise<any>;
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
|
import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
|
||||||
import { Animation, ComponentProps, Config, FrameworkDelegate, GestureDetail, Mode, NavOutlet, QueueController, RouteID, RouteWrite, RouterDirection } from '../../interface';
|
import { Animation, ComponentProps, Config, FrameworkDelegate, GestureDetail, Mode, NavOutlet, QueueController, RouteID, RouteWrite, RouterDirection } from '../../interface';
|
||||||
import { NavComponent, NavDirection, NavOptions, NavResult, TransitionDoneFn, TransitionInstruction } from '../../interface';
|
import { NavComponent, NavOptions, NavResult, TransitionDoneFn, TransitionInstruction } from '../../interface';
|
||||||
import { assert } from '../../utils/helpers';
|
import { assert } from '../../utils/helpers';
|
||||||
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
|
import { TransitionOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
|
||||||
import { ViewController, ViewState, convertToViews, matches } from './view-controller';
|
import { ViewController, ViewState, convertToViews, matches } from './view-controller';
|
||||||
|
|
||||||
import iosTransitionAnimation from './animations/ios.transition';
|
|
||||||
import mdTransitionAnimation from './animations/md.transition';
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-nav',
|
tag: 'ion-nav',
|
||||||
})
|
})
|
||||||
export class Nav implements NavOutlet {
|
export class Nav implements NavOutlet {
|
||||||
|
|
||||||
private init = false;
|
|
||||||
private transInstr: TransitionInstruction[] = [];
|
private transInstr: TransitionInstruction[] = [];
|
||||||
private sbTrns?: Animation;
|
private sbTrns?: Animation;
|
||||||
private useRouter = false;
|
private useRouter = false;
|
||||||
@ -288,7 +283,6 @@ export class Nav implements NavOutlet {
|
|||||||
this.fireError('nav controller was destroyed', ti);
|
this.fireError('nav controller was destroyed', ti);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.init = true;
|
|
||||||
|
|
||||||
if (ti.done) {
|
if (ti.done) {
|
||||||
ti.done(
|
ti.done(
|
||||||
@ -557,30 +551,24 @@ export class Nav implements NavOutlet {
|
|||||||
// or if it is a portal (modal, actionsheet, etc.)
|
// or if it is a portal (modal, actionsheet, etc.)
|
||||||
const opts = ti.opts!;
|
const opts = ti.opts!;
|
||||||
|
|
||||||
const animationBuilder = this.getAnimationBuilder(opts);
|
const progressCallback = opts.progressAnimation
|
||||||
|
? (animation: Animation) => { this.sbTrns = animation; }
|
||||||
const progressAnimation = opts.progressAnimation
|
|
||||||
? (animation: Animation) => this.sbTrns = animation
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const enteringEl = enteringView.element!;
|
const enteringEl = enteringView.element!;
|
||||||
const leavingEl = leavingView && leavingView.element!;
|
const leavingEl = leavingView && leavingView.element!;
|
||||||
const animationOpts: AnimationOptions = {
|
const animationOpts: TransitionOptions = {
|
||||||
animationCtrl: this.animationCtrl,
|
mode: this.mode,
|
||||||
animationBuilder: animationBuilder,
|
animate: this.animated,
|
||||||
animation: undefined,
|
|
||||||
|
|
||||||
direction: opts.direction as NavDirection,
|
|
||||||
duration: opts.duration,
|
|
||||||
easing: opts.easing,
|
|
||||||
viewIsReady: opts.viewIsReady,
|
|
||||||
|
|
||||||
showGoBack: this.canGoBack(enteringView),
|
showGoBack: this.canGoBack(enteringView),
|
||||||
progressAnimation,
|
animationCtrl: this.animationCtrl,
|
||||||
|
progressCallback,
|
||||||
window: this.win,
|
window: this.win,
|
||||||
baseEl: this.el,
|
baseEl: this.el,
|
||||||
enteringEl,
|
enteringEl,
|
||||||
leavingEl
|
leavingEl,
|
||||||
|
|
||||||
|
...opts
|
||||||
};
|
};
|
||||||
const trns = await transition(animationOpts);
|
const trns = await transition(animationOpts);
|
||||||
return this.transitionFinish(trns, enteringView, leavingView, opts);
|
return this.transitionFinish(trns, enteringView, leavingView, opts);
|
||||||
@ -607,14 +595,6 @@ export class Nav implements NavOutlet {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAnimationBuilder(opts: NavOptions) {
|
|
||||||
if (opts.duration === 0 || opts.animate === false || !this.init || this.animated === false || this.views.length <= 1) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const mode = opts.animation || this.config.get('pageTransition', this.mode);
|
|
||||||
return mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private insertViewAt(view: ViewController, index: number) {
|
private insertViewAt(view: ViewController, index: number) {
|
||||||
const views = this.views;
|
const views = this.views;
|
||||||
const existingIndex = views.indexOf(view);
|
const existingIndex = views.indexOf(view);
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Method, Prop } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Method, Prop } from '@stencil/core';
|
||||||
import { AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate, Mode, NavDirection, NavOutlet, RouteID, RouteWrite } from '../../interface';
|
import { AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate, Mode, NavOutlet, RouteID, RouteWrite, RouterOutletOptions } from '../../interface';
|
||||||
import { transition } from '../../utils';
|
import { transition } from '../../utils';
|
||||||
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
|
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
|
||||||
|
|
||||||
import iosTransitionAnimation from '../nav/animations/ios.transition';
|
|
||||||
import mdTransitionAnimation from '../nav/animations/md.transition';
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-router-outlet'
|
tag: 'ion-router-outlet'
|
||||||
@ -76,18 +73,15 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
await transition({
|
await transition({
|
||||||
animationBuilder: this.getAnimationBuilder(opts),
|
mode: this.mode,
|
||||||
direction: opts.direction,
|
animate: this.animated,
|
||||||
duration: opts.duration,
|
|
||||||
easing: opts.easing,
|
|
||||||
deepWait: opts.deepWait,
|
|
||||||
|
|
||||||
animationCtrl: this.animationCtrl,
|
animationCtrl: this.animationCtrl,
|
||||||
showGoBack: opts.showGoBack,
|
|
||||||
window: this.win,
|
window: this.win,
|
||||||
enteringEl: enteringEl,
|
enteringEl: enteringEl,
|
||||||
leavingEl: leavingEl,
|
leavingEl: leavingEl,
|
||||||
baseEl: this.el,
|
baseEl: this.el,
|
||||||
|
|
||||||
|
...opts
|
||||||
});
|
});
|
||||||
this.isTransitioning = false;
|
this.isTransitioning = false;
|
||||||
|
|
||||||
@ -117,16 +111,6 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
} : undefined;
|
} : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAnimationBuilder(opts: RouterOutletOptions) {
|
|
||||||
if (opts.duration === 0 || this.animated === false) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const mode = opts.mode || this.config.get('pageTransition', this.mode);
|
|
||||||
return opts.animationBuilder
|
|
||||||
|| this.animationBuilder
|
|
||||||
|| mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
this.mode === 'ios' && <div class="nav-decor"/>,
|
this.mode === 'ios' && <div class="nav-decor"/>,
|
||||||
@ -134,13 +118,3 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouterOutletOptions {
|
|
||||||
animationBuilder?: AnimationBuilder;
|
|
||||||
duration?: number;
|
|
||||||
easing?: string;
|
|
||||||
showGoBack?: boolean;
|
|
||||||
direction?: NavDirection;
|
|
||||||
deepWait?: boolean;
|
|
||||||
mode?: 'md' | 'ios';
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Animation } from '../../../interface';
|
import { Animation } from '../../interface';
|
||||||
import { AnimationOptions } from '../../../utils/transition';
|
import { TransitionOptions } from '../../utils/transition';
|
||||||
|
|
||||||
const DURATION = 500;
|
const DURATION = 500;
|
||||||
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
|
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
|
||||||
@ -9,7 +9,7 @@ const TRANSLATEX = 'translateX';
|
|||||||
const CENTER = '0%';
|
const CENTER = '0%';
|
||||||
const OFF_OPACITY = 0.8;
|
const OFF_OPACITY = 0.8;
|
||||||
|
|
||||||
export default function iosTransitionAnimation(Animation: Animation, navEl: HTMLElement, opts: AnimationOptions): Promise<Animation> {
|
export default function iosTransitionAnimation(Animation: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> {
|
||||||
|
|
||||||
const isRTL = document.dir === 'rtl';
|
const isRTL = document.dir === 'rtl';
|
||||||
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
|
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
|
@ -1,11 +1,11 @@
|
|||||||
import { Animation } from '../../../interface';
|
import { Animation } from '../../interface';
|
||||||
import { AnimationOptions } from '../../../utils/transition';
|
import { TransitionOptions } from '../../utils/transition';
|
||||||
|
|
||||||
const TRANSLATEY = 'translateY';
|
const TRANSLATEY = 'translateY';
|
||||||
const OFF_BOTTOM = '40px';
|
const OFF_BOTTOM = '40px';
|
||||||
const CENTER = '0px';
|
const CENTER = '0px';
|
||||||
|
|
||||||
export default function mdTransitionAnimation(Animation: Animation, _: HTMLElement, opts: AnimationOptions): Promise<Animation> {
|
export default function mdTransitionAnimation(Animation: Animation, _: HTMLElement, opts: TransitionOptions): Promise<Animation> {
|
||||||
|
|
||||||
const enteringEl = opts.enteringEl;
|
const enteringEl = opts.enteringEl;
|
||||||
const leavingEl = opts.leavingEl;
|
const leavingEl = opts.leavingEl;
|
@ -1,15 +1,29 @@
|
|||||||
import { Animation, AnimationBuilder, NavDirection } from '../interface';
|
import { Animation, AnimationBuilder, NavDirection, NavOptions } from '../interface';
|
||||||
|
|
||||||
|
import iosTransitionAnimation from './animations/ios.transition';
|
||||||
|
import mdTransitionAnimation from './animations/md.transition';
|
||||||
|
|
||||||
export function transition(opts: AnimationOptions): Promise<Animation|null> {
|
export function transition(opts: TransitionOptions): Promise<Animation|null> {
|
||||||
beforeTransition(opts);
|
beforeTransition(opts);
|
||||||
|
|
||||||
return (opts.leavingEl && (opts.animationBuilder || opts.animation))
|
const animationBuilder = getAnimationBuilder(opts);
|
||||||
? animation(opts)
|
return (animationBuilder)
|
||||||
|
? animation(animationBuilder, opts)
|
||||||
: noAnimation(opts); // fast path for no animation
|
: noAnimation(opts); // fast path for no animation
|
||||||
}
|
}
|
||||||
|
|
||||||
function beforeTransition(opts: AnimationOptions) {
|
function getAnimationBuilder(opts: TransitionOptions): AnimationBuilder | undefined {
|
||||||
|
if (!opts.leavingEl || opts.animate === false || opts.duration === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (opts.animationBuilder) {
|
||||||
|
return opts.animationBuilder;
|
||||||
|
}
|
||||||
|
return opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeTransition(opts: TransitionOptions) {
|
||||||
const enteringEl = opts.enteringEl;
|
const enteringEl = opts.enteringEl;
|
||||||
const leavingEl = opts.leavingEl;
|
const leavingEl = opts.leavingEl;
|
||||||
|
|
||||||
@ -26,10 +40,10 @@ function beforeTransition(opts: AnimationOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function animation(opts: AnimationOptions): Promise<Animation> {
|
async function animation(animationBuilder: AnimationBuilder, opts: TransitionOptions): Promise<Animation> {
|
||||||
await waitForReady(opts, true);
|
await waitForReady(opts, true);
|
||||||
|
|
||||||
const trns = await createTransition(opts);
|
const trns = await opts.animationCtrl.create(animationBuilder, opts.baseEl, opts);
|
||||||
fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
||||||
await playTransition(trns, opts);
|
await playTransition(trns, opts);
|
||||||
|
|
||||||
@ -39,7 +53,7 @@ async function animation(opts: AnimationOptions): Promise<Animation> {
|
|||||||
return trns;
|
return trns;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function noAnimation(opts: AnimationOptions): Promise<null> {
|
async function noAnimation(opts: TransitionOptions): Promise<null> {
|
||||||
const enteringEl = opts.enteringEl;
|
const enteringEl = opts.enteringEl;
|
||||||
const leavingEl = opts.leavingEl;
|
const leavingEl = opts.leavingEl;
|
||||||
if (enteringEl) {
|
if (enteringEl) {
|
||||||
@ -55,7 +69,7 @@ async function noAnimation(opts: AnimationOptions): Promise<null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForReady(opts: AnimationOptions, defaultDeep: boolean) {
|
async function waitForReady(opts: TransitionOptions, defaultDeep: boolean) {
|
||||||
const deep = opts.deepWait != null ? opts.deepWait : defaultDeep;
|
const deep = opts.deepWait != null ? opts.deepWait : defaultDeep;
|
||||||
const promises = deep ? [
|
const promises = deep ? [
|
||||||
deepReady(opts.enteringEl),
|
deepReady(opts.enteringEl),
|
||||||
@ -75,23 +89,17 @@ async function notifyViewReady(viewIsReady: undefined | ((enteringEl: HTMLElemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTransition(opts: AnimationOptions) {
|
|
||||||
if (opts.animation) {
|
|
||||||
return opts.animation;
|
|
||||||
}
|
|
||||||
return opts.animationCtrl.create(opts.animationBuilder, opts.baseEl, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
function playTransition(transition: Animation, opts: AnimationOptions): Promise<Animation> {
|
function playTransition(transition: Animation, opts: TransitionOptions): Promise<Animation> {
|
||||||
const progressAnimation = opts.progressAnimation;
|
const progressCallback = opts.progressCallback;
|
||||||
const promise = new Promise<Animation>(resolve => transition.onFinish(resolve));
|
const promise = new Promise<Animation>(resolve => transition.onFinish(resolve));
|
||||||
|
|
||||||
// cool, let's do this, start the transition
|
// cool, let's do this, start the transition
|
||||||
if (progressAnimation) {
|
if (progressCallback) {
|
||||||
// this is a swipe to go back, just get the transition progress ready
|
// this is a swipe to go back, just get the transition progress ready
|
||||||
// kick off the swipe animation start
|
// kick off the swipe animation start
|
||||||
transition.progressStart();
|
transition.progressStart();
|
||||||
progressAnimation(transition);
|
progressCallback(transition);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// only the top level transition should actually start "play"
|
// only the top level transition should actually start "play"
|
||||||
@ -176,19 +184,11 @@ export const enum ViewLifecycle {
|
|||||||
WillUnload = 'ionViewWillUnload',
|
WillUnload = 'ionViewWillUnload',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnimationOptions {
|
export interface TransitionOptions extends NavOptions {
|
||||||
animationCtrl: HTMLIonAnimationControllerElement;
|
animationCtrl: HTMLIonAnimationControllerElement;
|
||||||
animationBuilder: AnimationBuilder|undefined;
|
progressCallback?: ((ani: Animation) => void);
|
||||||
animation?: Animation;
|
|
||||||
direction?: NavDirection;
|
|
||||||
duration?: number;
|
|
||||||
easing?: string;
|
|
||||||
deepWait?: boolean;
|
|
||||||
viewIsReady?: (enteringEl: HTMLElement) => Promise<any>;
|
|
||||||
showGoBack?: boolean;
|
|
||||||
progressAnimation?: Function;
|
|
||||||
window: Window;
|
window: Window;
|
||||||
|
baseEl: HTMLElement;
|
||||||
enteringEl: HTMLElement;
|
enteringEl: HTMLElement;
|
||||||
leavingEl: HTMLElement|undefined;
|
leavingEl: HTMLElement|undefined;
|
||||||
baseEl: HTMLElement;
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user