UNPKG

6.75 kBJavaScriptView Raw
1"use strict";
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