1 | /*
|
2 | Copyright 2013-2016 ASIAL CORPORATION
|
3 |
|
4 | Licensed under the Apache License, Version 2.0 (the "License");
|
5 | you may not use this file except in compliance with the License.
|
6 | You may obtain a copy of the License at
|
7 |
|
8 | http://www.apache.org/licenses/LICENSE-2.0
|
9 |
|
10 | Unless required by applicable law or agreed to in writing, software
|
11 | distributed under the License is distributed on an "AS IS" BASIS,
|
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | See the License for the specific language governing permissions and
|
14 | limitations under the License.
|
15 |
|
16 | */
|
17 |
|
18 | import internal from '../../ons/internal/index.js';
|
19 |
|
20 |
|
21 | var raf = (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame)
|
22 | || (cb => { setTimeout(() => { cb(new Date().getTime()); }, 1000 / 60); });
|
23 |
|
24 | raf = raf.bind(window);
|
25 |
|
26 | /**
|
27 | * @class AnimatorJS - implementation of Animator class using javascript
|
28 | */
|
29 | class AnimatorJS {
|
30 |
|
31 | /**
|
32 | * @method animate
|
33 | * @desc main animation function
|
34 | * @param {Element} element
|
35 | * @param {Object} finalCSS
|
36 | * @param {number} [duration=200] - duration in milliseconds
|
37 | * @return {Object} result
|
38 | * @return {Function} result.then(callback) - sets a callback to be executed after the animation has stopped
|
39 | * @return {Function} result.stop(options) - stops the animation; if options.stopNext is true then it doesn't call the callback
|
40 | * @return {Function} result.finish(ms) - finishes the animation in the specified time in milliseconds
|
41 | * @return {Function} result.speed(ms) - sets the animation speed so that it finishes as if the original duration was the one specified here
|
42 | * @example
|
43 | * ````
|
44 | * var result = animator.animate(el, {opacity: 0.5}, 1000);
|
45 | *
|
46 | * el.addEventListener('click', function(e){
|
47 | * result.speed(200).then(function(){
|
48 | * console.log('done');
|
49 | * });
|
50 | * }, 300);
|
51 | * ````
|
52 | */
|
53 | animate(el, final, duration = 200) {
|
54 | var start,
|
55 | initial = {},
|
56 | stopped = false,
|
57 | next,
|
58 | elapsed,
|
59 | properties = Object.keys(final);
|
60 |
|
61 | var result = {
|
62 | stop: (options = {}) => {
|
63 | if (options.stopNext) {
|
64 | next = false;
|
65 | }
|
66 | if (!stopped) {
|
67 | stopped = true;
|
68 | next && next();
|
69 | }
|
70 | return result;
|
71 | },
|
72 | then: (cb) => {
|
73 | next = cb;
|
74 | if (stopped) {
|
75 | next && next();
|
76 | }
|
77 | return result;
|
78 | },
|
79 | finish: (milliseconds = 50) => {
|
80 | var k = milliseconds / (duration - elapsed);
|
81 | if (internal.config.animationsDisabled) {
|
82 | k = 0;
|
83 | }
|
84 | if (k < 1) {
|
85 | start += elapsed - elapsed * k;
|
86 | duration *= k;
|
87 | }
|
88 | return result;
|
89 | },
|
90 | speed: (newDuration) => {
|
91 | return result.finish(newDuration * (1 - elapsed / duration));
|
92 | }
|
93 | };
|
94 |
|
95 | if (el.hasAttribute('disabled') || internal.config.animationsDisabled) {
|
96 | return result;
|
97 | }
|
98 |
|
99 | var cs = window.getComputedStyle(el);
|
100 | properties.forEach(i => {
|
101 | initial[i] = parseFloat(el.style[i] || cs.getPropertyValue(i));
|
102 | });
|
103 | this._onStopAnimations(el, result.stop);
|
104 |
|
105 | var step = (timestamp) => {
|
106 | start = start || timestamp;
|
107 | elapsed = timestamp - start;
|
108 | if (!stopped) {
|
109 | properties.forEach(i => {
|
110 | el.style[i] = initial[i] + (final[i] - initial[i]) * Math.min(1, elapsed / duration) + (i == 'opacity' ? 0 : 'px');
|
111 | });
|
112 | stopped = stopped || elapsed >= duration;
|
113 | if (!stopped) {
|
114 | return raf(step);
|
115 | }
|
116 | }
|
117 | return next && next();
|
118 | };
|
119 | step(0);
|
120 |
|
121 | return result;
|
122 | }
|
123 |
|
124 | constructor () {
|
125 | this._queue = [];
|
126 | this._index = 0;
|
127 | }
|
128 |
|
129 | _onStopAnimations(el, listener) {
|
130 | var queue = this._queue;
|
131 | var i = this._index++;
|
132 | queue[el] = queue[el] || [];
|
133 | queue[el][i] = (options) => {
|
134 | delete queue[el][i];
|
135 | if (queue[el] && queue[el].length == 0) {
|
136 | delete queue[el];
|
137 | }
|
138 | return listener(options);
|
139 | };
|
140 | }
|
141 |
|
142 | /**
|
143 | * @method stopAnimations
|
144 | * @desc stops active animations on a specified element
|
145 | * @param {Element|Array} element - element or array of elements
|
146 | * @param {Object} [options={}]
|
147 | * @param {Boolean} [options.stopNext] - the callbacks after the animations won't be called if this option is true
|
148 | */
|
149 | stopAnimations(el, options = {}) {
|
150 | if (Array.isArray(el)) {
|
151 | return el.forEach(el => {
|
152 | this.stopAnimations(el, options);
|
153 | });
|
154 | }
|
155 |
|
156 | (this._queue[el] || []).forEach(e => { e(options || {}); });
|
157 | }
|
158 |
|
159 | /**
|
160 | * @method stopAll
|
161 | * @desc stops all active animations
|
162 | * @param {Object} [options={}]
|
163 | * @param {Boolean} [options.stopNext] - the callbacks after the animations won't be called if this option is true
|
164 | */
|
165 | stopAll(options = {}) {
|
166 | this.stopAnimations(Object.keys(this._queue), options);
|
167 | }
|
168 |
|
169 | /**
|
170 | * @method fade
|
171 | * @desc fades the element (short version for animate(el, {opacity: 0}))
|
172 | * @param {Element} element
|
173 | * @param {number} [duration=200]
|
174 | */
|
175 | fade(el, duration = 200) {
|
176 | return this.animate(el, {opacity: 0}, duration);
|
177 | }
|
178 |
|
179 | }
|
180 |
|
181 | export default AnimatorJS;
|
182 |
|