Skip to content

Commit fbb1a15

Browse files
authored
Merge pull request #485 from alancutter/optimiseAnimationMutations
Avoid updating all animation effects on single animation mutation
2 parents 5b96c1e + 24f7a24 commit fbb1a15

File tree

3 files changed

+69
-20
lines changed

3 files changed

+69
-20
lines changed

src/animation.js

+27-6
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
this._paused = true;
9595
}
9696
this._tickCurrentTime(newTime, true);
97-
scope.invalidateEffects();
97+
scope.applyDirtiedAnimation(this);
9898
},
9999
get startTime() {
100100
return this._startTime;
@@ -107,7 +107,7 @@
107107
return;
108108
this._startTime = newTime;
109109
this._tickCurrentTime((this._timeline.currentTime - this._startTime) * this.playbackRate);
110-
scope.invalidateEffects();
110+
scope.applyDirtiedAnimation(this);
111111
},
112112
get playbackRate() {
113113
return this._playbackRate;
@@ -123,7 +123,7 @@
123123
this._finishedFlag = false;
124124
this._idle = false;
125125
this._ensureAlive();
126-
scope.invalidateEffects();
126+
scope.applyDirtiedAnimation(this);
127127
}
128128
if (oldCurrentTime != null) {
129129
this.currentTime = oldCurrentTime;
@@ -165,7 +165,7 @@
165165
this._finishedFlag = false;
166166
this._idle = false;
167167
this._ensureAlive();
168-
scope.invalidateEffects();
168+
scope.applyDirtiedAnimation(this);
169169
},
170170
pause: function() {
171171
if (!this._isFinished && !this._paused && !this._idle) {
@@ -183,21 +183,22 @@
183183
this.currentTime = this._playbackRate > 0 ? this._totalDuration : 0;
184184
this._startTime = this._totalDuration - this.currentTime;
185185
this._currentTimePending = false;
186-
scope.invalidateEffects();
186+
scope.applyDirtiedAnimation(this);
187187
},
188188
cancel: function() {
189189
if (!this._inEffect)
190190
return;
191191
this._inEffect = false;
192192
this._idle = true;
193193
this._paused = false;
194+
this._isFinished = true;
194195
this._finishedFlag = true;
195196
this._currentTime = 0;
196197
this._startTime = null;
197198
this._effect._update(null);
198199
// effects are invalid after cancellation as the animation state
199200
// needs to un-apply.
200-
scope.invalidateEffects();
201+
scope.applyDirtiedAnimation(this);
201202
},
202203
reverse: function() {
203204
this.playbackRate *= -1;
@@ -249,6 +250,26 @@
249250
get _needsTick() {
250251
return (this.playState in {'pending': 1, 'running': 1}) || !this._finishedFlag;
251252
},
253+
_targetAnimations: function() {
254+
var target = this._effect._target;
255+
if (!target._activeAnimations) {
256+
target._activeAnimations = [];
257+
}
258+
return target._activeAnimations;
259+
},
260+
_markTarget: function() {
261+
var animations = this._targetAnimations();
262+
if (animations.indexOf(this) === -1) {
263+
animations.push(this);
264+
}
265+
},
266+
_unmarkTarget: function() {
267+
var animations = this._targetAnimations();
268+
var index = animations.indexOf(this);
269+
if (index !== -1) {
270+
animations.splice(index, 1);
271+
}
272+
},
252273
};
253274

254275
if (WEB_ANIMATIONS_TESTING) {

src/keyframe-effect.js

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
keyframeEffect._hasSameTarget = function(otherTarget) {
4444
return target === otherTarget;
4545
};
46+
keyframeEffect._target = target;
4647
keyframeEffect._totalDuration = effectTime._totalDuration;
4748
keyframeEffect._id = id;
4849
return keyframeEffect;

src/tick.js

+41-14
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
rafCallbacks = [];
4040
if (t < timeline.currentTime)
4141
t = timeline.currentTime;
42-
tick(t, true);
42+
timeline._animations.sort(compareAnimations);
43+
timeline._animations = tick(t, true, timeline._animations)[0];
4344
processing.forEach(function(entry) { entry[1](t); });
4445
applyPendingEffects();
4546
_now = undefined;
@@ -63,7 +64,7 @@
6364
animation._timeline = this;
6465
this._animations.push(animation);
6566
scope.restart();
66-
scope.invalidateEffects();
67+
scope.applyDirtiedAnimation(animation);
6768
return animation;
6869
}
6970
};
@@ -92,8 +93,24 @@
9293
return hasRestartedThisFrame;
9394
};
9495

95-
scope.invalidateEffects = function() {
96-
tick(scope.timeline.currentTime, false);
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+
});
97114
applyPendingEffects();
98115
};
99116

@@ -105,41 +122,51 @@
105122

106123
var t60hz = 1000 / 60;
107124

108-
function tick(t, isAnimationFrame) {
125+
var inTick = false;
126+
function tick(t, isAnimationFrame, updatingAnimations) {
127+
inTick = true;
109128
hasRestartedThisFrame = false;
110129
var timeline = scope.timeline;
130+
111131
timeline.currentTime = t;
112-
timeline._animations.sort(compareAnimations);
113132
ticking = false;
114-
var updatingAnimations = timeline._animations;
115-
timeline._animations = [];
116133

117134
var newPendingClears = [];
118135
var newPendingEffects = [];
119-
updatingAnimations = updatingAnimations.filter(function(animation) {
136+
var activeAnimations = [];
137+
var inactiveAnimations = [];
138+
updatingAnimations.forEach(function(animation) {
120139
animation._tick(t, isAnimationFrame);
121140

122-
if (!animation._inEffect)
141+
if (!animation._inEffect) {
123142
newPendingClears.push(animation._effect);
124-
else
143+
animation._unmarkTarget();
144+
} else {
125145
newPendingEffects.push(animation._effect);
146+
animation._markTarget();
147+
}
126148

127149
if (animation._needsTick)
128150
ticking = true;
129151

130152
var alive = animation._inEffect || animation._needsTick;
131153
animation._inTimeline = alive;
132-
return alive;
154+
if (alive) {
155+
activeAnimations.push(animation);
156+
} else {
157+
inactiveAnimations.push(animation);
158+
}
133159
});
134160

135161
// FIXME: Should remove dupliactes from pendingEffects.
136162
pendingEffects.push.apply(pendingEffects, newPendingClears);
137163
pendingEffects.push.apply(pendingEffects, newPendingEffects);
138164

139-
timeline._animations.push.apply(timeline._animations, updatingAnimations);
140-
141165
if (ticking)
142166
requestAnimationFrame(function() {});
167+
168+
inTick = false;
169+
return [activeAnimations, inactiveAnimations];
143170
};
144171

145172
if (WEB_ANIMATIONS_TESTING) {

0 commit comments

Comments
 (0)