mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
improve iOS transitions/reduce dom updates
This commit is contained in:
@@ -13,7 +13,7 @@ ion-navbar.toolbar {
|
||||
}
|
||||
|
||||
.back-button {
|
||||
order: map-get($toolbar-order, backButton);;
|
||||
order: map-get($toolbar-order, backButton);
|
||||
transform: translateZ(0px);
|
||||
|
||||
display: none;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {ViewController} from './view-controller';
|
||||
import {Transition} from '../../transitions/transition';
|
||||
import {SwipeBackGesture} from './swipe-back';
|
||||
import * as util from 'ionic/util';
|
||||
import {raf} from '../../util/dom';
|
||||
|
||||
/**
|
||||
* _For examples on the basic usage of NavController, check out the [Navigation section](../../../../components/#navigation)
|
||||
@@ -350,11 +351,11 @@ export class NavController extends Ion {
|
||||
let viewToRemove = this._views[index];
|
||||
if (this.isActive(viewToRemove)){
|
||||
return this.pop();
|
||||
} else {
|
||||
this._remove(index);
|
||||
viewToRemove.destroy();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
viewToRemove.shouldDestroy = true;
|
||||
this._cleanup();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -677,7 +678,7 @@ export class NavController extends Ion {
|
||||
* @private
|
||||
* TODO
|
||||
*/
|
||||
_runSwipeBack() {
|
||||
_sbComplete() {
|
||||
if (this.canSwipeBack()) {
|
||||
// it is possible to swipe back
|
||||
|
||||
@@ -744,12 +745,10 @@ export class NavController extends Ion {
|
||||
* @private
|
||||
*/
|
||||
_transComplete() {
|
||||
let destroys = [];
|
||||
|
||||
this._views.forEach(view => {
|
||||
if (view) {
|
||||
if (view.shouldDestroy) {
|
||||
destroys.push(view);
|
||||
view.didUnload();
|
||||
|
||||
} else if (view.state === CACHED_STATE && view.shouldCache) {
|
||||
view.shouldCache = false;
|
||||
@@ -757,100 +756,44 @@ export class NavController extends Ion {
|
||||
}
|
||||
});
|
||||
|
||||
destroys.forEach(view => {
|
||||
this._remove(view);
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
// allow clicks again, but still set an enable time
|
||||
// meaning nothing with this view controller can happen for XXms
|
||||
this.app.setEnabled(true);
|
||||
this.app.setTransitioning(false);
|
||||
|
||||
if (this._views.length === 1) {
|
||||
this.elementRef.nativeElement.classList.add('has-views');
|
||||
}
|
||||
this._sbComplete();
|
||||
|
||||
this._runSwipeBack();
|
||||
raf(() => {
|
||||
this._cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getActive() {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === ACTIVE_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
_cleanup() {
|
||||
// the active view, and the previous view, should be rendered in dom and ready to go
|
||||
// all others, like a cached page 2 back, should be display: none and not rendered
|
||||
let destroys = [];
|
||||
let activeView = this.getActive();
|
||||
let previousView = this.getPrevious(activeView);
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} instance TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getByInstance(instance) {
|
||||
if (instance) {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].instance === instance) {
|
||||
return this._views[i];
|
||||
this._views.forEach(view => {
|
||||
if (view) {
|
||||
if (view.shouldDestroy) {
|
||||
destroys.push(view);
|
||||
|
||||
} else {
|
||||
let isActiveView = (view === activeView);
|
||||
let isPreviousView = (view === previousView);
|
||||
view.domCache(isActiveView, isPreviousView);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} index TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getByIndex(index) {
|
||||
if (index < this._views.length && index > -1) {
|
||||
return this._views[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} view TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getPrevious(view) {
|
||||
if (view) {
|
||||
return this._views[ this._views.indexOf(view) - 1 ];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getStagedEnteringView() {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === STAGED_ENTERING_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getStagedLeavingView() {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === STAGED_LEAVING_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
// all views being destroyed should be removed from the list of views
|
||||
// and completely removed from the dom
|
||||
destroys.forEach(view => {
|
||||
this._remove(view);
|
||||
view.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -906,6 +849,91 @@ export class NavController extends Ion {
|
||||
util.array.remove(this._views, viewOrIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getActive() {
|
||||
for (let i = this._views.length - 1; i >= 0; i--) {
|
||||
if (this._views[i].state === ACTIVE_STATE && !this._views[i].shouldDestroy) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} instance TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getByInstance(instance) {
|
||||
if (instance) {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].instance === instance) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} index TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getByIndex(index) {
|
||||
if (index < this._views.length && index > -1) {
|
||||
return this._views[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param {TODO} view TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getPrevious(view) {
|
||||
if (view) {
|
||||
let viewIndex = this._views.indexOf(view);
|
||||
|
||||
for (let i = viewIndex - 1; i >= 0; i--) {
|
||||
if (!this._views[i].shouldDestroy) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getStagedEnteringView() {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === STAGED_ENTERING_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
getStagedLeavingView() {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === STAGED_LEAVING_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* First view in this nav controller's stack. This would
|
||||
* not return an view which is about to be destroyed.
|
||||
|
||||
@@ -130,6 +130,9 @@ import {NavController} from './nav-controller';
|
||||
defaultInputs: {
|
||||
'swipeBackEnabled': true
|
||||
},
|
||||
host: {
|
||||
'[class.has-views]': '_views.length > 0'
|
||||
},
|
||||
template: '<template pane-anchor></template>',
|
||||
directives: [forwardRef(() => NavPaneAnchor)]
|
||||
})
|
||||
|
||||
@@ -71,22 +71,31 @@ export function run() {
|
||||
expect(active).toBe(null);
|
||||
});
|
||||
|
||||
it('should return the first active page', () => {
|
||||
it('should return the last active page', () => {
|
||||
let activeView = new ViewController();
|
||||
activeView.state = 1; // ACTIVE_STATE
|
||||
|
||||
nav._add(activeView);
|
||||
var active = nav.getActive();
|
||||
|
||||
expect(active).toBe(activeView);
|
||||
expect(nav.getActive()).toBe(activeView);
|
||||
|
||||
let secondActiveView = new ViewController();
|
||||
secondActiveView.state = 1; // ACTIVE_STATE
|
||||
|
||||
nav._add(secondActiveView);
|
||||
active = nav.getActive();
|
||||
|
||||
expect(active).toBe(activeView);
|
||||
expect(nav.getActive()).toBe(secondActiveView);
|
||||
});
|
||||
|
||||
it('should return the last active page thats not shouldDestroy', () => {
|
||||
let view1 = new ViewController();
|
||||
view1.state = 1; // ACTIVE_STATE
|
||||
nav._add(view1);
|
||||
expect(nav.getActive()).toBe(view1);
|
||||
|
||||
let view2 = new ViewController();
|
||||
view2.state = 1; // ACTIVE_STATE
|
||||
view2.shouldDestroy = true;
|
||||
nav._add(view2);
|
||||
expect(nav.getActive()).toBe(view1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -94,8 +94,6 @@ export class ViewController {
|
||||
for (let i = 0; i < this.disposals.length; i++) {
|
||||
this.disposals[i]();
|
||||
}
|
||||
|
||||
this.didUnload();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +138,14 @@ export class ViewController {
|
||||
return this._nbVw;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
*/
|
||||
hasNavbar() {
|
||||
return !!this.getNavbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @returns {TODO} TODO
|
||||
@@ -256,4 +262,20 @@ export class ViewController {
|
||||
this.instance && this.instance.onPageDidUnload && this.instance.onPageDidUnload();
|
||||
}
|
||||
|
||||
domCache(isActiveView, isPreviousView) {
|
||||
let renderInDom = (isActiveView || isPreviousView);
|
||||
|
||||
let contentRef = this.contentRef();
|
||||
if (contentRef) {
|
||||
// the active view, and the previous view should have the 'show-view' css class
|
||||
// all others, like a cached page 2 back, should now have 'show-view' so it's not rendered
|
||||
contentRef.nativeElement.classList[renderInDom ? 'add' : 'remove' ]('show-view');
|
||||
}
|
||||
|
||||
let navbarRef = this.getNavbar();
|
||||
if (navbarRef) {
|
||||
navbarRef.elementRef.nativeElement.classList[renderInDom ? 'add' : 'remove' ]('show-navbar');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,156 +3,158 @@ import {Animation} from '../animations/animation';
|
||||
|
||||
const DURATION = 550;
|
||||
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
|
||||
|
||||
const OPACITY = 'opacity';
|
||||
const TRANSLATEX = 'translateX';
|
||||
|
||||
const OFF_RIGHT = '99.5%';
|
||||
const OFF_LEFT = '-33%';
|
||||
const CENTER = '0%'
|
||||
const OFF_OPACITY = 0.8;
|
||||
const SHOW_NAVBAR_CSS = 'show-navbar';
|
||||
const SHOW_VIEW_CSS = 'show-view';
|
||||
const SHOW_BACK_BTN_CSS = 'show-back-button';
|
||||
|
||||
|
||||
class IOSTransition extends Transition {
|
||||
class IOSTransition extends Animation {
|
||||
|
||||
constructor(navCtrl, opts) {
|
||||
super(navCtrl, opts, IOSEnteringTransition, IOSLeavingTransition);
|
||||
super(null, opts);
|
||||
|
||||
this.duration(DURATION);
|
||||
this.easing(EASING);
|
||||
}
|
||||
|
||||
}
|
||||
// what direction is the transition going
|
||||
let backDirection = (opts.direction === 'back');
|
||||
|
||||
class IOSEnteringTransition extends ViewTransition {
|
||||
// get entering/leaving views
|
||||
let enteringView = navCtrl.getStagedEnteringView();
|
||||
let leavingView = navCtrl.getStagedLeavingView();
|
||||
|
||||
constructor(navCtrl, viewCtrl, opts) {
|
||||
super(navCtrl, viewCtrl, opts);
|
||||
// do they have navbars?
|
||||
let enteringHasNavbar = enteringView.hasNavbar();
|
||||
let leavingHasNavbar = leavingView && leavingView.hasNavbar();
|
||||
|
||||
this.content
|
||||
.to(TRANSLATEX, CENTER)
|
||||
.to(OPACITY, 1)
|
||||
.before.setStyles({ zIndex: viewCtrl.index });
|
||||
|
||||
this.navbar.before.addClass('show-navbar');
|
||||
// entering content
|
||||
let enteringContent = new Animation(enteringView.contentRef());
|
||||
enteringContent
|
||||
.before.addClass(SHOW_VIEW_CSS)
|
||||
.before.setStyles({ zIndex: enteringView.index });
|
||||
this.add(enteringContent);
|
||||
|
||||
this.title
|
||||
.fadeIn()
|
||||
.to(TRANSLATEX, CENTER);
|
||||
|
||||
this.navbarBg
|
||||
.to(TRANSLATEX, CENTER);
|
||||
|
||||
this.navbarItems
|
||||
.fadeIn();
|
||||
|
||||
if (viewCtrl.enableBack()) {
|
||||
this.backButton
|
||||
.before.addClass('show-back-button')
|
||||
.fadeIn();
|
||||
}
|
||||
|
||||
// set properties depending on direction
|
||||
if (opts.direction === 'back') {
|
||||
if (backDirection) {
|
||||
// back direction
|
||||
this.content
|
||||
.from(TRANSLATEX, OFF_LEFT)
|
||||
.from(OPACITY, OFF_OPACITY)
|
||||
.to(OPACITY, 1);
|
||||
|
||||
this.title
|
||||
.from(TRANSLATEX, OFF_LEFT);
|
||||
|
||||
this.navbarBg
|
||||
.from(TRANSLATEX, OFF_LEFT);
|
||||
enteringContent
|
||||
.fromTo(TRANSLATEX, OFF_LEFT, CENTER)
|
||||
.fromTo(OPACITY, OFF_OPACITY, 1);
|
||||
|
||||
} else {
|
||||
// forward direction
|
||||
this.content
|
||||
.from(TRANSLATEX, '99.5%')
|
||||
.from(OPACITY, 1);
|
||||
enteringContent
|
||||
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER)
|
||||
.fromTo(OPACITY, 1, 1);
|
||||
}
|
||||
|
||||
this.title
|
||||
.from(TRANSLATEX, '99.5%');
|
||||
|
||||
this.navbarBg
|
||||
.from(TRANSLATEX, '99.5%')
|
||||
.fadeIn();
|
||||
// entering navbar
|
||||
if (enteringHasNavbar) {
|
||||
let enteringNavBar = new Animation(enteringView.navbarRef());
|
||||
enteringNavBar.before.addClass(SHOW_NAVBAR_CSS);
|
||||
this.add(enteringNavBar);
|
||||
|
||||
if (viewCtrl.enableBack()) {
|
||||
let backBtnText = new Animation(viewCtrl.backBtnTextRef());
|
||||
backBtnText.fromTo(TRANSLATEX, (300) + 'px', CENTER);
|
||||
this.navbar.add(backBtnText);
|
||||
let enteringTitle = new Animation(enteringView.titleRef());
|
||||
let enteringNavbarItems = new Animation(enteringView.navbarItemRefs());
|
||||
let enteringNavbarBg = new Animation(enteringView.navbarBgRef());
|
||||
let enteringBackButton = new Animation(enteringView.backBtnRef());
|
||||
enteringNavBar
|
||||
.add(enteringTitle)
|
||||
.add(enteringNavbarItems)
|
||||
.add(enteringNavbarBg)
|
||||
.add(enteringBackButton);
|
||||
|
||||
enteringTitle.fadeIn();
|
||||
enteringNavbarItems.fadeIn();
|
||||
|
||||
// set properties depending on direction
|
||||
if (backDirection) {
|
||||
// back direction
|
||||
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER);
|
||||
enteringNavbarBg.fromTo(TRANSLATEX, OFF_LEFT, CENTER);
|
||||
|
||||
} else {
|
||||
// forward direction
|
||||
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER);
|
||||
enteringNavbarBg.fromTo(TRANSLATEX, OFF_RIGHT, CENTER);
|
||||
|
||||
if (enteringView.enableBack()) {
|
||||
enteringBackButton.before.addClass(SHOW_BACK_BTN_CSS);
|
||||
enteringBackButton.fadeIn();
|
||||
|
||||
let enteringBackBtnText = new Animation(enteringView.backBtnTextRef());
|
||||
enteringBackBtnText.fromTo(TRANSLATEX, '150px', '0px');
|
||||
enteringNavBar.add(enteringBackBtnText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// setup leaving view
|
||||
if (leavingView) {
|
||||
// leaving content
|
||||
let leavingContent = new Animation(leavingView.contentRef());
|
||||
this.add(leavingContent);
|
||||
leavingContent
|
||||
.before.addClass(SHOW_VIEW_CSS)
|
||||
.before.setStyles({ zIndex: leavingView.index })
|
||||
|
||||
class IOSLeavingTransition extends ViewTransition {
|
||||
if (backDirection) {
|
||||
leavingContent
|
||||
.fromTo(TRANSLATEX, CENTER, '100%')
|
||||
.fromTo(OPACITY, 1, 1);
|
||||
|
||||
constructor(navCtrl, viewCtrl, opts) {
|
||||
super(navCtrl, viewCtrl, opts);
|
||||
} else {
|
||||
leavingContent
|
||||
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
|
||||
.fromTo(OPACITY, 1, OFF_OPACITY);
|
||||
}
|
||||
|
||||
// leaving viewCtrl moves off screen
|
||||
this.content
|
||||
.from(TRANSLATEX, CENTER)
|
||||
.from(OPACITY, 1)
|
||||
.before.setStyles({ zIndex: viewCtrl.index })
|
||||
.after.removeClass('show-view');
|
||||
if (leavingHasNavbar) {
|
||||
let leavingNavBar = new Animation(leavingView.navbarRef());
|
||||
let leavingBackButton = new Animation(leavingView.backBtnRef());
|
||||
let leavingTitle = new Animation(leavingView.titleRef());
|
||||
let leavingNavbarItems = new Animation(leavingView.navbarItemRefs());
|
||||
let leavingNavbarBg = new Animation(leavingView.navbarBgRef());
|
||||
|
||||
this.title
|
||||
.from(TRANSLATEX, CENTER)
|
||||
.from(OPACITY, 1);
|
||||
leavingNavBar
|
||||
.add(leavingBackButton)
|
||||
.add(leavingTitle)
|
||||
.add(leavingNavbarItems)
|
||||
.add(leavingNavbarBg);
|
||||
this.add(leavingNavBar);
|
||||
|
||||
this.navbarBg
|
||||
.from(TRANSLATEX, CENTER);
|
||||
leavingBackButton
|
||||
.after.removeClass(SHOW_BACK_BTN_CSS)
|
||||
.fadeOut();
|
||||
|
||||
this.navbarItems
|
||||
.fadeOut();
|
||||
leavingTitle.fadeOut();
|
||||
leavingNavbarItems.fadeOut();
|
||||
|
||||
this.backButton
|
||||
.after.removeClass('show-back-button')
|
||||
.fadeOut();
|
||||
// set properties depending on direction
|
||||
if (backDirection) {
|
||||
// back direction
|
||||
leavingTitle.fromTo(TRANSLATEX, CENTER, '100%');
|
||||
leavingNavbarBg.fromTo(TRANSLATEX, CENTER, '100%');
|
||||
|
||||
this.navbar.after.removeClass('show-navbar');
|
||||
let leavingBackBtnText = new Animation(leavingView.backBtnTextRef());
|
||||
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (300) + 'px');
|
||||
leavingNavBar.add(leavingBackBtnText);
|
||||
|
||||
// set properties depending on direction
|
||||
if (opts.direction === 'back') {
|
||||
} else {
|
||||
// forward direction
|
||||
leavingTitle.fromTo(TRANSLATEX, CENTER, OFF_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
// back direction
|
||||
this.content
|
||||
.to(TRANSLATEX, '100%')
|
||||
.to(OPACITY, 1);
|
||||
|
||||
this.title
|
||||
.to(TRANSLATEX, '100%')
|
||||
.to(OPACITY, 0);
|
||||
|
||||
this.navbarBg
|
||||
.to(TRANSLATEX, '100%')
|
||||
.fadeOut();
|
||||
|
||||
// if (this.leaving && this.leaving.enableBack() && this.viewWidth() > 200) {
|
||||
// let leavingBackButtonText = new Animation(this.leaving.backBtnTextRef());
|
||||
// leavingBackButtonText.fromTo(TRANSLATEX, CENTER, (this.viewWidth() / 2) + 'px');
|
||||
// this.leavingNavbar.add(leavingBackButtonText);
|
||||
// }
|
||||
|
||||
} else {
|
||||
// forward direction
|
||||
this.content
|
||||
.to(TRANSLATEX, OFF_LEFT)
|
||||
.to(OPACITY, OFF_OPACITY);
|
||||
|
||||
this.title
|
||||
.to(TRANSLATEX, OFF_LEFT)
|
||||
.to(OPACITY, 0);
|
||||
|
||||
this.navbarBg
|
||||
.to(TRANSLATEX, OFF_LEFT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user