UNPKG

5.59 kBJavaScriptView Raw
1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15
16(function(shared, scope, testing) {
17 var originalRequestAnimationFrame = window.requestAnimationFrame;
18 var rafCallbacks = [];
19 var rafId = 0;
20 window.requestAnimationFrame = function(f) {
21 var id = rafId++;
22 if (rafCallbacks.length == 0 && !WEB_ANIMATIONS_TESTING) {
23 originalRequestAnimationFrame(processRafCallbacks);
24 }
25 rafCallbacks.push([id, f]);
26 return id;
27 };
28
29 window.cancelAnimationFrame = function(id) {
30 rafCallbacks.forEach(function(entry) {
31 if (entry[0] == id) {
32 entry[1] = function() {};
33 }
34 });
35 };
36
37 function processRafCallbacks(t) {
38 var processing = rafCallbacks;
39 rafCallbacks = [];
40 if (t < timeline.currentTime)
41 t = timeline.currentTime;
42 timeline._animations.sort(compareAnimations);
43 timeline._animations = tick(t, true, timeline._animations)[0];
44 processing.forEach(function(entry) { entry[1](t); });
45 applyPendingEffects();
46 _now = undefined;
47 }
48
49 function compareAnimations(leftAnimation, rightAnimation) {
50 return leftAnimation._sequenceNumber - rightAnimation._sequenceNumber;
51 }
52
53 function InternalTimeline() {
54 this._animations = [];
55 // Android 4.3 browser has window.performance, but not window.performance.now
56 this.currentTime = window.performance && performance.now ? performance.now() : 0;
57 };
58
59 InternalTimeline.prototype = {
60 _play: function(effect) {
61 effect._timing = shared.normalizeTimingInput(effect.timing);
62 var animation = new scope.Animation(effect);
63 animation._idle = false;
64 animation._timeline = this;
65 this._animations.push(animation);
66 scope.restart();
67 scope.applyDirtiedAnimation(animation);
68 return animation;
69 }
70 };
71
72 var _now = undefined;
73
74 if (WEB_ANIMATIONS_TESTING) {
75 var now = function() { return timeline.currentTime; };
76 } else {
77 var now = function() {
78 if (_now == undefined)
79 _now = window.performance && performance.now ? performance.now() : Date.now();
80 return _now;
81 };
82 }
83
84 var ticking = false;
85 var hasRestartedThisFrame = false;
86
87 scope.restart = function() {
88 if (!ticking) {
89 ticking = true;
90 requestAnimationFrame(function() {});
91 hasRestartedThisFrame = true;
92 }
93 return hasRestartedThisFrame;
94 };
95
96 // RAF is supposed to be the last script to occur before frame rendering but not
97 // all browsers behave like this. This function is for synchonously updating an
98 // animation's effects whenever its state is mutated by script to work around
99 // incorrect script execution ordering by the browser.
100 scope.applyDirtiedAnimation = function(animation) {
101 if (inTick) {
102 return;
103 }
104 animation._markTarget();
105 var animations = animation._targetAnimations();
106 animations.sort(compareAnimations);
107 var inactiveAnimations = tick(scope.timeline.currentTime, false, animations.slice())[1];
108 inactiveAnimations.forEach(function(animation) {
109 var index = timeline._animations.indexOf(animation);
110 if (index !== -1) {
111 timeline._animations.splice(index, 1);
112 }
113 });
114 applyPendingEffects();
115 };
116
117 var pendingEffects = [];
118 function applyPendingEffects() {
119 pendingEffects.forEach(function(f) { f(); });
120 pendingEffects.length = 0;
121 }
122
123 var t60hz = 1000 / 60;
124
125 var inTick = false;
126 function tick(t, isAnimationFrame, updatingAnimations) {
127 inTick = true;
128 hasRestartedThisFrame = false;
129 var timeline = scope.timeline;
130
131 timeline.currentTime = t;
132 ticking = false;
133
134 var newPendingClears = [];
135 var newPendingEffects = [];
136 var activeAnimations = [];
137 var inactiveAnimations = [];
138 updatingAnimations.forEach(function(animation) {
139 animation._tick(t, isAnimationFrame);
140
141 if (!animation._inEffect) {
142 newPendingClears.push(animation._effect);
143 animation._unmarkTarget();
144 } else {
145 newPendingEffects.push(animation._effect);
146 animation._markTarget();
147 }
148
149 if (animation._needsTick)
150 ticking = true;
151
152 var alive = animation._inEffect || animation._needsTick;
153 animation._inTimeline = alive;
154 if (alive) {
155 activeAnimations.push(animation);
156 } else {
157 inactiveAnimations.push(animation);
158 }
159 });
160
161 // FIXME: Should remove dupliactes from pendingEffects.
162 pendingEffects.push.apply(pendingEffects, newPendingClears);
163 pendingEffects.push.apply(pendingEffects, newPendingEffects);
164
165 if (ticking)
166 requestAnimationFrame(function() {});
167
168 inTick = false;
169 return [activeAnimations, inactiveAnimations];
170 };
171
172 if (WEB_ANIMATIONS_TESTING) {
173 testing.tick = function(t) { timeline.currentTime = t; processRafCallbacks(t); };
174 testing.isTicking = function() { return ticking; };
175 testing.setTicking = function(newVal) { ticking = newVal; };
176 }
177
178 var timeline = new InternalTimeline();
179 scope.timeline = timeline;
180
181})(webAnimationsShared, webAnimations1, webAnimationsTesting);