forked from angular/angular.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanimator.js
314 lines (290 loc) · 12.8 KB
/
animator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
'use strict';
// NOTE: this is a pseudo directive.
/**
* @ngdoc directive
* @name ng.directive:ngAnimate
*
* @description
* The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives.
* It effects how the directive will perform DOM manipulation. This allows for complex animations to take place while
* without burduning the directive which uses the animation with animation details. The built dn directives
* `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive.
* Custom directives can take advantage of animation through {@link ng.$animator $animator service}.
*
* Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives:
*
* * {@link ng.directive:ngRepeat#animations ngRepeat} — enter, leave and move
* * {@link ng.directive:ngView#animations ngView} — enter and leave
* * {@link ng.directive:ngInclude#animations ngInclude} — enter and leave
* * {@link ng.directive:ngSwitch#animations ngSwitch} — enter and leave
* * {@link ng.directive:ngShow#animations ngShow & ngHide} - show and hide respectively
*
* You can find out more information about animations upon visiting each directive page.
*
* Below is an example of a directive that makes use of the ngAnimate attribute:
*
* <pre>
* <!-- you can also use data-ng-animate, ng:animate or x-ng-animate as well -->
* <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY>
*
* <!-- you can also use a short hand -->
* <ANY ng-directive ng-animate=" 'animation' "></ANY>
* <!-- which expands to -->
* <ANY ng-directive ng-animate="{ enter: 'animation-enter', leave: 'animation-leave', ...}"></ANY>
*
* <!-- keep in mind that ng-animate can take expressions -->
* <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY>
* </pre>
*
* The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned.
*
* <h2>CSS-defined Animations</h2>
* By default, ngAnimate attaches two CSS3 classes per animation event to the DOM element to achieve the animation.
* This is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions.
* All that is required is the following CSS code:
*
* <pre>
* <style type="text/css">
* /*
* The animate-enter prefix is the event name that you
* have provided within the ngAnimate attribute.
* */
* .animate-enter-setup {
* -webkit-transition: 1s linear all; /* Safari/Chrome */
* -moz-transition: 1s linear all; /* Firefox */
* -ms-transition: 1s linear all; /* IE10 */
* -o-transition: 1s linear all; /* Opera */
* transition: 1s linear all; /* Future Browsers */
*
* /* The animation preparation code */
* opacity: 0;
* }
*
* /*
* Keep in mind that you want to combine both CSS
* classes together to avoid any CSS-specificity
* conflicts
* */
* .animate-enter-setup.animate-enter-start {
* /* The animation code itself */
* opacity: 1;
* }
* </style>
*
* <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
* </pre>
*
* Upon DOM mutation, the setup class is added first, then the browser is allowed to reflow the content and then,
* the start class is added to trigger the animation. The ngAnimate directive will automatically extract the duration
* of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be
* removed from the DOM. If a browser does not support CSS transitions then the animation will start and end
* immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element
* has no CSS animation classes surrounding it.
*
* <h2>JavaScript-defined Animations</h2>
* In the event that you do not want to use CSS3 animations or if you wish to offer animations to browsers that do not
* yet support them, then you can make use of JavaScript animations defined inside ngModule.
*
* <pre>
* var ngModule = angular.module('YourApp', []);
* ngModule.animation('animate-enter', function() {
* return {
* setup : function(element) {
* //prepare the element for animation
* element.css({ 'opacity': 0 });
* var memo = "..."; //this value is passed to the start function
* return memo;
* },
* start : function(element, done, memo) {
* //start the animation
* element.animate({
* 'opacity' : 1
* }, function() {
* //call when the animation is complete
* done()
* });
* }
* }
* });
* </pre>
*
* As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation
* can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled
* animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're using
* JavaScript animations) to animated the element, but it will not attempt to find any CSS3 transition duration value.
* It will instead close off the animation once the provided done function is executed. So it's important that you
* make sure your animations remember to fire off the done function once the animations are complete.
*
* @param {expression} ngAnimate Used to configure the DOM manipulation animations.
*
*/
/**
* @ngdoc function
* @name ng.$animator
*
* @description
* The $animator service provides the DOM manipulation API which is decorated with animations.
*
* @param {Scope} scope the scope for the ng-animate.
* @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are
* passed into the linking function of the directive using the `$animator`.)
* @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods.
*/
var $AnimatorProvider = function() {
this.$get = ['$animation', '$window', '$sniffer', function($animation, $window, $sniffer) {
return function(scope, attrs) {
var ngAnimateAttr = attrs.ngAnimate;
var animator = {};
/**
* @ngdoc function
* @name ng.animator#enter
* @methodOf ng.$animator
* @function
*
* @description
* Injects the element object into the DOM (inside of the parent element) and then runs the enter animation.
*
* @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
* @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
*/
animator.enter = animateActionFactory('enter', insert, noop);
/**
* @ngdoc function
* @name ng.animator#leave
* @methodOf ng.$animator
* @function
*
* @description
* Runs the leave animation operation and, upon completion, removes the element from the DOM.
*
* @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation
*/
animator.leave = animateActionFactory('leave', noop, remove);
/**
* @ngdoc function
* @name ng.animator#move
* @methodOf ng.$animator
* @function
*
* @description
* Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or
* add the element directly after the after element if present. Then the move animation will be run.
*
* @param {jQuery/jqLite element} element the element that will be the focus of the move animation
* @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
* @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
*/
animator.move = animateActionFactory('move', move, noop);
/**
* @ngdoc function
* @name ng.animator#show
* @methodOf ng.$animator
* @function
*
* @description
* Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after.
*
* @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
* @param {boolean} instant Should the actual animation be shown or skip it? (useful for setting initial state)
*/
animator.show = animateActionFactory('show', show, noop);
/**
* @ngdoc function
* @name ng.animator#hide
* @methodOf ng.$animator
*
* @description
* Starts the hide animation first and sets the CSS `display` property to `none` upon completion.
*
* @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
* @param {boolean} instant Should the actual animation be shown or skip it? (useful for setting initial state)
*/
animator.hide = animateActionFactory('hide', noop, hide);
return animator;
function animateActionFactory(type, beforeFn, afterFn) {
var ngAnimateValue = ngAnimateAttr && scope.$eval(ngAnimateAttr);
var className = ngAnimateAttr
? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
: '';
var animationPolyfill = $animation(className);
var polyfillSetup = animationPolyfill && animationPolyfill.setup;
var polyfillStart = animationPolyfill && animationPolyfill.start;
if (!className) {
return function(element, parent, after) {
beforeFn(element, parent, after);
afterFn(element, parent, after);
}
} else {
var setupClass = className + '-setup';
var startClass = className + '-start';
return function(element, parent, after) {
if (!$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart) {
beforeFn(element, parent, after);
afterFn(element, parent, after);
return;
}
var instant = (typeof(parent) === 'boolean') && parent === true;
if (!instant) element.addClass(setupClass);
beforeFn(element, parent, after);
if (element.length == 0 || instant) return done();
var memento = (polyfillSetup || noop)(element);
// $window.setTimeout(beginAnimation, 0); this was causing the element not to animate
// keep at 1 for animation dom rerender
$window.setTimeout(beginAnimation, 1);
function beginAnimation() {
element.addClass(startClass);
if (polyfillStart) {
polyfillStart(element, done, memento);
} else if (isFunction($window.getComputedStyle)) {
var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
var w3cTransitionProp = 'transition'; //one day all browsers will have this
var durationKey = 'Duration';
var duration = 0;
//we want all the styles defined before and after
forEach(element, function(element) {
var globalStyles = $window.getComputedStyle(element) || {};
duration = Math.max(
parseFloat(globalStyles[w3cTransitionProp + durationKey]) ||
parseFloat(globalStyles[vendorTransitionProp + durationKey]) ||
0,
duration);
});
$window.setTimeout(done, duration * 1000);
} else {
done();
}
}
function done() {
afterFn(element, parent, after);
element.removeClass(setupClass);
element.removeClass(startClass);
}
}
}
}
}
function show(element) {
element.css('display', '');
}
function hide(element) {
element.css('display', 'none');
}
function insert(element, parent, after) {
if (after) {
after.after(element);
} else {
parent.append(element);
}
}
function remove(element) {
element.remove();
}
function move(element, parent, after) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
insert(element, parent, after);
}
}];
};