1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | 'use strict';
|
24 |
|
25 | var TIMEOUT_RATIO = 1.4;
|
26 |
|
27 | var util = {
|
28 | };
|
29 |
|
30 |
|
31 | util.capitalize = function(str) {
|
32 | return str.charAt(0).toUpperCase() + str.slice(1);
|
33 | };
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | util.buildTransitionValue = function(params) {
|
42 | params.property = params.property || 'all';
|
43 | params.duration = params.duration || 0.4;
|
44 | params.timing = params.timing || 'linear';
|
45 |
|
46 | var props = params.property.split(/ +/);
|
47 |
|
48 | return props.map(function(prop) {
|
49 | return prop + ' ' + params.duration + 's ' + params.timing;
|
50 | }).join(', ');
|
51 | };
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | util.onceOnTransitionEnd = function(element, callback) {
|
57 | if (!element) {
|
58 | return function() {};
|
59 | }
|
60 |
|
61 | var removeListeners = function() {
|
62 | util._transitionEndEvents.forEach(function(eventName) {
|
63 | element.removeEventListener(eventName, fn, false);
|
64 | });
|
65 | };
|
66 |
|
67 | var fn = function(event) {
|
68 | if (element == event.target) {
|
69 | event.stopPropagation();
|
70 | removeListeners();
|
71 |
|
72 | callback();
|
73 | }
|
74 | };
|
75 |
|
76 | util._transitionEndEvents.forEach(function(eventName) {
|
77 | element.addEventListener(eventName, fn, false);
|
78 | });
|
79 |
|
80 | return removeListeners;
|
81 | };
|
82 |
|
83 | util._transitionEndEvents = (function() {
|
84 |
|
85 | if ('ontransitionend' in window) {
|
86 | return ['transitionend'];
|
87 | }
|
88 |
|
89 | if ('onwebkittransitionend' in window) {
|
90 | return ['webkitTransitionEnd'];
|
91 | }
|
92 |
|
93 | if (util.vendorPrefix === 'webkit' || util.vendorPrefix === 'o' || util.vendorPrefix === 'moz' || util.vendorPrefix === 'ms') {
|
94 | return [util.vendorPrefix + 'TransitionEnd', 'transitionend'];
|
95 | }
|
96 |
|
97 | return [];
|
98 | })();
|
99 |
|
100 | util._cssPropertyDict = (function() {
|
101 | var styles = window.getComputedStyle(document.documentElement, '');
|
102 | var dict = {};
|
103 | var a = 'A'.charCodeAt(0);
|
104 | var z = 'z'.charCodeAt(0);
|
105 |
|
106 | var upper = function(s) {
|
107 | return s.substr(1).toUpperCase();
|
108 | };
|
109 |
|
110 | for (var i = 0; i < styles.length; i++) {
|
111 |
|
112 | var key = styles[i]
|
113 | .replace(/^[-]+/, '')
|
114 | .replace(/[-][a-z]/g, upper)
|
115 | .replace(/^moz/, 'Moz');
|
116 |
|
117 | if (a <= key.charCodeAt(0) && z >= key.charCodeAt(0)) {
|
118 | if (key !== 'cssText' && key !== 'parentText') {
|
119 | dict[key] = true;
|
120 | }
|
121 | }
|
122 | }
|
123 |
|
124 | return dict;
|
125 | })();
|
126 |
|
127 | util.hasCssProperty = function(name) {
|
128 | return name in util._cssPropertyDict;
|
129 | };
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | util.vendorPrefix = (function() {
|
135 | var styles = window.getComputedStyle(document.documentElement, ''),
|
136 | pre = (Array.prototype.slice
|
137 | .call(styles)
|
138 | .join('')
|
139 | .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
|
140 | )[1];
|
141 | return pre;
|
142 | })();
|
143 |
|
144 | util.forceLayoutAtOnce = function(elements, callback) {
|
145 | this.batchImmediate(function() {
|
146 | elements.forEach(function(element) {
|
147 |
|
148 | element.offsetHeight;
|
149 | });
|
150 | callback();
|
151 | });
|
152 | };
|
153 |
|
154 | util.batchImmediate = (function() {
|
155 | var callbacks = [];
|
156 |
|
157 | return function(callback) {
|
158 | if (callbacks.length === 0) {
|
159 | setImmediate(function() {
|
160 | var concreateCallbacks = callbacks.slice(0);
|
161 | callbacks = [];
|
162 | concreateCallbacks.forEach(function(callback) {
|
163 | callback();
|
164 | });
|
165 | });
|
166 | }
|
167 |
|
168 | callbacks.push(callback);
|
169 | };
|
170 | })();
|
171 |
|
172 | util.batchAnimationFrame = (function() {
|
173 | var callbacks = [];
|
174 |
|
175 | var raf = window.requestAnimationFrame ||
|
176 | window.webkitRequestAnimationFrame ||
|
177 | window.mozRequestAnimationFrame ||
|
178 | window.oRequestAnimationFrame ||
|
179 | window.msRequestAnimationFrame ||
|
180 | function(callback) {
|
181 | setTimeout(callback, 1000 / 60);
|
182 | };
|
183 |
|
184 | return function(callback) {
|
185 | if (callbacks.length === 0) {
|
186 | raf(function() {
|
187 | var concreateCallbacks = callbacks.slice(0);
|
188 | callbacks = [];
|
189 | concreateCallbacks.forEach(function(callback) {
|
190 | callback();
|
191 | });
|
192 | });
|
193 | }
|
194 |
|
195 | callbacks.push(callback);
|
196 | };
|
197 | })();
|
198 |
|
199 | util.transitionPropertyName = (function() {
|
200 | if (util.hasCssProperty('transitionDuration')) {
|
201 | return 'transition';
|
202 | }
|
203 |
|
204 | if (util.hasCssProperty(util.vendorPrefix + 'TransitionDuration')) {
|
205 | return util.vendorPrefix + 'Transition';
|
206 | }
|
207 |
|
208 | throw new Error('Invalid state');
|
209 | })();
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 | var Animit = function(element, defaults) {
|
216 | if (!(this instanceof Animit)) {
|
217 | return new Animit(element, defaults);
|
218 | }
|
219 |
|
220 | if (element instanceof HTMLElement) {
|
221 | this.elements = [element];
|
222 | } else if (Object.prototype.toString.call(element) === '[object Array]') {
|
223 | this.elements = element;
|
224 |
|
225 | } else {
|
226 | throw new Error('First argument must be an array or an instance of HTMLElement.');
|
227 | }
|
228 |
|
229 | this.defaults = defaults;
|
230 | this.transitionQueue = [];
|
231 | this.lastStyleAttributeDict = [];
|
232 | };
|
233 |
|
234 | Animit.prototype = {
|
235 |
|
236 | |
237 |
|
238 |
|
239 | transitionQueue: undefined,
|
240 |
|
241 | |
242 |
|
243 |
|
244 | elements: undefined,
|
245 |
|
246 | |
247 |
|
248 |
|
249 | defaults: undefined,
|
250 |
|
251 | |
252 |
|
253 |
|
254 |
|
255 |
|
256 | play: function(callback) {
|
257 | if (typeof callback === 'function') {
|
258 | this.transitionQueue.push(function(done) {
|
259 | callback();
|
260 | done();
|
261 | });
|
262 | }
|
263 |
|
264 | this.startAnimation();
|
265 |
|
266 | return this;
|
267 | },
|
268 |
|
269 | |
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 | default: function(from, to, delay) {
|
277 | function step(params, duration, timing) {
|
278 | if (params.duration !== undefined) {
|
279 | duration = params.duration;
|
280 | }
|
281 | if (params.timing !== undefined) {
|
282 | timing = params.timing;
|
283 | }
|
284 |
|
285 | return {
|
286 | css: params.css || params,
|
287 | duration: duration,
|
288 | timing: timing
|
289 | };
|
290 | }
|
291 |
|
292 | return this.saveStyle()
|
293 | .queue(step(from, 0, this.defaults.timing))
|
294 | .wait(delay === undefined ? this.defaults.delay : delay)
|
295 | .queue(step(to, this.defaults.duration, this.defaults.timing))
|
296 | .restoreStyle();
|
297 | },
|
298 |
|
299 | |
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | queue: function(transition, options) {
|
310 | var queue = this.transitionQueue;
|
311 |
|
312 | if (transition && options) {
|
313 | options.css = transition;
|
314 | transition = new Animit.Transition(options);
|
315 | }
|
316 |
|
317 | if (!(transition instanceof Function || transition instanceof Animit.Transition)) {
|
318 | if (transition.css) {
|
319 | transition = new Animit.Transition(transition);
|
320 | } else {
|
321 | transition = new Animit.Transition({
|
322 | css: transition
|
323 | });
|
324 | }
|
325 | }
|
326 |
|
327 | if (transition instanceof Function) {
|
328 | queue.push(transition);
|
329 | } else if (transition instanceof Animit.Transition) {
|
330 | queue.push(transition.build());
|
331 | } else {
|
332 | throw new Error('Invalid arguments');
|
333 | }
|
334 |
|
335 | return this;
|
336 | },
|
337 |
|
338 | |
339 |
|
340 |
|
341 |
|
342 |
|
343 | wait: function(seconds) {
|
344 | if (seconds > 0) {
|
345 | this.transitionQueue.push(function(done) {
|
346 | setTimeout(done, 1000 * seconds);
|
347 | });
|
348 | }
|
349 |
|
350 | return this;
|
351 | },
|
352 |
|
353 | saveStyle: function() {
|
354 |
|
355 | this.transitionQueue.push(function(done) {
|
356 | this.elements.forEach(function(element, index) {
|
357 | var css = this.lastStyleAttributeDict[index] = {};
|
358 |
|
359 | for (var i = 0; i < element.style.length; i++) {
|
360 | css[element.style[i]] = element.style[element.style[i]];
|
361 | }
|
362 | }.bind(this));
|
363 | done();
|
364 | }.bind(this));
|
365 |
|
366 | return this;
|
367 | },
|
368 |
|
369 | |
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 | restoreStyle: function(options) {
|
378 | options = options || {};
|
379 | var self = this;
|
380 |
|
381 | if (options.transition && !options.duration) {
|
382 | throw new Error('"options.duration" is required when "options.transition" is enabled.');
|
383 | }
|
384 |
|
385 | var transitionName = util.transitionPropertyName;
|
386 |
|
387 | if (options.transition || (options.duration && options.duration > 0)) {
|
388 | var transitionValue = options.transition || ('all ' + options.duration + 's ' + (options.timing || 'linear'));
|
389 |
|
390 | this.transitionQueue.push(function(done) {
|
391 | var elements = this.elements;
|
392 | var timeoutId;
|
393 |
|
394 | var clearTransition = function() {
|
395 | elements.forEach(function(element) {
|
396 | element.style[transitionName] = '';
|
397 | });
|
398 | };
|
399 |
|
400 |
|
401 | var removeListeners = util.onceOnTransitionEnd(elements[0], function() {
|
402 | clearTimeout(timeoutId);
|
403 | clearTransition();
|
404 | done();
|
405 | });
|
406 |
|
407 |
|
408 | timeoutId = setTimeout(function() {
|
409 | removeListeners();
|
410 | clearTransition();
|
411 | done();
|
412 | }, options.duration * 1000 * TIMEOUT_RATIO);
|
413 |
|
414 |
|
415 | elements.forEach(function(element, index) {
|
416 |
|
417 | var css = self.lastStyleAttributeDict[index];
|
418 |
|
419 | if (!css) {
|
420 | throw new Error('restoreStyle(): The style is not saved. Invoke saveStyle() before.');
|
421 | }
|
422 |
|
423 | self.lastStyleAttributeDict[index] = undefined;
|
424 |
|
425 | var name;
|
426 | for (var i = 0, len = element.style.length; i < len; i++) {
|
427 | name = element.style[i];
|
428 | if (css[name] === undefined) {
|
429 | css[name] = '';
|
430 | }
|
431 | }
|
432 |
|
433 | element.style[transitionName] = transitionValue;
|
434 |
|
435 | Object.keys(css).forEach(function(key) {
|
436 | if (key !== transitionName) {
|
437 | element.style[key] = css[key];
|
438 | }
|
439 | });
|
440 |
|
441 | element.style[transitionName] = transitionValue;
|
442 | });
|
443 | });
|
444 | } else {
|
445 | this.transitionQueue.push(function(done) {
|
446 | reset();
|
447 | done();
|
448 | });
|
449 | }
|
450 |
|
451 | return this;
|
452 |
|
453 | function reset() {
|
454 |
|
455 | self.elements.forEach(function(element, index) {
|
456 | element.style[transitionName] = 'none';
|
457 |
|
458 | var css = self.lastStyleAttributeDict[index];
|
459 |
|
460 | if (!css) {
|
461 | throw new Error('restoreStyle(): The style is not saved. Invoke saveStyle() before.');
|
462 | }
|
463 |
|
464 | self.lastStyleAttributeDict[index] = undefined;
|
465 |
|
466 | for (var i = 0, name = ''; i < element.style.length; i++) {
|
467 | name = element.style[i];
|
468 | if (typeof css[element.style[i]] === 'undefined') {
|
469 | css[element.style[i]] = '';
|
470 | }
|
471 | }
|
472 |
|
473 | Object.keys(css).forEach(function(key) {
|
474 | element.style[key] = css[key];
|
475 | });
|
476 |
|
477 | });
|
478 | }
|
479 | },
|
480 |
|
481 | |
482 |
|
483 |
|
484 | startAnimation: function() {
|
485 | this._dequeueTransition();
|
486 |
|
487 | return this;
|
488 | },
|
489 |
|
490 | _dequeueTransition: function() {
|
491 | var transition = this.transitionQueue.shift();
|
492 | if (this._currentTransition) {
|
493 | throw new Error('Current transition exists.');
|
494 | }
|
495 | this._currentTransition = transition;
|
496 | var self = this;
|
497 | var called = false;
|
498 |
|
499 | var done = function() {
|
500 | if (!called) {
|
501 | called = true;
|
502 | self._currentTransition = undefined;
|
503 | self._dequeueTransition();
|
504 | } else {
|
505 | throw new Error('Invalid state: This callback is called twice.');
|
506 | }
|
507 | };
|
508 |
|
509 | if (transition) {
|
510 | transition.call(this, done);
|
511 | }
|
512 | }
|
513 |
|
514 | };
|
515 |
|
516 |
|
517 |
|
518 |
|
519 | Animit.runAll = function(/* arguments... */) {
|
520 | for (var i = 0; i < arguments.length; i++) {
|
521 | arguments[i].play();
|
522 | }
|
523 | };
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 |
|
532 | Animit.Transition = function(options) {
|
533 | this.options = options || {};
|
534 | this.options.duration = this.options.duration || 0;
|
535 | this.options.timing = this.options.timing || 'linear';
|
536 | this.options.css = this.options.css || {};
|
537 | this.options.property = this.options.property || 'all';
|
538 | };
|
539 |
|
540 | Animit.Transition.prototype = {
|
541 |
|
542 | |
543 |
|
544 |
|
545 |
|
546 | build: function() {
|
547 |
|
548 | if (Object.keys(this.options.css).length === 0) {
|
549 | throw new Error('options.css is required.');
|
550 | }
|
551 |
|
552 | var css = createActualCssProps(this.options.css);
|
553 |
|
554 | if (this.options.duration > 0) {
|
555 | var transitionValue = util.buildTransitionValue(this.options);
|
556 | var self = this;
|
557 |
|
558 | return function(callback) {
|
559 | var elements = this.elements;
|
560 | var timeout = self.options.duration * 1000 * TIMEOUT_RATIO;
|
561 | var timeoutId;
|
562 |
|
563 | var removeListeners = util.onceOnTransitionEnd(elements[0], function() {
|
564 | clearTimeout(timeoutId);
|
565 | callback();
|
566 | });
|
567 |
|
568 | timeoutId = setTimeout(function() {
|
569 | removeListeners();
|
570 | callback();
|
571 | }, timeout);
|
572 |
|
573 | elements.forEach(function(element) {
|
574 | element.style[util.transitionPropertyName] = transitionValue;
|
575 |
|
576 | Object.keys(css).forEach(function(name) {
|
577 | element.style[name] = css[name];
|
578 | });
|
579 | });
|
580 |
|
581 | };
|
582 | }
|
583 |
|
584 | if (this.options.duration <= 0) {
|
585 | return function(callback) {
|
586 | var elements = this.elements;
|
587 |
|
588 | elements.forEach(function(element) {
|
589 | element.style[util.transitionPropertyName] = '';
|
590 |
|
591 | Object.keys(css).forEach(function(name) {
|
592 | element.style[name] = css[name];
|
593 | });
|
594 | });
|
595 |
|
596 | if (elements.length > 0) {
|
597 | util.forceLayoutAtOnce(elements, function() {
|
598 | util.batchAnimationFrame(callback);
|
599 | });
|
600 | } else {
|
601 | util.batchAnimationFrame(callback);
|
602 | }
|
603 | };
|
604 | }
|
605 |
|
606 | function createActualCssProps(css) {
|
607 | var result = {};
|
608 |
|
609 | Object.keys(css).forEach(function(name) {
|
610 | var value = css[name];
|
611 |
|
612 | if (util.hasCssProperty(name)) {
|
613 | result[name] = value;
|
614 | return;
|
615 | }
|
616 |
|
617 | var prefixed = util.vendorPrefix + util.capitalize(name);
|
618 | if (util.hasCssProperty(prefixed)) {
|
619 | result[prefixed] = value;
|
620 | } else {
|
621 | result[prefixed] = value;
|
622 | result[name] = value;
|
623 | }
|
624 | });
|
625 |
|
626 | return result;
|
627 | }
|
628 |
|
629 | }
|
630 | };
|
631 |
|
632 | export default Animit;
|
633 |
|