UNPKG

6.49 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(function(shared, scope, testing) {
16
17 function groupChildDuration(node) {
18 return node._timing.delay + node.activeDuration + node._timing.endDelay;
19 }
20
21 function constructor(children, timingInput, id) {
22 this._id = id;
23 this._parent = null;
24 this.children = children || [];
25 this._reparent(this.children);
26 timingInput = shared.numericTimingToObject(timingInput);
27 this._timingInput = shared.cloneTimingInput(timingInput);
28 this._timing = shared.normalizeTimingInput(timingInput, true);
29 this.timing = shared.makeTiming(timingInput, true, this);
30 this.timing._effect = this;
31
32 if (this._timing.duration === 'auto') {
33 this._timing.duration = this.activeDuration;
34 }
35 }
36
37 window.SequenceEffect = function() {
38 constructor.apply(this, arguments);
39 };
40
41 window.GroupEffect = function() {
42 constructor.apply(this, arguments);
43 };
44
45 constructor.prototype = {
46 _isAncestor: function(effect) {
47 var a = this;
48 while (a !== null) {
49 if (a == effect)
50 return true;
51 a = a._parent;
52 }
53 return false;
54 },
55 _rebuild: function() {
56 // Re-calculate durations for ancestors with specified duration 'auto'.
57 var node = this;
58 while (node) {
59 if (node.timing.duration === 'auto') {
60 node._timing.duration = node.activeDuration;
61 }
62 node = node._parent;
63 }
64 if (this._animation) {
65 this._animation._rebuildUnderlyingAnimation();
66 }
67 },
68 _reparent: function(newChildren) {
69 scope.removeMulti(newChildren);
70 for (var i = 0; i < newChildren.length; i++) {
71 newChildren[i]._parent = this;
72 }
73 },
74 _putChild: function(args, isAppend) {
75 var message = isAppend ? 'Cannot append an ancestor or self' : 'Cannot prepend an ancestor or self';
76 for (var i = 0; i < args.length; i++) {
77 if (this._isAncestor(args[i])) {
78 throw {
79 type: DOMException.HIERARCHY_REQUEST_ERR,
80 name: 'HierarchyRequestError',
81 message: message
82 };
83 }
84 }
85 var oldParents = [];
86 for (var i = 0; i < args.length; i++) {
87 isAppend ? this.children.push(args[i]) : this.children.unshift(args[i]);
88 }
89 this._reparent(args);
90 this._rebuild();
91 },
92 append: function() {
93 this._putChild(arguments, true);
94 },
95 prepend: function() {
96 this._putChild(arguments, false);
97 },
98 get parent() {
99 return this._parent;
100 },
101 get firstChild() {
102 return this.children.length ? this.children[0] : null;
103 },
104 get lastChild() {
105 return this.children.length ? this.children[this.children.length - 1] : null;
106 },
107 clone: function() {
108 var clonedTiming = shared.cloneTimingInput(this._timingInput);
109 var clonedChildren = [];
110 for (var i = 0; i < this.children.length; i++) {
111 clonedChildren.push(this.children[i].clone());
112 }
113 return (this instanceof GroupEffect) ?
114 new GroupEffect(clonedChildren, clonedTiming) :
115 new SequenceEffect(clonedChildren, clonedTiming);
116 },
117 remove: function() {
118 scope.removeMulti([this]);
119 }
120 };
121
122 window.SequenceEffect.prototype = Object.create(constructor.prototype);
123 Object.defineProperty(
124 window.SequenceEffect.prototype,
125 'activeDuration',
126 {
127 get: function() {
128 var total = 0;
129 this.children.forEach(function(child) {
130 total += groupChildDuration(child);
131 });
132 return Math.max(total, 0);
133 }
134 });
135
136 window.GroupEffect.prototype = Object.create(constructor.prototype);
137 Object.defineProperty(
138 window.GroupEffect.prototype,
139 'activeDuration',
140 {
141 get: function() {
142 var max = 0;
143 this.children.forEach(function(child) {
144 max = Math.max(max, groupChildDuration(child));
145 });
146 return max;
147 }
148 });
149
150 scope.newUnderlyingAnimationForGroup = function(group) {
151 var underlyingAnimation;
152 var timing = null;
153 var ticker = function(tf) {
154 var animation = underlyingAnimation._wrapper;
155 if (!animation) {
156 return;
157 }
158 if (animation.playState == 'pending') {
159 return;
160 }
161 if (!animation.effect) {
162 return;
163 }
164 if (tf == null) {
165 animation._removeChildAnimations();
166 return;
167 }
168
169 // If the group has a negative playback rate and is not fill backwards/both, then it should go
170 // out of effect when it reaches the start of its active interval (tf == 0). If it is fill
171 // backwards/both then it should stay in effect. calculateIterationProgress will return 0 in the
172 // backwards-filling case, and null otherwise.
173 if (tf == 0 && animation.playbackRate < 0) {
174 if (!timing) {
175 timing = shared.normalizeTimingInput(animation.effect.timing);
176 }
177 tf = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), -1, timing);
178 if (isNaN(tf) || tf == null) {
179 animation._forEachChild(function(child) {
180 child.currentTime = -1;
181 });
182 animation._removeChildAnimations();
183 return;
184 }
185 }
186 };
187
188 var underlyingEffect = new KeyframeEffect(null, [], group._timing, group._id);
189 underlyingEffect.onsample = ticker;
190 underlyingAnimation = scope.timeline._play(underlyingEffect);
191 return underlyingAnimation;
192 };
193
194 scope.bindAnimationForGroup = function(animation) {
195 animation._animation._wrapper = animation;
196 animation._isGroup = true;
197 scope.awaitStartTime(animation);
198 animation._constructChildAnimations();
199 animation._setExternalAnimation(animation);
200 };
201
202 scope.groupChildDuration = groupChildDuration;
203
204})(webAnimationsShared, webAnimationsNext, webAnimationsTesting);