Skip to content

Commit b593cf1

Browse files
author
Adam Bradley
committed
fix(modal): Improve slide-up/down animations, remove flickers
1 parent 1b2cd5c commit b593cf1

File tree

6 files changed

+96
-121
lines changed

6 files changed

+96
-121
lines changed

Diff for: js/ext/angular/src/service/ionicModal.js

+41-36
Original file line numberDiff line numberDiff line change
@@ -10,74 +10,79 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ionic.serv
1010
// Show the modal
1111
show: function() {
1212
var self = this;
13-
var element = angular.element(this.el);
13+
var element = angular.element(self.el);
1414

1515
document.body.classList.add('modal-open');
1616

1717
self._isShown = true;
1818

1919
if(!element.parent().length) {
20-
element.addClass(this.animation);
21-
$animate.enter(element, angular.element($document[0].body), null, function() {
22-
});
23-
ionic.views.Modal.prototype.show.call(self);
24-
} else {
25-
$animate.addClass(element, this.animation, function() {
26-
});
20+
self.el.classList.add(self.animation);
21+
$document[0].body.appendChild(self.el);
2722
}
2823

29-
if(!this.didInitEvents) {
30-
var onHardwareBackButton = function() {
31-
self.hide();
32-
};
24+
element.addClass('ng-enter active');
25+
element.removeClass('ng-leave ng-leave-active');
3326

34-
self.scope.$on('$destroy', function() {
35-
$ionicPlatform.offHardwareBackButton(onHardwareBackButton);
36-
});
27+
$timeout(function(){
28+
element.addClass('ng-enter-active');
3729

38-
// Support Android back button to close
39-
$ionicPlatform.onHardwareBackButton(onHardwareBackButton);
30+
if(!self.didInitEvents) {
31+
var onHardwareBackButton = function() {
32+
self.hide();
33+
};
4034

41-
this.didInitEvents = true;
42-
}
35+
self.scope.$on('$destroy', function() {
36+
$ionicPlatform.offHardwareBackButton(onHardwareBackButton);
37+
});
38+
39+
// Support Android back button to close
40+
$ionicPlatform.onHardwareBackButton(onHardwareBackButton);
41+
42+
self.didInitEvents = true;
43+
}
4344

44-
this.scope.$parent.$broadcast('modal.shown', this);
45+
self.scope.$parent.$broadcast('modal.shown');
46+
}, 20);
4547

4648
},
4749
// Hide the modal
4850
hide: function() {
4951
this._isShown = false;
5052
var element = angular.element(this.el);
51-
$animate.removeClass(element, this.animation, function() {
52-
onHideModal(element[0]);
53-
});
53+
54+
element.addClass('ng-leave');
55+
56+
$timeout(function(){
57+
element.addClass('ng-leave-active');
58+
element.removeClass('ng-enter ng-enter-active active');
59+
}, 20);
60+
61+
$timeout(function(){
62+
document.body.classList.remove('modal-open');
63+
}, 400);
5464

5565
ionic.views.Modal.prototype.hide.call(this);
5666

57-
this.scope.$parent.$broadcast('modal.hidden', this);
67+
this.scope.$parent.$broadcast('modal.hidden');
5868
},
5969

6070
// Remove and destroy the modal scope
6171
remove: function() {
62-
var self = this,
63-
element = angular.element(this.el);
64-
this._isShown = false;
65-
$animate.leave(angular.element(this.el), function() {
66-
onHideModal(element[0]);
67-
self.scope.$parent.$broadcast('modal.removed', self);
72+
var self = this;
73+
self.hide();
74+
self.scope.$parent.$broadcast('modal.removed');
75+
76+
$timeout(function(){
6877
self.scope.$destroy();
69-
});
78+
}, 500);
7079
},
7180

7281
isShown: function() {
7382
return !!this._isShown;
7483
}
7584
});
7685

77-
function onHideModal(element) {
78-
document.body.classList.remove('modal-open');
79-
}
80-
8186
var createModal = function(templateString, options) {
8287
// Create a new scope for the modal
8388
var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
@@ -116,6 +121,6 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ionic.serv
116121
cb ? cb(modal) : null;
117122
return modal;
118123
});
119-
},
124+
}
120125
};
121126
}]);

Diff for: js/ext/angular/test/modal.html

+6-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
88
<link rel="stylesheet" href="../../../../dist/css/ionic.css">
99
<script src="../../../../dist/js/ionic.bundle.js"></script>
10+
<script src="dom-trace.js"></script>
1011
</head>
1112
<body>
1213
<ion-pane ng-controller="AppCtrl">
@@ -23,7 +24,7 @@
2324

2425
<script id="modal.html" type="text/ng-template">
2526
<div class="modal" ng-controller="ModalCtrl">
26-
<header class="bar bar-header bar-positive">
27+
<header class="bar bar-header bar-assertive">
2728
<h1 class="title">New Contact</h1>
2829
<button class="button button-clear button-primary" ng-click="close()">Cancel</button>
2930
</header>
@@ -42,8 +43,8 @@ <h1 class="title">New Contact</h1>
4243
<span class="input-label">Email</span>
4344
<input type="text" placeholder="">
4445
</label>
45-
<button class="button button-full button-positive" ng-click="closeModal()">Create</button>
46-
<button class="button button-full button-positive" ng-click="openActionSheet()">ActionSheet</button>
46+
<button class="button button-block button-positive" ng-click="closeModal()">Create</button>
47+
<button class="button button-block button-positive" ng-click="openActionSheet()">ActionSheet</button>
4748
</div>
4849
</div>
4950
</ion-content>
@@ -127,6 +128,8 @@ <h1 class="title">New Contact</h1>
127128
};
128129

129130
});
131+
132+
domTrace.observe();
130133
</script>
131134
</body>
132135
</html>

Diff for: js/ext/angular/test/service/ionicModal.unit.js

+12-14
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,21 @@ describe('Ionic Modal', function() {
5353
expect(m.isShown()).toBe(false);
5454
});
5555

56-
it('show & remove should add .model-open to body', inject(function($animate) {
56+
it('show & remove should add .model-open to body', inject(function() {
5757
var m = modal.fromTemplate('<div class="modal">hi</div>');
5858
m.show();
5959
expect(angular.element(document.body).hasClass('modal-open')).toBe(true);
60-
spyOn($animate, 'leave').andCallFake(function(el, cb) {
61-
cb();
62-
});
6360
m.remove();
61+
timeout.flush();
6462
expect(angular.element(document.body).hasClass('modal-open')).toBe(false);
6563
}));
6664

67-
it('show & hide should add .model-open body', inject(function($animate) {
65+
it('show & hide should add .model-open body', inject(function() {
6866
var m = modal.fromTemplate('<div class="modal">hi</div>');
6967
m.show();
7068
expect(angular.element(document.body).hasClass('modal-open')).toBe(true);
71-
spyOn($animate, 'removeClass').andCallFake(function(el, cls, cb) {
72-
cb();
73-
});
7469
m.hide();
70+
timeout.flush();
7571
expect(angular.element(document.body).hasClass('modal-open')).toBe(false);
7672
}));
7773

@@ -80,7 +76,7 @@ describe('Ionic Modal', function() {
8076
spyOn($animate, 'leave').andCallFake(function(el, cb) { cb(); });
8177
spyOn(m.scope, '$destroy');
8278
m.remove();
83-
expect($animate.leave).toHaveBeenCalled();
79+
timeout.flush();
8480
expect(m.scope.$destroy).toHaveBeenCalled();
8581
}));
8682

@@ -89,14 +85,15 @@ describe('Ionic Modal', function() {
8985
var modalInstance = modal.fromTemplate(template);
9086
modalInstance.show();
9187

92-
//timeout.flush();
88+
timeout.flush();
9389

9490
expect(modalInstance.el.classList.contains('active')).toBe(true);
9591

9692
ionic.trigger('backbutton', {
9793
target: document
9894
});
9995

96+
timeout.flush();
10097
expect(modalInstance.el.classList.contains('active')).toBe(false);
10198
});
10299

@@ -105,14 +102,15 @@ describe('Ionic Modal', function() {
105102
var m = modal.fromTemplate(template, {});
106103
spyOn(m.scope.$parent, '$broadcast');
107104
m.show();
108-
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.shown', m);
105+
timeout.flush();
106+
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.shown');
109107
});
110108
it('should broadcast "modal.hidden" on hide', function() {
111109
var template = '<div class="modal"></div>';
112110
var m = modal.fromTemplate(template, {});
113111
spyOn(m.scope.$parent, '$broadcast');
114112
m.hide();
115-
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.hidden', m);
113+
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.hidden');
116114
});
117115
it('should broadcast "modal.removed" on remove', inject(function($animate) {
118116
var template = '<div class="modal"></div>';
@@ -125,9 +123,9 @@ describe('Ionic Modal', function() {
125123
spyOn(m.scope.$parent, '$broadcast').andCallFake(function(e, modal) {
126124
broadcastedModal = modal;
127125
});
128-
spyOn($animate, 'leave').andCallFake(function(el, cb) { cb(); });
129126

130127
m.remove();
131-
expect(broadcastedModal).toBe(m);
128+
expect(m.scope.$parent.$broadcast).toHaveBeenCalledWith('modal.removed');
129+
timeout.flush();
132130
}));
133131
});

Diff for: js/views/modalView.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,15 @@
1616
show: function() {
1717
var self = this;
1818

19-
this.el.classList.add('active');
20-
21-
if(this.focusFirstInput) {
19+
if(self.focusFirstInput) {
2220
// Let any animations run first
2321
window.setTimeout(function() {
2422
var input = self.el.querySelector('input, textarea');
2523
input && input.focus && input.focus();
26-
}, this.focusFirstDelay);
24+
}, self.focusFirstDelay);
2725
}
2826
},
2927
hide: function() {
30-
this.el.classList.remove('active');
31-
3228
// Unfocus all elements
3329
if(this.unfocusOnHide) {
3430
var inputs = this.el.querySelectorAll('input, textarea');

0 commit comments

Comments
 (0)