mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(modal): Improve slide-up/down animations, remove flickers
This commit is contained in:
77
js/ext/angular/src/service/ionicModal.js
vendored
77
js/ext/angular/src/service/ionicModal.js
vendored
@@ -10,63 +10,72 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ionic.serv
|
||||
// Show the modal
|
||||
show: function() {
|
||||
var self = this;
|
||||
var element = angular.element(this.el);
|
||||
var element = angular.element(self.el);
|
||||
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
self._isShown = true;
|
||||
|
||||
if(!element.parent().length) {
|
||||
element.addClass(this.animation);
|
||||
$animate.enter(element, angular.element($document[0].body), null, function() {
|
||||
});
|
||||
ionic.views.Modal.prototype.show.call(self);
|
||||
} else {
|
||||
$animate.addClass(element, this.animation, function() {
|
||||
});
|
||||
self.el.classList.add(self.animation);
|
||||
$document[0].body.appendChild(self.el);
|
||||
}
|
||||
|
||||
if(!this.didInitEvents) {
|
||||
var onHardwareBackButton = function() {
|
||||
self.hide();
|
||||
};
|
||||
element.addClass('ng-enter active');
|
||||
element.removeClass('ng-leave ng-leave-active');
|
||||
|
||||
self.scope.$on('$destroy', function() {
|
||||
$ionicPlatform.offHardwareBackButton(onHardwareBackButton);
|
||||
});
|
||||
$timeout(function(){
|
||||
element.addClass('ng-enter-active');
|
||||
|
||||
// Support Android back button to close
|
||||
$ionicPlatform.onHardwareBackButton(onHardwareBackButton);
|
||||
if(!self.didInitEvents) {
|
||||
var onHardwareBackButton = function() {
|
||||
self.hide();
|
||||
};
|
||||
|
||||
this.didInitEvents = true;
|
||||
}
|
||||
self.scope.$on('$destroy', function() {
|
||||
$ionicPlatform.offHardwareBackButton(onHardwareBackButton);
|
||||
});
|
||||
|
||||
this.scope.$parent.$broadcast('modal.shown', this);
|
||||
// Support Android back button to close
|
||||
$ionicPlatform.onHardwareBackButton(onHardwareBackButton);
|
||||
|
||||
self.didInitEvents = true;
|
||||
}
|
||||
|
||||
self.scope.$parent.$broadcast('modal.shown');
|
||||
}, 20);
|
||||
|
||||
},
|
||||
// Hide the modal
|
||||
hide: function() {
|
||||
this._isShown = false;
|
||||
var element = angular.element(this.el);
|
||||
$animate.removeClass(element, this.animation, function() {
|
||||
onHideModal(element[0]);
|
||||
});
|
||||
|
||||
element.addClass('ng-leave');
|
||||
|
||||
$timeout(function(){
|
||||
element.addClass('ng-leave-active');
|
||||
element.removeClass('ng-enter ng-enter-active active');
|
||||
}, 20);
|
||||
|
||||
$timeout(function(){
|
||||
document.body.classList.remove('modal-open');
|
||||
}, 400);
|
||||
|
||||
ionic.views.Modal.prototype.hide.call(this);
|
||||
|
||||
this.scope.$parent.$broadcast('modal.hidden', this);
|
||||
this.scope.$parent.$broadcast('modal.hidden');
|
||||
},
|
||||
|
||||
// Remove and destroy the modal scope
|
||||
remove: function() {
|
||||
var self = this,
|
||||
element = angular.element(this.el);
|
||||
this._isShown = false;
|
||||
$animate.leave(angular.element(this.el), function() {
|
||||
onHideModal(element[0]);
|
||||
self.scope.$parent.$broadcast('modal.removed', self);
|
||||
var self = this;
|
||||
self.hide();
|
||||
self.scope.$parent.$broadcast('modal.removed');
|
||||
|
||||
$timeout(function(){
|
||||
self.scope.$destroy();
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
|
||||
isShown: function() {
|
||||
@@ -74,10 +83,6 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ionic.serv
|
||||
}
|
||||
});
|
||||
|
||||
function onHideModal(element) {
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
|
||||
var createModal = function(templateString, options) {
|
||||
// Create a new scope for the modal
|
||||
var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
|
||||
@@ -116,6 +121,6 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ionic.serv
|
||||
cb ? cb(modal) : null;
|
||||
return modal;
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="stylesheet" href="../../../../dist/css/ionic.css">
|
||||
<script src="../../../../dist/js/ionic.bundle.js"></script>
|
||||
<script src="dom-trace.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ion-pane ng-controller="AppCtrl">
|
||||
@@ -23,7 +24,7 @@
|
||||
|
||||
<script id="modal.html" type="text/ng-template">
|
||||
<div class="modal" ng-controller="ModalCtrl">
|
||||
<header class="bar bar-header bar-positive">
|
||||
<header class="bar bar-header bar-assertive">
|
||||
<h1 class="title">New Contact</h1>
|
||||
<button class="button button-clear button-primary" ng-click="close()">Cancel</button>
|
||||
</header>
|
||||
@@ -42,8 +43,8 @@
|
||||
<span class="input-label">Email</span>
|
||||
<input type="text" placeholder="">
|
||||
</label>
|
||||
<button class="button button-full button-positive" ng-click="closeModal()">Create</button>
|
||||
<button class="button button-full button-positive" ng-click="openActionSheet()">ActionSheet</button>
|
||||
<button class="button button-block button-positive" ng-click="closeModal()">Create</button>
|
||||
<button class="button button-block button-positive" ng-click="openActionSheet()">ActionSheet</button>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -127,6 +128,8 @@
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
domTrace.observe();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -53,25 +53,21 @@ describe('Ionic Modal', function() {
|
||||
expect(m.isShown()).toBe(false);
|
||||
});
|
||||
|
||||
it('show & remove should add .model-open to body', inject(function($animate) {
|
||||
it('show & remove should add .model-open to body', inject(function() {
|
||||
var m = modal.fromTemplate('<div class="modal">hi</div>');
|
||||
m.show();
|
||||
expect(angular.element(document.body).hasClass('modal-open')).toBe(true);
|
||||
spyOn($animate, 'leave').andCallFake(function(el, cb) {
|
||||
cb();
|
||||
});
|
||||
m.remove();
|
||||
timeout.flush();
|
||||
expect(angular.element(document.body).hasClass('modal-open')).toBe(false);
|
||||
}));
|
||||
|
||||
it('show & hide should add .model-open body', inject(function($animate) {
|
||||
it('show & hide should add .model-open body', inject(function() {
|
||||
var m = modal.fromTemplate('<div class="modal">hi</div>');
|
||||
m.show();
|
||||
expect(angular.element(document.body).hasClass('modal-open')).toBe(true);
|
||||
spyOn($animate, 'removeClass').andCallFake(function(el, cls, cb) {
|
||||
cb();
|
||||
});
|
||||
m.hide();
|
||||
timeout.flush();
|
||||
expect(angular.element(document.body).hasClass('modal-open')).toBe(false);
|
||||
}));
|
||||
|
||||
@@ -80,7 +76,7 @@ describe('Ionic Modal', function() {
|
||||
spyOn($animate, 'leave').andCallFake(function(el, cb) { cb(); });
|
||||
spyOn(m.scope, '$destroy');
|
||||
m.remove();
|
||||
expect($animate.leave).toHaveBeenCalled();
|
||||
timeout.flush();
|
||||
expect(m.scope.$destroy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
@@ -89,7 +85,7 @@ describe('Ionic Modal', function() {
|
||||
var modalInstance = modal.fromTemplate(template);
|
||||
modalInstance.show();
|
||||
|
||||
//timeout.flush();
|
||||
timeout.flush();
|
||||
|
||||
expect(modalInstance.el.classList.contains('active')).toBe(true);
|
||||
|
||||
@@ -97,6 +93,7 @@ describe('Ionic Modal', function() {
|
||||
target: document
|
||||
});
|
||||
|
||||
timeout.flush();
|
||||
expect(modalInstance.el.classList.contains('active')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -105,14 +102,15 @@ describe('Ionic Modal', function() {
|
||||
var m = modal.fromTemplate(template, {});
|
||||
spyOn(m.scope.$parent, '$broadcast');
|
||||
m.show();
|
||||
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.shown', m);
|
||||
timeout.flush();
|
||||
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.shown');
|
||||
});
|
||||
it('should broadcast "modal.hidden" on hide', function() {
|
||||
var template = '<div class="modal"></div>';
|
||||
var m = modal.fromTemplate(template, {});
|
||||
spyOn(m.scope.$parent, '$broadcast');
|
||||
m.hide();
|
||||
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.hidden', m);
|
||||
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.hidden');
|
||||
});
|
||||
it('should broadcast "modal.removed" on remove', inject(function($animate) {
|
||||
var template = '<div class="modal"></div>';
|
||||
@@ -125,9 +123,9 @@ describe('Ionic Modal', function() {
|
||||
spyOn(m.scope.$parent, '$broadcast').andCallFake(function(e, modal) {
|
||||
broadcastedModal = modal;
|
||||
});
|
||||
spyOn($animate, 'leave').andCallFake(function(el, cb) { cb(); });
|
||||
|
||||
m.remove();
|
||||
expect(broadcastedModal).toBe(m);
|
||||
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.removed');
|
||||
timeout.flush();
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -16,19 +16,15 @@
|
||||
show: function() {
|
||||
var self = this;
|
||||
|
||||
this.el.classList.add('active');
|
||||
|
||||
if(this.focusFirstInput) {
|
||||
if(self.focusFirstInput) {
|
||||
// Let any animations run first
|
||||
window.setTimeout(function() {
|
||||
var input = self.el.querySelector('input, textarea');
|
||||
input && input.focus && input.focus();
|
||||
}, this.focusFirstDelay);
|
||||
}, self.focusFirstDelay);
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
this.el.classList.remove('active');
|
||||
|
||||
// Unfocus all elements
|
||||
if(this.unfocusOnHide) {
|
||||
var inputs = this.el.querySelectorAll('input, textarea');
|
||||
|
||||
@@ -17,7 +17,7 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
@-webkit-keyframes slideInUp {
|
||||
0% {
|
||||
@include translate3d(0, 100%, 0);
|
||||
opacity: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
@include translate3d(0, 0, 0);
|
||||
@@ -27,7 +27,7 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
@-moz-keyframes slideInUp {
|
||||
0% {
|
||||
@include translate3d(0, 100%, 0);
|
||||
opacity: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
@include translate3d(0, 0, 0);
|
||||
@@ -41,7 +41,7 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
}
|
||||
100% {
|
||||
@include translate3d(0, 100%, 0);
|
||||
opacity: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes slideOutUp {
|
||||
@@ -51,7 +51,7 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
}
|
||||
100% {
|
||||
@include translate3d(0, 100%, 0);
|
||||
opacity: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,14 +160,14 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
// Spin
|
||||
// -------------------------------
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
@-webkit-keyframes spin {
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
@-moz-keyframes spin {
|
||||
100% { -moz-transform: rotate(360deg); }
|
||||
@-moz-keyframes spin {
|
||||
100% { -moz-transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes spin {
|
||||
100% { transform: rotate(360deg); }
|
||||
@keyframes spin {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.no-animation {
|
||||
@@ -201,7 +201,7 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
*/
|
||||
|
||||
.slide-left-right {
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-leave, &.ng-leave {
|
||||
@include transition(all ease-in-out $transition-duration);
|
||||
position: absolute;
|
||||
@@ -249,14 +249,14 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
|
||||
|
||||
|
||||
/**
|
||||
* iOS7 style slide left to right
|
||||
* iOS7 style slide left to right
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
$ios7-timing-function: ease-in-out;
|
||||
$ios7-transition-duration: 250ms;
|
||||
|
||||
.slide-left-right-ios7 {
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-leave, &.ng-leave {
|
||||
@include transition(all $ios7-timing-function $ios7-transition-duration);
|
||||
position: absolute;
|
||||
@@ -280,7 +280,7 @@ $ios7-transition-duration: 250ms;
|
||||
/* OLD content ACTIVELY sliding OUT to the LEFT */
|
||||
@include translate3d(-15%, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
&.reverse {
|
||||
> .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave {
|
||||
@include transition(all $ios7-timing-function $ios7-transition-duration);
|
||||
@@ -420,54 +420,29 @@ $ios7-transition-duration: 250ms;
|
||||
@include animation-name(slideOutToRight);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Slide up from the bottom, used for modals
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
|
||||
.slide-in-up {
|
||||
// Start it down low
|
||||
@include translate3d(0, 100%, 0);
|
||||
}
|
||||
.slide-in-up.ng-enter,
|
||||
.slide-in-up .ng-enter {
|
||||
@include transition(all $slide-in-up-function 400ms);
|
||||
}
|
||||
.slide-in-up.ng-enter-active,
|
||||
.slide-in-up .ng-enter-active {
|
||||
@include translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
|
||||
&.ng-enter, .ng-enter {
|
||||
// Start it down low
|
||||
@include translate3d(0, 100%, 0);
|
||||
|
||||
@include animation-duration(400ms);
|
||||
@include animation-timing-function($slide-in-up-function);
|
||||
@include animation-fill-mode(both);
|
||||
// Start hidden
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.ng-enter-active, .ng-enter-active {
|
||||
@include animation-name(slideInUp);
|
||||
}
|
||||
|
||||
&.ng-leave, .ng-leave {
|
||||
@include animation-duration(400ms);
|
||||
@include animation-timing-function($slide-in-up-function);
|
||||
@include animation-fill-mode(both);
|
||||
}
|
||||
|
||||
&.ng-leave-active, .ng-leave {
|
||||
@include animation-name(slideOutUp);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-in-up-add {
|
||||
@include animation-duration(400ms);
|
||||
@include animation-timing-function($slide-in-up-function);
|
||||
@include animation-fill-mode(forwards);
|
||||
}
|
||||
.slide-in-up-add-active {
|
||||
@include animation-name(slideInUp);
|
||||
.slide-in-up.ng-leave,
|
||||
.slide-in-up .ng-leave {
|
||||
@include transition(all ease-in-out 250ms);
|
||||
}
|
||||
|
||||
.slide-in-up-remove {
|
||||
@include animation-duration(400ms);
|
||||
@include animation-timing-function($slide-in-up-function);
|
||||
@include animation-fill-mode(forwards);
|
||||
}
|
||||
.slide-in-up-remove-active {
|
||||
@include animation-name(slideOutUp);
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
@include animation(fadeOut 0.3s);
|
||||
@@ -492,7 +467,7 @@ $ios7-transition-duration: 250ms;
|
||||
*/
|
||||
$nav-title-slide-ios7-delay: 250ms;
|
||||
.nav-title-slide-ios7 {
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-leave, &.ng-leave {
|
||||
@include transition(all $nav-title-slide-ios7-delay);
|
||||
@include transition-timing-function($ios7-timing-function);
|
||||
@@ -512,7 +487,7 @@ $nav-title-slide-ios7-delay: 250ms;
|
||||
}
|
||||
|
||||
&.reverse {
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-enter, &.ng-enter,
|
||||
> .ng-leave, &.ng-leave {
|
||||
@include transition(all ease-in-out $transition-duration);
|
||||
opacity: 1;
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
*/
|
||||
|
||||
.modal {
|
||||
@include translate3d(0, 100%, 0);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: $z-index-modal;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -16,7 +15,6 @@
|
||||
width: 100%;
|
||||
|
||||
background-color: $modal-bg-color;
|
||||
opacity: 0;
|
||||
|
||||
// Active modal
|
||||
&.active {
|
||||
@@ -30,4 +28,4 @@
|
||||
.modal {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user