1 | ;
|
2 |
|
3 | /*
|
4 | * Scroller
|
5 | * http://github.com/zynga/scroller
|
6 | *
|
7 | * Copyright 2011, Zynga Inc.
|
8 | * Licensed under the MIT License.
|
9 | * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
|
10 | *
|
11 | * Based on the work of: Unify Project (unify-project.org)
|
12 | * http://unify-project.org
|
13 | * Copyright 2011, Deutsche Telekom AG
|
14 | * License: MIT + Apache (V2)
|
15 | */
|
16 |
|
17 | /**
|
18 | * Generic animation class with support for dropped frames both optional easing and duration.
|
19 | *
|
20 | * Optional duration is useful when the lifetime is defined by another condition than time
|
21 | * e.g. speed of an animating object, etc.
|
22 | *
|
23 | * Dropped frame logic allows to keep using the same updater logic independent from the actual
|
24 | * rendering. This eases a lot of cases where it might be pretty complex to break down a state
|
25 | * based on the pure time difference.
|
26 | */
|
27 | (function (global) {
|
28 | var time = Date.now || function () {
|
29 | return +new Date();
|
30 | };
|
31 | var desiredFrames = 60;
|
32 | var millisecondsPerSecond = 1000;
|
33 | var running = {};
|
34 | var counter = 1;
|
35 |
|
36 | // Create namespaces
|
37 | if (!global.core) {
|
38 | global.core = { effect: {} };
|
39 | } else if (!core.effect) {
|
40 | core.effect = {};
|
41 | }
|
42 |
|
43 | core.effect.Animate = {
|
44 |
|
45 | /**
|
46 | * A requestAnimationFrame wrapper / polyfill.
|
47 | *
|
48 | * @param callback {Function} The callback to be invoked before the next repaint.
|
49 | * @param root {HTMLElement} The root element for the repaint
|
50 | */
|
51 | requestAnimationFrame: function () {
|
52 |
|
53 | // Check for request animation Frame support
|
54 | var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
|
55 | var isNative = !!requestFrame;
|
56 |
|
57 | if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
|
58 | isNative = false;
|
59 | }
|
60 |
|
61 | if (isNative) {
|
62 | return function (callback, root) {
|
63 | requestFrame(callback, root);
|
64 | };
|
65 | }
|
66 |
|
67 | var TARGET_FPS = 60;
|
68 | var requests = {};
|
69 | var requestCount = 0;
|
70 | var rafHandle = 1;
|
71 | var intervalHandle = null;
|
72 | var lastActive = +new Date();
|
73 |
|
74 | return function (callback, root) {
|
75 | var callbackHandle = rafHandle++;
|
76 |
|
77 | // Store callback
|
78 | requests[callbackHandle] = callback;
|
79 | requestCount++;
|
80 |
|
81 | // Create timeout at first request
|
82 | if (intervalHandle === null) {
|
83 |
|
84 | intervalHandle = setInterval(function () {
|
85 |
|
86 | var time = +new Date();
|
87 | var currentRequests = requests;
|
88 |
|
89 | // Reset data structure before executing callbacks
|
90 | requests = {};
|
91 | requestCount = 0;
|
92 |
|
93 | for (var key in currentRequests) {
|
94 | if (currentRequests.hasOwnProperty(key)) {
|
95 | currentRequests[key](time);
|
96 | lastActive = time;
|
97 | }
|
98 | }
|
99 |
|
100 | // Disable the timeout when nothing happens for a certain
|
101 | // period of time
|
102 | if (time - lastActive > 2500) {
|
103 | clearInterval(intervalHandle);
|
104 | intervalHandle = null;
|
105 | }
|
106 | }, 1000 / TARGET_FPS);
|
107 | }
|
108 |
|
109 | return callbackHandle;
|
110 | };
|
111 | }(),
|
112 |
|
113 | /**
|
114 | * Stops the given animation.
|
115 | *
|
116 | * @param id {Integer} Unique animation ID
|
117 | * @return {Boolean} Whether the animation was stopped (aka, was running before)
|
118 | */
|
119 | stop: function stop(id) {
|
120 | var cleared = running[id] != null;
|
121 | if (cleared) {
|
122 | running[id] = null;
|
123 | }
|
124 |
|
125 | return cleared;
|
126 | },
|
127 |
|
128 | /**
|
129 | * Whether the given animation is still running.
|
130 | *
|
131 | * @param id {Integer} Unique animation ID
|
132 | * @return {Boolean} Whether the animation is still running
|
133 | */
|
134 | isRunning: function isRunning(id) {
|
135 | return running[id] != null;
|
136 | },
|
137 |
|
138 | /**
|
139 | * Start the animation.
|
140 | *
|
141 | * @param stepCallback {Function} Pointer to function which is executed on every step.
|
142 | * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
|
143 | * @param verifyCallback {Function} Executed before every animation step.
|
144 | * Signature of the method should be `function() { return continueWithAnimation; }`
|
145 | * @param completedCallback {Function}
|
146 | * Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
|
147 | * @param duration {Integer} Milliseconds to run the animation
|
148 | * @param easingMethod {Function} Pointer to easing function
|
149 | * Signature of the method should be `function(percent) { return modifiedValue; }`
|
150 | * @param root {Element ? document.body} Render root, when available. Used for internal
|
151 | * usage of requestAnimationFrame.
|
152 | * @return {Integer} Identifier of animation. Can be used to stop it any time.
|
153 | */
|
154 | start: function start(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
|
155 |
|
156 | var start = time();
|
157 | var lastFrame = start;
|
158 | var percent = 0;
|
159 | var dropCounter = 0;
|
160 | var id = counter++;
|
161 |
|
162 | if (!root) {
|
163 | root = document.body;
|
164 | }
|
165 |
|
166 | // Compacting running db automatically every few new animations
|
167 | if (id % 20 === 0) {
|
168 | var newRunning = {};
|
169 | for (var usedId in running) {
|
170 | newRunning[usedId] = true;
|
171 | }
|
172 | running = newRunning;
|
173 | }
|
174 |
|
175 | // This is the internal step method which is called every few milliseconds
|
176 | var step = function step(virtual) {
|
177 |
|
178 | // Normalize virtual value
|
179 | var render = virtual !== true;
|
180 |
|
181 | // Get current time
|
182 | var now = time();
|
183 |
|
184 | // Verification is executed before next animation step
|
185 | if (!running[id] || verifyCallback && !verifyCallback(id)) {
|
186 |
|
187 | running[id] = null;
|
188 | completedCallback && completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, false);
|
189 | return;
|
190 | }
|
191 |
|
192 | // For the current rendering to apply let's update omitted steps in memory.
|
193 | // This is important to bring internal state variables up-to-date with progress in time.
|
194 | if (render) {
|
195 |
|
196 | var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
|
197 | for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
|
198 | step(true);
|
199 | dropCounter++;
|
200 | }
|
201 | }
|
202 |
|
203 | // Compute percent value
|
204 | if (duration) {
|
205 | percent = (now - start) / duration;
|
206 | if (percent > 1) {
|
207 | percent = 1;
|
208 | }
|
209 | }
|
210 |
|
211 | // Execute step callback, then...
|
212 | var value = easingMethod ? easingMethod(percent) : percent;
|
213 | if ((stepCallback(value, now, render) === false || percent === 1) && render) {
|
214 | running[id] = null;
|
215 | completedCallback && completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, percent === 1 || duration == null);
|
216 | } else if (render) {
|
217 | lastFrame = now;
|
218 | core.effect.Animate.requestAnimationFrame(step, root);
|
219 | }
|
220 | };
|
221 |
|
222 | // Mark as running
|
223 | running[id] = true;
|
224 |
|
225 | // Init first step
|
226 | core.effect.Animate.requestAnimationFrame(step, root);
|
227 |
|
228 | // Return unique animation ID
|
229 | return id;
|
230 | }
|
231 | };
|
232 | })(global); |
\ | No newline at end of file |