mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 21:15:24 +08:00
fix(nav): flickering
This commit is contained in:
1
packages/core/src/components.d.ts
vendored
1
packages/core/src/components.d.ts
vendored
@ -3059,6 +3059,7 @@ declare global {
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonTabAttributes extends HTMLAttributes {
|
||||
active?: boolean;
|
||||
badge?: string;
|
||||
badgeStyle?: string;
|
||||
btnId?: string;
|
||||
|
@ -22,6 +22,7 @@ export interface Animation {
|
||||
beforeClearStyles(propertyNames: string[]): Animation;
|
||||
beforeAddRead(domReadFn: Function): Animation;
|
||||
beforeAddWrite(domWriteFn: Function): Animation;
|
||||
duringAddClass(className: string): Animation;
|
||||
afterAddClass(className: string): Animation;
|
||||
afterRemoveClass(className: string): Animation;
|
||||
afterStyles(styles: { [property: string]: any; }): Animation;
|
||||
|
@ -231,6 +231,15 @@ export class Animator {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a CSS class during the duration of the animation.
|
||||
*/
|
||||
duringAddClass(className: string): Animator {
|
||||
this.beforeAddClass(className);
|
||||
this.afterRemoveClass(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set CSS inline styles to this animation's elements
|
||||
* before the animation begins.
|
||||
|
@ -8,6 +8,3 @@ $content-ios-font-family: $font-family-ios-base !default;
|
||||
|
||||
/// @prop - Background color of the outer content
|
||||
$content-ios-outer-background: $background-ios-color-step-50 !default;
|
||||
|
||||
/// @prop - Background color of the content when making transition
|
||||
$content-ios-transition-background: #000 !default;
|
||||
|
@ -68,7 +68,7 @@ export function canNavGoBack(nav: Nav, view?: ViewController) {
|
||||
if (!nav) {
|
||||
return false;
|
||||
}
|
||||
return nav.getPrevious(view);
|
||||
return !!nav.getPrevious(view);
|
||||
}
|
||||
|
||||
export function transitionFactory(animation: Animation): Transition {
|
||||
@ -127,10 +127,18 @@ export function destroyTransition(transitionId: number) {
|
||||
}
|
||||
|
||||
export function getHydratedTransition(name: string, config: Config, transitionId: number, emptyTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions, defaultTransitionFactory: TransitionBuilder): Promise<Transition> {
|
||||
|
||||
// Let makes sure everything is hydrated and ready to animate
|
||||
const componentReadyPromise: Promise<any>[] = [];
|
||||
if (enteringView && (enteringView.element as any).componentOnReady) {
|
||||
componentReadyPromise.push((enteringView.element as any).componentOnReady());
|
||||
}
|
||||
if (leavingView && (leavingView.element as any).componentOnReady) {
|
||||
componentReadyPromise.push((leavingView.element as any).componentOnReady());
|
||||
}
|
||||
const transitionFactory = config.get(name) as TransitionBuilder || defaultTransitionFactory;
|
||||
|
||||
return transitionFactory(emptyTransition, enteringView, leavingView, opts).then((hydratedTransition) => {
|
||||
return Promise.all(componentReadyPromise)
|
||||
.then(() => transitionFactory(emptyTransition, enteringView, leavingView, opts))
|
||||
.then((hydratedTransition) => {
|
||||
hydratedTransition.transitionId = transitionId;
|
||||
if (!activeTransitions.has(transitionId)) {
|
||||
// sweet, this is the root transition
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Util
|
||||
// --------------------------------------------------
|
||||
@import "../../themes/util";
|
||||
|
||||
$navigation-ios-transition-background: #000 !default;
|
||||
|
||||
ion-nav {
|
||||
@include position(0);
|
||||
@ -13,10 +12,7 @@ ion-nav {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-color: #000;
|
||||
|
||||
contain: layout size style;
|
||||
|
||||
}
|
||||
|
||||
.ion-page {
|
||||
@ -31,3 +27,24 @@ ion-nav {
|
||||
|
||||
contain: layout size style;
|
||||
}
|
||||
|
||||
.nav-decor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show-decor > .nav-decor {
|
||||
@include position(0, null, null, 0);
|
||||
|
||||
// when ios pages transition, the leaving page grays out
|
||||
// this is the black square behind all pages so they gray out
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: $navigation-ios-transition-background;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -335,6 +335,9 @@ export class Nav implements PublicNav, NavOutlet {
|
||||
attachTo='body'
|
||||
></ion-gesture>);
|
||||
}
|
||||
if (this.mode === 'ios') {
|
||||
dom.push(<div class='nav-decor'/>);
|
||||
}
|
||||
dom.push(<slot></slot>);
|
||||
return dom;
|
||||
}
|
||||
@ -820,7 +823,8 @@ export function loadViewAndTransition(nav: Nav, enteringView: ViewController, le
|
||||
|
||||
|
||||
const emptyTransition = transitionFactory(ti.animation);
|
||||
return getHydratedTransition(animationOpts.animation, nav.config, nav.transitionId, emptyTransition, enteringView, leavingView, animationOpts, getDefaultTransition(nav.config)).then((transition) => {
|
||||
return getHydratedTransition(animationOpts.animation, nav.config, nav.transitionId, emptyTransition, enteringView, leavingView, animationOpts, getDefaultTransition(nav.config))
|
||||
.then((transition) => {
|
||||
|
||||
if (nav.sbTrns) {
|
||||
nav.sbTrns.destroy();
|
||||
@ -967,6 +971,7 @@ export function fireViewWillLifecycles(enteringView: ViewController, leavingView
|
||||
export function attachViewToDom(nav: Nav, enteringView: ViewController, ti: TransitionInstruction) {
|
||||
if (enteringView && enteringView.state === STATE_NEW) {
|
||||
return ti.delegate.attachViewToDom(nav.element, enteringView.component, enteringView.data, [], ti.escapeHatch).then((mountingData) => {
|
||||
mountingData.element.classList.add('nav-page');
|
||||
ti.mountingData = mountingData;
|
||||
Object.assign(enteringView, mountingData);
|
||||
enteringView.state = STATE_ATTACHED;
|
||||
|
@ -12,16 +12,6 @@ const OFF_OPACITY = 0.8;
|
||||
const SHOW_BACK_BTN_CSS = 'show-back-button';
|
||||
|
||||
export function buildIOSTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
|
||||
const componentReadyPromise: Promise<any>[] = [];
|
||||
// Let makes sure everything is hydrated and ready to animate
|
||||
if (enteringView && (enteringView.element as any).componentOnReady) {
|
||||
componentReadyPromise.push((enteringView.element as any).componentOnReady());
|
||||
}
|
||||
if (leavingView && (leavingView.element as any).componentOnReady) {
|
||||
componentReadyPromise.push((leavingView.element as any).componentOnReady());
|
||||
}
|
||||
|
||||
return Promise.all(componentReadyPromise).then(() => {
|
||||
// Cool we're all hydrated, and can do deep selector
|
||||
rootTransition.enteringView = enteringView;
|
||||
rootTransition.leavingView = leavingView;
|
||||
@ -37,8 +27,16 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
rootTransition.addElement(enteringView.element);
|
||||
rootTransition.beforeRemoveClass('hide-page');
|
||||
|
||||
const backDirection = (opts.direction === 'back');
|
||||
if (leavingView) {
|
||||
const navEl = leavingView.element.closest('ion-nav');
|
||||
if (navEl) {
|
||||
const navDecor = rootTransition.create();
|
||||
navDecor.addElement(navEl).duringAddClass('show-decor');
|
||||
rootTransition.add(navDecor);
|
||||
}
|
||||
}
|
||||
|
||||
const backDirection = (opts.direction === 'back');
|
||||
// setting up enter view
|
||||
if (enteringView) {
|
||||
|
||||
@ -201,8 +199,6 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the rootTransition promise
|
||||
return rootTransition;
|
||||
});
|
||||
return Promise.resolve(rootTransition);
|
||||
}
|
||||
|
@ -9,15 +9,6 @@ const SHOW_BACK_BTN_CSS = 'show-back-button';
|
||||
|
||||
export function buildMdTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
|
||||
|
||||
const componentReadyPromise: Promise<any>[] = [];
|
||||
if (enteringView && (enteringView.element as any).componentOnReady) {
|
||||
componentReadyPromise.push((enteringView.element as any).componentOnReady());
|
||||
}
|
||||
if (leavingView && (leavingView.element as any).componentOnReady) {
|
||||
componentReadyPromise.push((leavingView.element as any).componentOnReady());
|
||||
}
|
||||
|
||||
return Promise.all(componentReadyPromise).then(() => {
|
||||
rootTransition.enteringView = enteringView;
|
||||
rootTransition.leavingView = leavingView;
|
||||
|
||||
@ -68,10 +59,7 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
|
||||
rootTransition.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0));
|
||||
}
|
||||
|
||||
return rootTransition;
|
||||
|
||||
});
|
||||
|
||||
return Promise.resolve(rootTransition);
|
||||
}
|
||||
|
||||
function getIonPageElement(element: HTMLElement) {
|
||||
|
@ -32,7 +32,7 @@ export class Router {
|
||||
}
|
||||
|
||||
@Listen('window:popstate')
|
||||
protected onURLHashChanged() {
|
||||
protected onPopState() {
|
||||
if (window.history.state === null) {
|
||||
this.state++;
|
||||
window.history.replaceState(this.state, document.title, document.location.href);
|
||||
|
@ -50,6 +50,11 @@ export class Tabs {
|
||||
|
||||
## Properties
|
||||
|
||||
#### active
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
#### badge
|
||||
|
||||
string
|
||||
@ -131,6 +136,11 @@ The title of the tab button.
|
||||
|
||||
## Attributes
|
||||
|
||||
#### active
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
#### badge
|
||||
|
||||
string
|
||||
@ -222,7 +232,7 @@ Emitted when the current tab is selected.
|
||||
#### getRouteId()
|
||||
|
||||
|
||||
#### setActive()
|
||||
#### prepareActive()
|
||||
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ export class Tab {
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
@State() init = false;
|
||||
@State() active = false;
|
||||
@Prop() active = false;
|
||||
|
||||
/**
|
||||
* Set the root page for this tab.
|
||||
@ -79,11 +79,7 @@ export class Tab {
|
||||
@Event() ionSelect: EventEmitter<void>;
|
||||
|
||||
@Method()
|
||||
setActive(active: boolean): Promise<any> {
|
||||
this.active = active;
|
||||
if (!active) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
prepareActive(): Promise<any> {
|
||||
if (this.loaded) {
|
||||
return this.configChildgNav();
|
||||
}
|
||||
@ -140,13 +136,13 @@ export class Tab {
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const visible = this.active && this.selected;
|
||||
const hidden = !this.active || !this.selected;
|
||||
return {
|
||||
'aria-hidden': !visible,
|
||||
'aria-hidden': hidden,
|
||||
'aria-labelledby': this.btnId,
|
||||
'role': 'tabpanel',
|
||||
class: {
|
||||
'show-tab': visible
|
||||
'show-tab': this.active
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
||||
import { Config, NavEventDetail, NavOutlet } from '../../index';
|
||||
|
||||
import { ensureExternalRounterController } from '../../utils/helpers';
|
||||
import { asyncRaf, ensureExternalRounterController } from '../../utils/helpers';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -121,12 +121,14 @@ export class Tabs implements NavOutlet {
|
||||
const leavingTab = this.selectedTab;
|
||||
this.selectedTab = selectedTab;
|
||||
|
||||
let promise = selectedTab.setActive(true);
|
||||
if (leavingTab && leavingTab !== selectedTab) {
|
||||
promise = promise.then(() => leavingTab.setActive(false));
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
return selectedTab.prepareActive()
|
||||
.then(() => selectedTab.active = true)
|
||||
.then(() => asyncRaf())
|
||||
.then(() => {
|
||||
if (leavingTab) {
|
||||
leavingTab.active = false;
|
||||
}
|
||||
this.ionChange.emit(selectedTab);
|
||||
this.ionNavChanged.emit({isPop: false});
|
||||
});
|
||||
@ -209,7 +211,7 @@ export class Tabs implements NavOutlet {
|
||||
tab.selected = false;
|
||||
}
|
||||
}
|
||||
const promise = selectedTab ? selectedTab.setActive(true) : Promise.resolve();
|
||||
const promise = selectedTab ? selectedTab.prepareActive() : Promise.resolve();
|
||||
return promise.then(() => {
|
||||
this.selectedTab = selectedTab;
|
||||
if (selectedTab) {
|
||||
|
@ -295,6 +295,10 @@ export function debounce(func: Function, wait = 0) {
|
||||
};
|
||||
}
|
||||
|
||||
export function asyncRaf(): Promise<number> {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
export function getNavAsChildIfExists(element: HTMLElement): HTMLIonNavElement|null {
|
||||
for (let i = 0; i < element.children.length; i++) {
|
||||
if (element.children[i].tagName.toLowerCase() === 'ion-nav') {
|
||||
|
Reference in New Issue
Block a user