web animations update

This commit is contained in:
Adam Bradley
2015-08-23 15:24:46 -05:00
parent 26e0117a3f
commit 442050038b
56 changed files with 5528 additions and 22 deletions

View File

@ -0,0 +1,229 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
shared.sequenceNumber = 0;
var AnimationEvent = function(target, currentTime, timelineTime) {
this.target = target;
this.currentTime = currentTime;
this.timelineTime = timelineTime;
this.type = 'finish';
this.bubbles = false;
this.cancelable = false;
this.currentTarget = target;
this.defaultPrevented = false;
this.eventPhase = Event.AT_TARGET;
this.timeStamp = Date.now();
};
scope.Animation = function(effect) {
this._sequenceNumber = shared.sequenceNumber++;
this._currentTime = 0;
this._startTime = null;
this._paused = false;
this._playbackRate = 1;
this._inTimeline = true;
this._finishedFlag = false;
this.onfinish = null;
this._finishHandlers = [];
this._effect = effect;
this._inEffect = this._effect._update(0);
this._idle = true;
this._currentTimePending = false;
};
scope.Animation.prototype = {
_ensureAlive: function() {
// If an animation is playing backwards and is not fill backwards/both
// then it should go out of effect when it reaches the start of its
// active interval (currentTime == 0).
if (this.playbackRate < 0 && this.currentTime === 0) {
this._inEffect = this._effect._update(-1);
} else {
this._inEffect = this._effect._update(this.currentTime);
}
if (!this._inTimeline && (this._inEffect || !this._finishedFlag)) {
this._inTimeline = true;
scope.timeline._animations.push(this);
}
},
_tickCurrentTime: function(newTime, ignoreLimit) {
if (newTime != this._currentTime) {
this._currentTime = newTime;
if (this._isFinished && !ignoreLimit)
this._currentTime = this._playbackRate > 0 ? this._totalDuration : 0;
this._ensureAlive();
}
},
get currentTime() {
if (this._idle || this._currentTimePending)
return null;
return this._currentTime;
},
set currentTime(newTime) {
newTime = +newTime;
if (isNaN(newTime))
return;
scope.restart();
if (!this._paused && this._startTime != null) {
this._startTime = this._timeline.currentTime - newTime / this._playbackRate;
}
this._currentTimePending = false;
if (this._currentTime == newTime)
return;
this._tickCurrentTime(newTime, true);
scope.invalidateEffects();
},
get startTime() {
return this._startTime;
},
set startTime(newTime) {
newTime = +newTime;
if (isNaN(newTime))
return;
if (this._paused || this._idle)
return;
this._startTime = newTime;
this._tickCurrentTime((this._timeline.currentTime - this._startTime) * this.playbackRate);
scope.invalidateEffects();
},
get playbackRate() {
return this._playbackRate;
},
set playbackRate(value) {
if (value == this._playbackRate) {
return;
}
var oldCurrentTime = this.currentTime;
this._playbackRate = value;
this._startTime = null;
if (this.playState != 'paused' && this.playState != 'idle') {
this.play();
}
if (oldCurrentTime != null) {
this.currentTime = oldCurrentTime;
}
},
get _isFinished() {
return !this._idle && (this._playbackRate > 0 && this._currentTime >= this._totalDuration ||
this._playbackRate < 0 && this._currentTime <= 0);
},
get _totalDuration() { return this._effect._totalDuration; },
get playState() {
if (this._idle)
return 'idle';
if ((this._startTime == null && !this._paused && this.playbackRate != 0) || this._currentTimePending)
return 'pending';
if (this._paused)
return 'paused';
if (this._isFinished)
return 'finished';
return 'running';
},
play: function() {
this._paused = false;
if (this._isFinished || this._idle) {
this._currentTime = this._playbackRate > 0 ? 0 : this._totalDuration;
this._startTime = null;
scope.invalidateEffects();
}
this._finishedFlag = false;
scope.restart();
this._idle = false;
this._ensureAlive();
},
pause: function() {
if (!this._isFinished && !this._paused && !this._idle) {
this._currentTimePending = true;
}
this._startTime = null;
this._paused = true;
},
finish: function() {
if (this._idle)
return;
this.currentTime = this._playbackRate > 0 ? this._totalDuration : 0;
this._startTime = this._totalDuration - this.currentTime;
this._currentTimePending = false;
},
cancel: function() {
if (!this._inEffect)
return;
this._inEffect = false;
this._idle = true;
this.currentTime = 0;
this._startTime = null;
this._effect._update(null);
// effects are invalid after cancellation as the animation state
// needs to un-apply.
scope.invalidateEffects();
// in the absence of effect revalidation via getComputedStyle, we
// need a single tick to remove the effect of the cancelled
// animation.
scope.restart();
},
reverse: function() {
this.playbackRate *= -1;
this.play();
},
addEventListener: function(type, handler) {
if (typeof handler == 'function' && type == 'finish')
this._finishHandlers.push(handler);
},
removeEventListener: function(type, handler) {
if (type != 'finish')
return;
var index = this._finishHandlers.indexOf(handler);
if (index >= 0)
this._finishHandlers.splice(index, 1);
},
_fireEvents: function(baseTime) {
var finished = this._isFinished;
if ((finished || this._idle) && !this._finishedFlag) {
var event = new AnimationEvent(this, this._currentTime, baseTime);
var handlers = this._finishHandlers.concat(this.onfinish ? [this.onfinish] : []);
// IONIC HACK!
// CANNOT SETTIMEOUT OR ELSE THE BROWSER HAS A CHANCE TO
// RENDER AND CAUSES A FLICKER ON SAFAR
//setTimeout(function() {
handlers.forEach(function(handler) {
handler.call(event.target, event);
});
//}, 0);
}
this._finishedFlag = finished;
},
_tick: function(timelineTime) {
if (!this._idle && !this._paused) {
if (this._startTime == null)
this.startTime = timelineTime - this._currentTime / this.playbackRate;
else if (!this._isFinished)
this._tickCurrentTime((timelineTime - this._startTime) * this.playbackRate);
}
this._currentTimePending = false;
this._fireEvents(timelineTime);
return !this._idle && (this._inEffect || !this._finishedFlag);
},
};
if (WEB_ANIMATIONS_TESTING) {
testing.webAnimations1Animation = scope.Animation;
}
})(webAnimationsShared, webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,191 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
var styleAttributes = {
cssText: 1,
length: 1,
parentRule: 1,
};
var styleMethods = {
getPropertyCSSValue: 1,
getPropertyPriority: 1,
getPropertyValue: 1,
item: 1,
removeProperty: 1,
setProperty: 1,
};
var styleMutatingMethods = {
removeProperty: 1,
setProperty: 1,
};
function configureProperty(object, property, descriptor) {
descriptor.enumerable = true;
descriptor.configurable = true;
Object.defineProperty(object, property, descriptor);
}
function AnimatedCSSStyleDeclaration(element) {
WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration),
'Element must not already have an animated style attached.');
// Stores the inline style of the element on its behalf while the
// polyfill uses the element's inline style to simulate web animations.
// This is needed to fake regular inline style CSSOM access on the element.
this._surrogateStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style;
this._style = element.style;
this._length = 0;
this._isAnimatedProperty = {};
// Copy the inline style contents over to the surrogate.
for (var i = 0; i < this._style.length; i++) {
var property = this._style[i];
this._surrogateStyle[property] = this._style[property];
}
this._updateIndices();
}
AnimatedCSSStyleDeclaration.prototype = {
get cssText() {
return this._surrogateStyle.cssText;
},
set cssText(text) {
var isAffectedProperty = {};
for (var i = 0; i < this._surrogateStyle.length; i++) {
isAffectedProperty[this._surrogateStyle[i]] = true;
}
this._surrogateStyle.cssText = text;
this._updateIndices();
for (var i = 0; i < this._surrogateStyle.length; i++) {
isAffectedProperty[this._surrogateStyle[i]] = true;
}
for (var property in isAffectedProperty) {
if (!this._isAnimatedProperty[property]) {
this._style.setProperty(property, this._surrogateStyle.getPropertyValue(property));
}
}
},
get length() {
return this._surrogateStyle.length;
},
get parentRule() {
return this._style.parentRule;
},
// Mirror the indexed getters and setters of the surrogate style.
_updateIndices: function() {
while (this._length < this._surrogateStyle.length) {
Object.defineProperty(this, this._length, {
configurable: true,
enumerable: false,
get: (function(index) {
return function() { return this._surrogateStyle[index]; };
})(this._length)
});
this._length++;
}
while (this._length > this._surrogateStyle.length) {
this._length--;
Object.defineProperty(this, this._length, {
configurable: true,
enumerable: false,
value: undefined
});
}
},
_set: function(property, value) {
this._style[property] = value;
this._isAnimatedProperty[property] = true;
},
_clear: function(property) {
this._style[property] = this._surrogateStyle[property];
delete this._isAnimatedProperty[property];
},
};
// Wrap the style methods.
for (var method in styleMethods) {
AnimatedCSSStyleDeclaration.prototype[method] = (function(method, modifiesStyle) {
return function() {
var result = this._surrogateStyle[method].apply(this._surrogateStyle, arguments);
if (modifiesStyle) {
if (!this._isAnimatedProperty[arguments[0]])
this._style[method].apply(this._style, arguments);
this._updateIndices();
}
return result;
}
})(method, method in styleMutatingMethods);
}
// Wrap the style.cssProperty getters and setters.
for (var property in document.documentElement.style) {
if (property in styleAttributes || property in styleMethods) {
continue;
}
(function(property) {
configureProperty(AnimatedCSSStyleDeclaration.prototype, property, {
get: function() {
return this._surrogateStyle[property];
},
set: function(value) {
this._surrogateStyle[property] = value;
this._updateIndices();
if (!this._isAnimatedProperty[property])
this._style[property] = value;
}
});
})(property);
}
function ensureStyleIsPatched(element) {
if (element._webAnimationsPatchedStyle)
return;
var animatedStyle = new AnimatedCSSStyleDeclaration(element);
try {
configureProperty(element, 'style', { get: function() { return animatedStyle; } });
} catch (_) {
// iOS and older versions of Safari (pre v7) do not support overriding an element's
// style object. Animations will clobber any inline styles as a result.
element.style._set = function(property, value) {
element.style[property] = value;
};
element.style._clear = function(property) {
element.style[property] = '';
};
}
// We must keep a handle on the patched style to prevent it from getting GC'd.
element._webAnimationsPatchedStyle = element.style;
}
scope.apply = function(element, property, value) {
ensureStyleIsPatched(element);
element.style._set(scope.propertyName(property), value);
};
scope.clear = function(element, property) {
if (element._webAnimationsPatchedStyle) {
element.style._clear(scope.propertyName(property));
}
};
if (WEB_ANIMATIONS_TESTING)
testing.ensureStyleIsPatched = ensureStyleIsPatched;
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,25 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
scope.apply = function(element, property, value) {
element.style[scope.propertyName(property)] = value;
};
scope.clear = function(element, property) {
element.style[scope.propertyName(property)] = '';
};
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,57 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
function consumeLengthPercentOrAuto(string) {
return scope.consumeLengthOrPercent(string) || scope.consumeToken(/^auto/, string);
}
function parseBox(string) {
var result = scope.consumeList([
scope.ignore(scope.consumeToken.bind(null, /^rect/)),
scope.ignore(scope.consumeToken.bind(null, /^\(/)),
scope.consumeRepeated.bind(null, consumeLengthPercentOrAuto, /^,/),
scope.ignore(scope.consumeToken.bind(null, /^\)/)),
], string);
if (result && result[0].length == 4) {
return result[0];
}
}
function mergeComponent(left, right) {
if (left == 'auto' || right == 'auto') {
return [true, false, function(t) {
var result = t ? left : right;
if (result == 'auto') {
return 'auto';
}
// FIXME: There's probably a better way to turn a dimension back into a string.
var merged = scope.mergeDimensions(result, result);
return merged[2](merged[0]);
}];
}
return scope.mergeDimensions(left, right);
}
function wrap(result) {
return 'rect(' + result + ')';
}
var mergeBoxes = scope.mergeWrappedNestedRepeated.bind(null, wrap, mergeComponent, ', ');
scope.parseBox = parseBox;
scope.mergeBoxes = mergeBoxes;
scope.addPropertiesHandler(parseBox, mergeBoxes, ['clip']);
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,62 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
canvas.width = canvas.height = 1;
var context = canvas.getContext('2d');
function parseColor(string) {
string = string.trim();
// The context ignores invalid colors
context.fillStyle = '#000';
context.fillStyle = string;
var contextSerializedFillStyle = context.fillStyle;
context.fillStyle = '#fff';
context.fillStyle = string;
if (contextSerializedFillStyle != context.fillStyle)
return;
context.fillRect(0, 0, 1, 1);
var pixelColor = context.getImageData(0, 0, 1, 1).data;
context.clearRect(0, 0, 1, 1);
var alpha = pixelColor[3] / 255;
return [pixelColor[0] * alpha, pixelColor[1] * alpha, pixelColor[2] * alpha, alpha];
}
function mergeColors(left, right) {
return [left, right, function(x) {
function clamp(v) {
return Math.max(0, Math.min(255, v));
}
if (x[3]) {
for (var i = 0; i < 3; i++)
x[i] = Math.round(clamp(x[i] / x[3]));
}
x[3] = scope.numberToString(scope.clamp(0, 1, x[3]));
return 'rgba(' + x.join(',') + ')';
}];
}
scope.addPropertiesHandler(parseColor, mergeColors,
['background-color', 'border-bottom-color', 'border-left-color', 'border-right-color',
'border-top-color', 'color', 'outline-color', 'text-decoration-color']);
scope.consumeColor = scope.consumeParenthesised.bind(null, parseColor);
scope.mergeColors = mergeColors;
if (WEB_ANIMATIONS_TESTING) {
testing.parseColor = parseColor;
}
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,43 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared) {
var silenced = {};
shared.isDeprecated = function(feature, date, advice, plural) {
var auxVerb = plural ? 'are' : 'is';
var today = new Date();
var expiry = new Date(date);
expiry.setMonth(expiry.getMonth() + 3); // 3 months grace period
if (today < expiry) {
if (!(feature in silenced)) {
console.warn('Web Animations: ' + feature + ' ' + auxVerb + ' deprecated and will stop working on ' + expiry.toDateString() + '. ' + advice);
}
silenced[feature] = true;
return false;
} else {
return true;
}
};
shared.deprecated = function(feature, date, advice, plural) {
var auxVerb = plural ? 'are' : 'is';
if (shared.isDeprecated(feature, date, advice, plural)) {
throw new Error(feature + ' ' + auxVerb + ' no longer supported. ' + advice);
}
};
})(webAnimationsShared);

View File

@ -0,0 +1,16 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var WEB_ANIMATIONS_TESTING = false;
var webAnimationsTesting = null;

View File

@ -0,0 +1,167 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
function parseDimension(unitRegExp, string) {
string = string.trim().toLowerCase();
if (string == '0' && 'px'.search(unitRegExp) >= 0)
return {px: 0};
// If we have parenthesis, we're a calc and need to start with 'calc'.
if (!/^[^(]*$|^calc/.test(string))
return;
string = string.replace(/calc\(/g, '(');
// We tag units by prefixing them with 'U' (note that we are already
// lowercase) to prevent problems with types which are substrings of
// each other (although prefixes may be problematic!)
var matchedUnits = {};
string = string.replace(unitRegExp, function(match) {
matchedUnits[match] = null;
return 'U' + match;
});
var taggedUnitRegExp = 'U(' + unitRegExp.source + ')';
// Validating input is simply applying as many reductions as we can.
var typeCheck = string.replace(/[-+]?(\d*\.)?\d+/g, 'N')
.replace(new RegExp('N' + taggedUnitRegExp, 'g'), 'D')
.replace(/\s[+-]\s/g, 'O')
.replace(/\s/g, '');
var reductions = [/N\*(D)/g, /(N|D)[*/]N/g, /(N|D)O\1/g, /\((N|D)\)/g];
var i = 0;
while (i < reductions.length) {
if (reductions[i].test(typeCheck)) {
typeCheck = typeCheck.replace(reductions[i], '$1');
i = 0;
} else {
i++;
}
}
if (typeCheck != 'D')
return;
for (var unit in matchedUnits) {
var result = eval(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
if (!isFinite(result))
return;
matchedUnits[unit] = result;
}
return matchedUnits;
}
function mergeDimensionsNonNegative(left, right) {
return mergeDimensions(left, right, true);
}
function mergeDimensions(left, right, nonNegative) {
var units = [], unit;
for (unit in left)
units.push(unit);
for (unit in right) {
if (units.indexOf(unit) < 0)
units.push(unit);
}
left = units.map(function(unit) { return left[unit] || 0; });
right = units.map(function(unit) { return right[unit] || 0; });
return [left, right, function(values) {
var result = values.map(function(value, i) {
if (values.length == 1 && nonNegative) {
value = Math.max(value, 0);
}
// Scientific notation (e.g. 1e2) is not yet widely supported by browser vendors.
return scope.numberToString(value) + units[i];
}).join(' + ');
return values.length > 1 ? 'calc(' + result + ')' : result;
}];
}
var lengthUnits = 'px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc';
var parseLength = parseDimension.bind(null, new RegExp(lengthUnits, 'g'));
var parseLengthOrPercent = parseDimension.bind(null, new RegExp(lengthUnits + '|%', 'g'));
var parseAngle = parseDimension.bind(null, /deg|rad|grad|turn/g);
scope.parseLength = parseLength;
scope.parseLengthOrPercent = parseLengthOrPercent;
scope.consumeLengthOrPercent = scope.consumeParenthesised.bind(null, parseLengthOrPercent);
scope.parseAngle = parseAngle;
scope.mergeDimensions = mergeDimensions;
var consumeLength = scope.consumeParenthesised.bind(null, parseLength);
var consumeSizePair = scope.consumeRepeated.bind(undefined, consumeLength, /^/);
var consumeSizePairList = scope.consumeRepeated.bind(undefined, consumeSizePair, /^,/);
scope.consumeSizePairList = consumeSizePairList;
var parseSizePairList = function(input) {
var result = consumeSizePairList(input);
if (result && result[1] == '') {
return result[0];
}
};
var mergeNonNegativeSizePair = scope.mergeNestedRepeated.bind(undefined, mergeDimensionsNonNegative, ' ');
var mergeNonNegativeSizePairList = scope.mergeNestedRepeated.bind(undefined, mergeNonNegativeSizePair, ',');
scope.mergeNonNegativeSizePair = mergeNonNegativeSizePair;
scope.addPropertiesHandler(parseSizePairList, mergeNonNegativeSizePairList, [
'background-size'
]);
scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensionsNonNegative, [
'border-bottom-width',
'border-image-width',
'border-left-width',
'border-right-width',
'border-top-width',
'flex-basis',
'font-size',
'height',
'line-height',
'max-height',
'max-width',
'outline-width',
'width',
]);
scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensions, [
'border-bottom-left-radius',
'border-bottom-right-radius',
'border-top-left-radius',
'border-top-right-radius',
'bottom',
'left',
'letter-spacing',
'margin-bottom',
'margin-left',
'margin-right',
'margin-top',
'min-height',
'min-width',
'outline-offset',
'padding-bottom',
'padding-left',
'padding-right',
'padding-top',
'perspective',
'right',
'shape-margin',
'text-indent',
'top',
'vertical-align',
'word-spacing',
]);
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,98 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
var nullTarget = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var sequenceNumber = 0;
scope.bindAnimationForCustomEffect = function(animation) {
var target = animation.effect.target;
var effectFunction;
var isKeyframeEffect = typeof animation.effect.getFrames() == 'function';
if (isKeyframeEffect) {
effectFunction = animation.effect.getFrames();
} else {
effectFunction = animation.effect._onsample;
}
var timing = animation.effect.timing;
var last = null;
timing = shared.normalizeTimingInput(timing);
var callback = function() {
var t = callback._animation ? callback._animation.currentTime : null;
if (t !== null) {
t = shared.calculateTimeFraction(shared.calculateActiveDuration(timing), t, timing);
if (isNaN(t))
t = null;
}
// FIXME: There are actually more conditions under which the effectFunction
// should be called.
if (t !== last) {
if (isKeyframeEffect) {
effectFunction(t, target, animation.effect);
} else {
effectFunction(t, animation.effect, animation.effect._animation);
}
}
last = t;
};
callback._animation = animation;
callback._registered = false;
callback._sequenceNumber = sequenceNumber++;
animation._callback = callback;
register(callback);
};
var callbacks = [];
var ticking = false;
function register(callback) {
if (callback._registered)
return;
callback._registered = true;
callbacks.push(callback);
if (!ticking) {
ticking = true;
requestAnimationFrame(tick);
}
}
function tick(t) {
var updating = callbacks;
callbacks = [];
updating.sort(function(left, right) {
return left._sequenceNumber - right._sequenceNumber;
});
updating = updating.filter(function(callback) {
callback();
var playState = callback._animation ? callback._animation.playState : 'idle';
if (playState != 'running' && playState != 'pending')
callback._registered = false;
return callback._registered;
});
callbacks.push.apply(callbacks, updating);
if (callbacks.length) {
ticking = true;
requestAnimationFrame(tick);
} else {
ticking = false;
}
}
scope.Animation.prototype._register = function() {
if (this._callback)
register(this._callback);
};
})(webAnimationsShared, webAnimationsNext, webAnimationsTesting);

View File

@ -0,0 +1,19 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
window.Element.prototype.animate = function(effectInput, timingInput) {
return scope.timeline._play(scope.KeyframeEffect(this, effectInput, timingInput));
};
})(webAnimations1);

View File

@ -0,0 +1,42 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
function parse(string) {
var out = Number(string);
if (isNaN(out) || out < 100 || out > 900 || out % 100 !== 0) {
return;
}
return out;
}
function toCss(value) {
value = Math.round(value / 100) * 100;
value = scope.clamp(100, 900, value);
if (value === 400) {
return 'normal';
}
if (value === 700) {
return 'bold';
}
return String(value);
}
function merge(left, right) {
return [left, right, toCss];
}
scope.addPropertiesHandler(parse, merge, ['font-weight']);
})(webAnimations1);

View File

@ -0,0 +1,203 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
function groupChildDuration(node) {
return node._timing.delay + node.activeDuration + node._timing.endDelay;
}
function constructor(children, timingInput) {
this._parent = null;
this.children = children || [];
this._reparent(this.children);
timingInput = shared.numericTimingToObject(timingInput);
this._timingInput = shared.cloneTimingInput(timingInput);
this._timing = shared.normalizeTimingInput(timingInput, true);
this.timing = shared.makeTiming(timingInput, true, this);
this.timing._effect = this;
if (this._timing.duration === 'auto') {
this._timing.duration = this.activeDuration;
}
}
window.SequenceEffect = function() {
constructor.apply(this, arguments);
};
window.GroupEffect = function() {
constructor.apply(this, arguments);
};
constructor.prototype = {
_isAncestor: function(effect) {
var a = this;
while (a !== null) {
if (a == effect)
return true;
a = a._parent;
}
return false;
},
_rebuild: function() {
// Re-calculate durations for ancestors with specified duration 'auto'.
var node = this;
while (node) {
if (node.timing.duration === 'auto') {
node._timing.duration = node.activeDuration;
}
node = node._parent;
}
if (this._animation) {
this._animation._rebuildUnderlyingAnimation();
}
},
_reparent: function(newChildren) {
scope.removeMulti(newChildren);
for (var i = 0; i < newChildren.length; i++) {
newChildren[i]._parent = this;
}
},
_putChild: function(args, isAppend) {
var message = isAppend ? 'Cannot append an ancestor or self' : 'Cannot prepend an ancestor or self';
for (var i = 0; i < args.length; i++) {
if (this._isAncestor(args[i])) {
throw {
type: DOMException.HIERARCHY_REQUEST_ERR,
name: 'HierarchyRequestError',
message: message
};
}
}
var oldParents = [];
for (var i = 0; i < args.length; i++) {
isAppend ? this.children.push(args[i]) : this.children.unshift(args[i]);
}
this._reparent(args);
this._rebuild();
},
append: function() {
this._putChild(arguments, true);
},
prepend: function() {
this._putChild(arguments, false);
},
get parent() {
return this._parent;
},
get firstChild() {
return this.children.length ? this.children[0] : null;
},
get lastChild() {
return this.children.length ? this.children[this.children.length - 1] : null;
},
clone: function() {
var clonedTiming = shared.cloneTimingInput(this._timingInput);
var clonedChildren = [];
for (var i = 0; i < this.children.length; i++) {
clonedChildren.push(this.children[i].clone());
}
return (this instanceof GroupEffect) ?
new GroupEffect(clonedChildren, clonedTiming) :
new SequenceEffect(clonedChildren, clonedTiming);
},
remove: function() {
scope.removeMulti([this]);
}
};
window.SequenceEffect.prototype = Object.create(constructor.prototype);
Object.defineProperty(
window.SequenceEffect.prototype,
'activeDuration',
{
get: function() {
var total = 0;
this.children.forEach(function(child) {
total += groupChildDuration(child);
});
return Math.max(total, 0);
}
});
window.GroupEffect.prototype = Object.create(constructor.prototype);
Object.defineProperty(
window.GroupEffect.prototype,
'activeDuration',
{
get: function() {
var max = 0;
this.children.forEach(function(child) {
max = Math.max(max, groupChildDuration(child));
});
return max;
}
});
scope.newUnderlyingAnimationForGroup = function(group) {
var underlyingAnimation;
var timing = null;
var ticker = function(tf) {
var animation = underlyingAnimation._wrapper;
if (!animation) {
return;
}
if (animation.playState == 'pending') {
return;
}
if (!animation.effect) {
return;
}
if (tf == null) {
animation._removeChildAnimations();
return;
}
// If the group has a negative playback rate and is not fill backwards/both, then it should go
// out of effect when it reaches the start of its active interval (tf == 0). If it is fill
// backwards/both then it should stay in effect. calculateTimeFraction will return 0 in the
// backwards-filling case, and null otherwise.
if (tf == 0 && animation.playbackRate < 0) {
if (!timing) {
timing = shared.normalizeTimingInput(animation.effect.timing);
}
tf = shared.calculateTimeFraction(shared.calculateActiveDuration(timing), -1, timing);
if (isNaN(tf) || tf == null) {
animation._forEachChild(function(child) {
child.currentTime = -1;
});
animation._removeChildAnimations();
return;
}
}
};
var underlyingEffect = new KeyframeEffect(null, [], group._timing);
underlyingEffect.onsample = ticker;
underlyingAnimation = scope.timeline._play(underlyingEffect);
return underlyingAnimation;
};
scope.bindAnimationForGroup = function(animation) {
animation._animation._wrapper = animation;
animation._isGroup = true;
scope.awaitStartTime(animation);
animation._constructChildAnimations();
animation._setExternalAnimation(animation);
};
scope.groupChildDuration = groupChildDuration;
})(webAnimationsShared, webAnimationsNext, webAnimationsTesting);

View File

@ -0,0 +1,177 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
// consume* functions return a 2 value array of [parsed-data, '' or not-yet consumed input]
// Regex should be anchored with /^
function consumeToken(regex, string) {
var result = regex.exec(string);
if (result) {
result = regex.ignoreCase ? result[0].toLowerCase() : result[0];
return [result, string.substr(result.length)];
}
}
function consumeTrimmed(consumer, string) {
string = string.replace(/^\s*/, '');
var result = consumer(string);
if (result) {
return [result[0], result[1].replace(/^\s*/, '')];
}
}
function consumeRepeated(consumer, separator, string) {
consumer = consumeTrimmed.bind(null, consumer);
var list = [];
while (true) {
var result = consumer(string);
if (!result) {
return [list, string];
}
list.push(result[0]);
string = result[1];
result = consumeToken(separator, string);
if (!result || result[1] == '') {
return [list, string];
}
string = result[1];
}
}
// Consumes a token or expression with balanced parentheses
function consumeParenthesised(parser, string) {
var nesting = 0;
for (var n = 0; n < string.length; n++) {
if (/\s|,/.test(string[n]) && nesting == 0) {
break;
} else if (string[n] == '(') {
nesting++;
} else if (string[n] == ')') {
nesting--;
if (nesting == 0)
n++;
if (nesting <= 0)
break;
}
}
var parsed = parser(string.substr(0, n));
return parsed == undefined ? undefined : [parsed, string.substr(n)];
}
function lcm(a, b) {
var c = a;
var d = b;
while (c && d)
c > d ? c %= d : d %= c;
c = (a * b) / (c + d);
return c;
}
function ignore(value) {
return function(input) {
var result = value(input);
if (result)
result[0] = undefined;
return result;
}
}
function optional(value, defaultValue) {
return function(input) {
var result = value(input);
if (result)
return result;
return [defaultValue, input];
}
}
function consumeList(list, input) {
var output = [];
for (var i = 0; i < list.length; i++) {
var result = scope.consumeTrimmed(list[i], input);
if (!result || result[0] == '')
return;
if (result[0] !== undefined)
output.push(result[0]);
input = result[1];
}
if (input == '') {
return output;
}
}
function mergeWrappedNestedRepeated(wrap, nestedMerge, separator, left, right) {
var matchingLeft = [];
var matchingRight = [];
var reconsititution = [];
var length = lcm(left.length, right.length);
for (var i = 0; i < length; i++) {
var thing = nestedMerge(left[i % left.length], right[i % right.length]);
if (!thing) {
return;
}
matchingLeft.push(thing[0]);
matchingRight.push(thing[1]);
reconsititution.push(thing[2]);
}
return [matchingLeft, matchingRight, function(positions) {
var result = positions.map(function(position, i) {
return reconsititution[i](position);
}).join(separator);
return wrap ? wrap(result) : result;
}];
}
function mergeList(left, right, list) {
var lefts = [];
var rights = [];
var functions = [];
var j = 0;
for (var i = 0; i < list.length; i++) {
if (typeof list[i] == 'function') {
var result = list[i](left[j], right[j++]);
lefts.push(result[0]);
rights.push(result[1]);
functions.push(result[2]);
} else {
(function(pos) {
lefts.push(false);
rights.push(false);
functions.push(function() { return list[pos]; });
})(i);
}
}
return [lefts, rights, function(results) {
var result = '';
for (var i = 0; i < results.length; i++) {
result += functions[i](results[i]);
}
return result;
}];
}
scope.consumeToken = consumeToken;
scope.consumeTrimmed = consumeTrimmed;
scope.consumeRepeated = consumeRepeated;
scope.consumeParenthesised = consumeParenthesised;
scope.ignore = ignore;
scope.optional = optional;
scope.consumeList = consumeList;
scope.mergeNestedRepeated = mergeWrappedNestedRepeated.bind(null, null);
scope.mergeWrappedNestedRepeated = mergeWrappedNestedRepeated;
scope.mergeList = mergeList;
})(webAnimations1);

View File

@ -0,0 +1,49 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
function interpolate(from, to, f) {
if ((typeof from == 'number') && (typeof to == 'number')) {
return from * (1 - f) + to * f;
}
if ((typeof from == 'boolean') && (typeof to == 'boolean')) {
return f < 0.5 ? from : to;
}
WEB_ANIMATIONS_TESTING && console.assert(
Array.isArray(from) && Array.isArray(to),
'If interpolation arguments are not numbers or bools they must be arrays');
if (from.length == to.length) {
var r = [];
for (var i = 0; i < from.length; i++) {
r.push(interpolate(from[i], to[i], f));
}
return r;
}
throw 'Mismatched interpolation arguments ' + from + ':' + to;
}
scope.Interpolation = function(from, to, convertToString) {
return function(f) {
return convertToString(interpolate(from, to, f));
}
};
if (WEB_ANIMATIONS_TESTING) {
testing.interpolate = interpolate;
}
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,177 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
var disassociate = function(effect) {
effect._animation = undefined;
if (effect instanceof window.SequenceEffect || effect instanceof window.GroupEffect) {
for (var i = 0; i < effect.children.length; i++) {
disassociate(effect.children[i]);
}
}
};
scope.removeMulti = function(effects) {
var oldParents = [];
for (var i = 0; i < effects.length; i++) {
var effect = effects[i];
if (effect._parent) {
if (oldParents.indexOf(effect._parent) == -1) {
oldParents.push(effect._parent);
}
effect._parent.children.splice(effect._parent.children.indexOf(effect), 1);
effect._parent = null;
disassociate(effect);
} else if (effect._animation && (effect._animation.effect == effect)) {
effect._animation.cancel();
effect._animation.effect = new KeyframeEffect(null, []);
if (effect._animation._callback) {
effect._animation._callback._animation = null;
}
effect._animation._rebuildUnderlyingAnimation();
disassociate(effect);
}
}
for (i = 0; i < oldParents.length; i++) {
oldParents[i]._rebuild();
}
};
function KeyframeList(effectInput) {
this._frames = shared.normalizeKeyframes(effectInput);
}
scope.KeyframeEffect = function(target, effectInput, timingInput) {
this.target = target;
this._parent = null;
timingInput = shared.numericTimingToObject(timingInput);
this._timingInput = shared.cloneTimingInput(timingInput);
this._timing = shared.normalizeTimingInput(timingInput);
this.timing = shared.makeTiming(timingInput, false, this);
this.timing._effect = this;
if (typeof effectInput == 'function') {
shared.deprecated('Custom KeyframeEffect', '2015-06-22', 'Use KeyframeEffect.onsample instead.');
this._normalizedKeyframes = effectInput;
} else {
this._normalizedKeyframes = new KeyframeList(effectInput);
}
this._keyframes = effectInput;
this.activeDuration = shared.calculateActiveDuration(this._timing);
return this;
};
scope.KeyframeEffect.prototype = {
getFrames: function() {
if (typeof this._normalizedKeyframes == 'function')
return this._normalizedKeyframes;
return this._normalizedKeyframes._frames;
},
set onsample(callback) {
if (typeof this.getFrames() == 'function') {
throw new Error('Setting onsample on custom effect KeyframeEffect is not supported.');
}
this._onsample = callback;
if (this._animation) {
this._animation._rebuildUnderlyingAnimation();
}
},
get parent() {
return this._parent;
},
clone: function() {
if (typeof this.getFrames() == 'function') {
throw new Error('Cloning custom effects is not supported.');
}
var clone = new KeyframeEffect(this.target, [], shared.cloneTimingInput(this._timingInput));
clone._normalizedKeyframes = this._normalizedKeyframes;
clone._keyframes = this._keyframes;
return clone;
},
remove: function() {
scope.removeMulti([this]);
}
};
var originalElementAnimate = Element.prototype.animate;
Element.prototype.animate = function(effectInput, timing) {
return scope.timeline._play(new scope.KeyframeEffect(this, effectInput, timing));
};
var nullTarget = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
scope.newUnderlyingAnimationForKeyframeEffect = function(keyframeEffect) {
if (keyframeEffect) {
var target = keyframeEffect.target || nullTarget;
var keyframes = keyframeEffect._keyframes;
if (typeof keyframes == 'function') {
keyframes = [];
}
var timing = keyframeEffect._timingInput;
} else {
var target = nullTarget;
var keyframes = [];
var timing = 0;
}
return originalElementAnimate.apply(target, [keyframes, timing]);
};
// TODO: Remove this once we remove support for custom KeyframeEffects.
scope.bindAnimationForKeyframeEffect = function(animation) {
if (animation.effect && typeof animation.effect._normalizedKeyframes == 'function') {
scope.bindAnimationForCustomEffect(animation);
}
};
var pendingGroups = [];
scope.awaitStartTime = function(groupAnimation) {
if (groupAnimation.startTime !== null || !groupAnimation._isGroup)
return;
if (pendingGroups.length == 0) {
requestAnimationFrame(updatePendingGroups);
}
pendingGroups.push(groupAnimation);
};
function updatePendingGroups() {
var updated = false;
while (pendingGroups.length) {
var group = pendingGroups.shift();
group._updateChildren();
updated = true;
}
return updated;
}
var originalGetComputedStyle = window.getComputedStyle;
Object.defineProperty(window, 'getComputedStyle', {
configurable: true,
enumerable: true,
value: function() {
window.document.timeline._updateAnimationsPromises();
var result = originalGetComputedStyle.apply(this, arguments);
if (updatePendingGroups())
result = originalGetComputedStyle.apply(this, arguments);
window.document.timeline._updateAnimationsPromises();
return result;
},
});
window.KeyframeEffect = scope.KeyframeEffect;
window.Element.prototype.getAnimations = function() {
return document.timeline.getAnimations().filter(function(animation) {
return animation.effect !== null && animation.effect.target == this;
}.bind(this));
};
}(webAnimationsShared, webAnimationsNext, webAnimationsTesting));

View File

@ -0,0 +1,80 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
function EffectTime(timing) {
var timeFraction = 0;
var activeDuration = shared.calculateActiveDuration(timing);
var effectTime = function(localTime) {
return shared.calculateTimeFraction(activeDuration, localTime, timing);
};
effectTime._totalDuration = timing.delay + activeDuration + timing.endDelay;
effectTime._isCurrent = function(localTime) {
var phase = shared.calculatePhase(activeDuration, localTime, timing);
return phase === PhaseActive || phase === PhaseBefore;
};
return effectTime;
}
scope.KeyframeEffect = function(target, effectInput, timingInput) {
var effectTime = EffectTime(shared.normalizeTimingInput(timingInput));
var interpolations = scope.convertEffectInput(effectInput);
var timeFraction;
var keyframeEffect = function() {
WEB_ANIMATIONS_TESTING && console.assert(typeof timeFraction !== 'undefined');
interpolations(target, timeFraction);
};
// Returns whether the keyframeEffect is in effect or not after the timing update.
keyframeEffect._update = function(localTime) {
timeFraction = effectTime(localTime);
return timeFraction !== null;
};
keyframeEffect._clear = function() {
interpolations(target, null);
};
keyframeEffect._hasSameTarget = function(otherTarget) {
return target === otherTarget;
};
keyframeEffect._isCurrent = effectTime._isCurrent;
keyframeEffect._totalDuration = effectTime._totalDuration;
return keyframeEffect;
};
scope.NullEffect = function(clear) {
var nullEffect = function() {
if (clear) {
clear();
clear = null;
}
};
nullEffect._update = function() {
return null;
};
nullEffect._totalDuration = 0;
nullEffect._isCurrent = function() {
return false;
};
nullEffect._hasSameTarget = function() {
return false;
};
return nullEffect;
};
if (WEB_ANIMATIONS_TESTING) {
testing.webAnimations1KeyframeEffect = scope.KeyframeEffect;
testing.effectTime = EffectTime;
}
})(webAnimationsShared, webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,110 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
scope.convertEffectInput = function(effectInput) {
var keyframes = shared.normalizeKeyframes(effectInput);
var propertySpecificKeyframeGroups = makePropertySpecificKeyframeGroups(keyframes);
var interpolations = makeInterpolations(propertySpecificKeyframeGroups);
return function(target, fraction) {
if (fraction != null) {
interpolations.filter(function(interpolation) {
return (fraction <= 0 && interpolation.startTime == 0) ||
(fraction >= 1 && interpolation.endTime == 1) ||
(fraction >= interpolation.startTime && fraction <= interpolation.endTime);
}).forEach(function(interpolation) {
var offsetFraction = fraction - interpolation.startTime;
var localDuration = interpolation.endTime - interpolation.startTime;
var scaledLocalTime = localDuration == 0 ? 0 : interpolation.easing(offsetFraction / localDuration);
scope.apply(target, interpolation.property, interpolation.interpolation(scaledLocalTime));
});
} else {
for (var property in propertySpecificKeyframeGroups)
if (property != 'offset' && property != 'easing' && property != 'composite')
scope.clear(target, property);
}
};
};
function makePropertySpecificKeyframeGroups(keyframes) {
var propertySpecificKeyframeGroups = {};
for (var i = 0; i < keyframes.length; i++) {
for (var member in keyframes[i]) {
if (member != 'offset' && member != 'easing' && member != 'composite') {
var propertySpecificKeyframe = {
offset: keyframes[i].offset,
easing: keyframes[i].easing,
value: keyframes[i][member]
};
propertySpecificKeyframeGroups[member] = propertySpecificKeyframeGroups[member] || [];
propertySpecificKeyframeGroups[member].push(propertySpecificKeyframe);
}
}
}
for (var groupName in propertySpecificKeyframeGroups) {
var group = propertySpecificKeyframeGroups[groupName];
if (group[0].offset != 0 || group[group.length - 1].offset != 1) {
throw {
type: DOMException.NOT_SUPPORTED_ERR,
name: 'NotSupportedError',
message: 'Partial keyframes are not supported'
};
}
}
return propertySpecificKeyframeGroups;
}
function makeInterpolations(propertySpecificKeyframeGroups) {
var interpolations = [];
for (var groupName in propertySpecificKeyframeGroups) {
var group = propertySpecificKeyframeGroups[groupName];
for (var i = 0; i < group.length - 1; i++) {
var startTime = group[i].offset;
var endTime = group[i + 1].offset;
var startValue = group[i].value;
var endValue = group[i + 1].value;
if (startTime == endTime) {
if (endTime == 1) {
startValue = endValue;
} else {
endValue = startValue;
}
}
interpolations.push({
startTime: startTime,
endTime: endTime,
easing: group[i].easing,
property: groupName,
interpolation: scope.propertyInterpolation(groupName, startValue, endValue)
});
}
}
interpolations.sort(function(leftInterpolation, rightInterpolation) {
return leftInterpolation.startTime - rightInterpolation.startTime;
});
return interpolations;
}
if (WEB_ANIMATIONS_TESTING) {
testing.makePropertySpecificKeyframeGroups = makePropertySpecificKeyframeGroups;
testing.makeInterpolations = makeInterpolations;
}
})(webAnimationsShared, webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,446 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
var decomposeMatrix = (function() {
function determinant(m) {
return m[0][0] * m[1][1] * m[2][2] +
m[1][0] * m[2][1] * m[0][2] +
m[2][0] * m[0][1] * m[1][2] -
m[0][2] * m[1][1] * m[2][0] -
m[1][2] * m[2][1] * m[0][0] -
m[2][2] * m[0][1] * m[1][0];
}
// from Wikipedia:
//
// [A B]^-1 = [A^-1 + A^-1B(D - CA^-1B)^-1CA^-1 -A^-1B(D - CA^-1B)^-1]
// [C D] [-(D - CA^-1B)^-1CA^-1 (D - CA^-1B)^-1 ]
//
// Therefore
//
// [A [0]]^-1 = [A^-1 [0]]
// [C 1 ] [ -CA^-1 1 ]
function inverse(m) {
var iDet = 1 / determinant(m);
var a = m[0][0], b = m[0][1], c = m[0][2];
var d = m[1][0], e = m[1][1], f = m[1][2];
var g = m[2][0], h = m[2][1], k = m[2][2];
var Ainv = [
[(e * k - f * h) * iDet, (c * h - b * k) * iDet,
(b * f - c * e) * iDet, 0],
[(f * g - d * k) * iDet, (a * k - c * g) * iDet,
(c * d - a * f) * iDet, 0],
[(d * h - e * g) * iDet, (g * b - a * h) * iDet,
(a * e - b * d) * iDet, 0]
];
var lastRow = [];
for (var i = 0; i < 3; i++) {
var val = 0;
for (var j = 0; j < 3; j++) {
val += m[3][j] * Ainv[j][i];
}
lastRow.push(val);
}
lastRow.push(1);
Ainv.push(lastRow);
return Ainv;
}
function transposeMatrix4(m) {
return [[m[0][0], m[1][0], m[2][0], m[3][0]],
[m[0][1], m[1][1], m[2][1], m[3][1]],
[m[0][2], m[1][2], m[2][2], m[3][2]],
[m[0][3], m[1][3], m[2][3], m[3][3]]];
}
function multVecMatrix(v, m) {
var result = [];
for (var i = 0; i < 4; i++) {
var val = 0;
for (var j = 0; j < 4; j++) {
val += v[j] * m[j][i];
}
result.push(val);
}
return result;
}
function normalize(v) {
var len = length(v);
return [v[0] / len, v[1] / len, v[2] / len];
}
function length(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
function combine(v1, v2, v1s, v2s) {
return [v1s * v1[0] + v2s * v2[0], v1s * v1[1] + v2s * v2[1],
v1s * v1[2] + v2s * v2[2]];
}
function cross(v1, v2) {
return [v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0]];
}
function decomposeMatrix(matrix) {
var m3d = [
matrix.slice(0, 4),
matrix.slice(4, 8),
matrix.slice(8, 12),
matrix.slice(12, 16)
];
// skip normalization step as m3d[3][3] should always be 1
if (m3d[3][3] !== 1) {
return null;
}
var perspectiveMatrix = [];
for (var i = 0; i < 4; i++) {
perspectiveMatrix.push(m3d[i].slice());
}
for (var i = 0; i < 3; i++) {
perspectiveMatrix[i][3] = 0;
}
if (determinant(perspectiveMatrix) === 0) {
return false;
}
var rhs = [];
var perspective;
if (m3d[0][3] || m3d[1][3] || m3d[2][3]) {
rhs.push(m3d[0][3]);
rhs.push(m3d[1][3]);
rhs.push(m3d[2][3]);
rhs.push(m3d[3][3]);
var inversePerspectiveMatrix = inverse(perspectiveMatrix);
var transposedInversePerspectiveMatrix =
transposeMatrix4(inversePerspectiveMatrix);
perspective = multVecMatrix(rhs, transposedInversePerspectiveMatrix);
} else {
perspective = [0, 0, 0, 1];
}
var translate = m3d[3].slice(0, 3);
var row = [];
row.push(m3d[0].slice(0, 3));
var scale = [];
scale.push(length(row[0]));
row[0] = normalize(row[0]);
var skew = [];
row.push(m3d[1].slice(0, 3));
skew.push(dot(row[0], row[1]));
row[1] = combine(row[1], row[0], 1.0, -skew[0]);
scale.push(length(row[1]));
row[1] = normalize(row[1]);
skew[0] /= scale[1];
row.push(m3d[2].slice(0, 3));
skew.push(dot(row[0], row[2]));
row[2] = combine(row[2], row[0], 1.0, -skew[1]);
skew.push(dot(row[1], row[2]));
row[2] = combine(row[2], row[1], 1.0, -skew[2]);
scale.push(length(row[2]));
row[2] = normalize(row[2]);
skew[1] /= scale[2];
skew[2] /= scale[2];
var pdum3 = cross(row[1], row[2]);
if (dot(row[0], pdum3) < 0) {
for (var i = 0; i < 3; i++) {
scale[i] *= -1;
row[i][0] *= -1;
row[i][1] *= -1;
row[i][2] *= -1;
}
}
var t = row[0][0] + row[1][1] + row[2][2] + 1;
var s;
var quaternion;
if (t > 1e-4) {
s = 0.5 / Math.sqrt(t);
quaternion = [
(row[2][1] - row[1][2]) * s,
(row[0][2] - row[2][0]) * s,
(row[1][0] - row[0][1]) * s,
0.25 / s
];
} else if (row[0][0] > row[1][1] && row[0][0] > row[2][2]) {
s = Math.sqrt(1 + row[0][0] - row[1][1] - row[2][2]) * 2.0;
quaternion = [
0.25 * s,
(row[0][1] + row[1][0]) / s,
(row[0][2] + row[2][0]) / s,
(row[2][1] - row[1][2]) / s
];
} else if (row[1][1] > row[2][2]) {
s = Math.sqrt(1.0 + row[1][1] - row[0][0] - row[2][2]) * 2.0;
quaternion = [
(row[0][1] + row[1][0]) / s,
0.25 * s,
(row[1][2] + row[2][1]) / s,
(row[0][2] - row[2][0]) / s
];
} else {
s = Math.sqrt(1.0 + row[2][2] - row[0][0] - row[1][1]) * 2.0;
quaternion = [
(row[0][2] + row[2][0]) / s,
(row[1][2] + row[2][1]) / s,
0.25 * s,
(row[1][0] - row[0][1]) / s
];
}
return [translate, scale, skew, quaternion, perspective];
}
return decomposeMatrix;
})();
function dot(v1, v2) {
var result = 0;
for (var i = 0; i < v1.length; i++) {
result += v1[i] * v2[i];
}
return result;
}
function multiplyMatrices(a, b) {
return [
a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]
];
}
function convertItemToMatrix(item) {
switch (item.t) {
case 'rotatex':
var rads = item.d[0].rad || 0;
var degs = item.d[0].deg || 0;
var angle = (degs * Math.PI / 180) + rads;
return [1, 0, 0, 0,
0, Math.cos(angle), Math.sin(angle), 0,
0, -Math.sin(angle), Math.cos(angle), 0,
0, 0, 0, 1];
case 'rotatey':
var rads = item.d[0].rad || 0;
var degs = item.d[0].deg || 0;
var angle = (degs * Math.PI / 180) + rads;
return [Math.cos(angle), 0, -Math.sin(angle), 0,
0, 1, 0, 0,
Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, 0, 1];
case 'rotate':
case 'rotatez':
var rads = item.d[0].rad || 0;
var degs = item.d[0].deg || 0;
var angle = (degs * Math.PI / 180) + rads;
return [Math.cos(angle), Math.sin(angle), 0, 0,
-Math.sin(angle), Math.cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'rotate3d':
var x = item.d[0];
var y = item.d[1];
var z = item.d[2];
var rads = item.d[3].rad || 0;
var degs = item.d[3].deg || 0;
var angle = (degs * Math.PI / 180) + rads;
var sqrLength = x * x + y * y + z * z;
if (sqrLength === 0) {
x = 1;
y = 0;
z = 0;
} else if (sqrLength !== 1) {
var length = Math.sqrt(sqrLength);
x /= length;
y /= length;
z /= length;
}
var s = Math.sin(angle / 2);
var sc = s * Math.cos(angle / 2);
var sq = s * s;
return [
1 - 2 * (y * y + z * z) * sq,
2 * (x * y * sq + z * sc),
2 * (x * z * sq - y * sc),
0,
2 * (x * y * sq - z * sc),
1 - 2 * (x * x + z * z) * sq,
2 * (y * z * sq + x * sc),
0,
2 * (x * z * sq + y * sc),
2 * (y * z * sq - x * sc),
1 - 2 * (x * x + y * y) * sq,
0,
0, 0, 0, 1
];
case 'scale':
return [item.d[0], 0, 0, 0,
0, item.d[1], 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'scalex':
return [item.d[0], 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'scaley':
return [1, 0, 0, 0,
0, item.d[0], 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'scalez':
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, item.d[0], 0,
0, 0, 0, 1];
case 'scale3d':
return [item.d[0], 0, 0, 0,
0, item.d[1], 0, 0,
0, 0, item.d[2], 0,
0, 0, 0, 1];
case 'skew':
var xDegs = item.d[0].deg || 0;
var xRads = item.d[0].rad || 0;
var yDegs = item.d[1].deg || 0;
var yRads = item.d[1].rad || 0;
var xAngle = (xDegs * Math.PI / 180) + xRads;
var yAngle = (yDegs * Math.PI / 180) + yRads;
return [1, Math.tan(yAngle), 0, 0,
Math.tan(xAngle), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'skewx':
var rads = item.d[0].rad || 0;
var degs = item.d[0].deg || 0;
var angle = (degs * Math.PI / 180) + rads;
return [1, 0, 0, 0,
Math.tan(angle), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'skewy':
var rads = item.d[0].rad || 0;
var degs = item.d[0].deg || 0;
var angle = (degs * Math.PI / 180) + rads;
return [1, Math.tan(angle), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
case 'translate':
var x = item.d[0].px || 0;
var y = item.d[1].px || 0;
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, 0, 1];
case 'translatex':
var x = item.d[0].px || 0;
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, 0, 0, 1];
case 'translatey':
var y = item.d[0].px || 0;
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, y, 0, 1];
case 'translatez':
var z = item.d[0].px || 0;
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, z, 1];
case 'translate3d':
var x = item.d[0].px || 0;
var y = item.d[1].px || 0;
var z = item.d[2].px || 0;
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1];
case 'perspective':
var p = item.d[0].px ? (-1 / item.d[0].px) : 0;
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, p,
0, 0, 0, 1];
case 'matrix':
return [item.d[0], item.d[1], 0, 0,
item.d[2], item.d[3], 0, 0,
0, 0, 1, 0,
item.d[4], item.d[5], 0, 1];
case 'matrix3d':
return item.d;
default:
WEB_ANIMATIONS_TESTING && console.assert(false, 'Transform item type ' + item.t +
' conversion to matrix not yet implemented.');
}
}
function convertToMatrix(transformList) {
if (transformList.length === 0) {
return [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
}
return transformList.map(convertItemToMatrix).reduce(multiplyMatrices);
}
function makeMatrixDecomposition(transformList) {
return [decomposeMatrix(convertToMatrix(transformList))];
}
scope.dot = dot;
scope.makeMatrixDecomposition = makeMatrixDecomposition;
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,130 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
var composeMatrix = (function() {
function multiply(a, b) {
var result = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
for (var k = 0; k < 4; k++) {
result[i][j] += b[i][k] * a[k][j];
}
}
}
return result;
}
function is2D(m) {
return (
m[0][2] == 0 &&
m[0][3] == 0 &&
m[1][2] == 0 &&
m[1][3] == 0 &&
m[2][0] == 0 &&
m[2][1] == 0 &&
m[2][2] == 1 &&
m[2][3] == 0 &&
m[3][2] == 0 &&
m[3][3] == 1);
}
function composeMatrix(translate, scale, skew, quat, perspective) {
var matrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
for (var i = 0; i < 4; i++) {
matrix[i][3] = perspective[i];
}
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
matrix[3][i] += translate[j] * matrix[j][i];
}
}
var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
var rotMatrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
rotMatrix[0][0] = 1 - 2 * (y * y + z * z);
rotMatrix[0][1] = 2 * (x * y - z * w);
rotMatrix[0][2] = 2 * (x * z + y * w);
rotMatrix[1][0] = 2 * (x * y + z * w);
rotMatrix[1][1] = 1 - 2 * (x * x + z * z);
rotMatrix[1][2] = 2 * (y * z - x * w);
rotMatrix[2][0] = 2 * (x * z - y * w);
rotMatrix[2][1] = 2 * (y * z + x * w);
rotMatrix[2][2] = 1 - 2 * (x * x + y * y);
matrix = multiply(matrix, rotMatrix);
var temp = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
if (skew[2]) {
temp[2][1] = skew[2];
matrix = multiply(matrix, temp);
}
if (skew[1]) {
temp[2][1] = 0;
temp[2][0] = skew[0];
matrix = multiply(matrix, temp);
}
if (skew[0]) {
temp[2][0] = 0;
temp[1][0] = skew[0];
matrix = multiply(matrix, temp);
}
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
matrix[i][j] *= scale[i];
}
}
if (is2D(matrix)) {
return [matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1], matrix[3][0], matrix[3][1]];
}
return matrix[0].concat(matrix[1], matrix[2], matrix[3]);
}
return composeMatrix;
})();
function clamp(x, min, max) {
return Math.max(Math.min(x, max), min);
};
function quat(fromQ, toQ, f) {
var product = scope.dot(fromQ, toQ);
product = clamp(product, -1.0, 1.0);
var quat = [];
if (product === 1.0) {
quat = fromQ;
} else {
var theta = Math.acos(product);
var w = Math.sin(f * theta) * 1 / Math.sqrt(1 - product * product);
for (var i = 0; i < 4; i++) {
quat.push(fromQ[i] * (Math.cos(f * theta) - product * w) +
toQ[i] * w);
}
}
return quat;
}
scope.composeMatrix = composeMatrix;
scope.quat = quat;
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,259 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, testing) {
var shorthandToLonghand = {
background: [
'backgroundImage',
'backgroundPosition',
'backgroundSize',
'backgroundRepeat',
'backgroundAttachment',
'backgroundOrigin',
'backgroundClip',
'backgroundColor'
],
border: [
'borderTopColor',
'borderTopStyle',
'borderTopWidth',
'borderRightColor',
'borderRightStyle',
'borderRightWidth',
'borderBottomColor',
'borderBottomStyle',
'borderBottomWidth',
'borderLeftColor',
'borderLeftStyle',
'borderLeftWidth'
],
borderBottom: [
'borderBottomWidth',
'borderBottomStyle',
'borderBottomColor'
],
borderColor: [
'borderTopColor',
'borderRightColor',
'borderBottomColor',
'borderLeftColor'
],
borderLeft: [
'borderLeftWidth',
'borderLeftStyle',
'borderLeftColor'
],
borderRadius: [
'borderTopLeftRadius',
'borderTopRightRadius',
'borderBottomRightRadius',
'borderBottomLeftRadius'
],
borderRight: [
'borderRightWidth',
'borderRightStyle',
'borderRightColor'
],
borderTop: [
'borderTopWidth',
'borderTopStyle',
'borderTopColor'
],
borderWidth: [
'borderTopWidth',
'borderRightWidth',
'borderBottomWidth',
'borderLeftWidth'
],
flex: [
'flexGrow',
'flexShrink',
'flexBasis'
],
font: [
'fontFamily',
'fontSize',
'fontStyle',
'fontVariant',
'fontWeight',
'lineHeight'
],
margin: [
'marginTop',
'marginRight',
'marginBottom',
'marginLeft'
],
outline: [
'outlineColor',
'outlineStyle',
'outlineWidth'
],
padding: [
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft'
]
};
var shorthandExpanderElem = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
var borderWidthAliases = {
thin: '1px',
medium: '3px',
thick: '5px'
};
var aliases = {
borderBottomWidth: borderWidthAliases,
borderLeftWidth: borderWidthAliases,
borderRightWidth: borderWidthAliases,
borderTopWidth: borderWidthAliases,
fontSize: {
'xx-small': '60%',
'x-small': '75%',
'small': '89%',
'medium': '100%',
'large': '120%',
'x-large': '150%',
'xx-large': '200%'
},
fontWeight: {
normal: '400',
bold: '700'
},
outlineWidth: borderWidthAliases,
textShadow: {
none: '0px 0px 0px transparent'
},
boxShadow: {
none: '0px 0px 0px 0px transparent'
}
};
function antiAlias(property, value) {
if (property in aliases) {
return aliases[property][value] || value;
}
return value;
}
// This delegates parsing shorthand value syntax to the browser.
function expandShorthandAndAntiAlias(property, value, result) {
var longProperties = shorthandToLonghand[property];
if (longProperties) {
shorthandExpanderElem.style[property] = value;
for (var i in longProperties) {
var longProperty = longProperties[i];
var longhandValue = shorthandExpanderElem.style[longProperty];
result[longProperty] = antiAlias(longProperty, longhandValue);
}
} else {
result[property] = antiAlias(property, value);
}
};
function normalizeKeyframes(effectInput) {
if (!Array.isArray(effectInput) && effectInput !== null)
throw new TypeError('Keyframes must be null or an array of keyframes');
if (effectInput == null)
return [];
var keyframes = effectInput.map(function(originalKeyframe) {
var keyframe = {};
for (var member in originalKeyframe) {
var memberValue = originalKeyframe[member];
if (member == 'offset') {
if (memberValue != null) {
memberValue = Number(memberValue);
if (!isFinite(memberValue))
throw new TypeError('keyframe offsets must be numbers.');
}
} else if (member == 'composite') {
throw {
type: DOMException.NOT_SUPPORTED_ERR,
name: 'NotSupportedError',
message: 'add compositing is not supported'
};
} else if (member == 'easing') {
memberValue = shared.toTimingFunction(memberValue);
} else {
memberValue = '' + memberValue;
}
expandShorthandAndAntiAlias(member, memberValue, keyframe);
}
if (keyframe.offset == undefined)
keyframe.offset = null;
if (keyframe.easing == undefined)
keyframe.easing = shared.toTimingFunction('linear');
return keyframe;
});
var everyFrameHasOffset = true;
var looselySortedByOffset = true;
var previousOffset = -Infinity;
for (var i = 0; i < keyframes.length; i++) {
var offset = keyframes[i].offset;
if (offset != null) {
if (offset < previousOffset) {
throw {
code: DOMException.INVALID_MODIFICATION_ERR,
name: 'InvalidModificationError',
message: 'Keyframes are not loosely sorted by offset. Sort or specify offsets.'
};
}
previousOffset = offset;
} else {
everyFrameHasOffset = false;
}
}
keyframes = keyframes.filter(function(keyframe) {
return keyframe.offset >= 0 && keyframe.offset <= 1;
});
function spaceKeyframes() {
var length = keyframes.length;
if (keyframes[length - 1].offset == null)
keyframes[length - 1].offset = 1;
if (length > 1 && keyframes[0].offset == null)
keyframes[0].offset = 0;
var previousIndex = 0;
var previousOffset = keyframes[0].offset;
for (var i = 1; i < length; i++) {
var offset = keyframes[i].offset;
if (offset != null) {
for (var j = 1; j < i - previousIndex; j++)
keyframes[previousIndex + j].offset = previousOffset + (offset - previousOffset) * j / (i - previousIndex);
previousIndex = i;
previousOffset = offset;
}
}
}
if (!everyFrameHasOffset)
spaceKeyframes();
return keyframes;
}
shared.normalizeKeyframes = normalizeKeyframes;
if (WEB_ANIMATIONS_TESTING) {
testing.normalizeKeyframes = normalizeKeyframes;
}
})(webAnimationsShared, webAnimationsTesting);

View File

@ -0,0 +1,70 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
function numberToString(x) {
return x.toFixed(3).replace('.000', '');
}
function clamp(min, max, x) {
return Math.min(max, Math.max(min, x));
}
function parseNumber(string) {
if (/^\s*[-+]?(\d*\.)?\d+\s*$/.test(string))
return Number(string);
}
function mergeNumbers(left, right) {
return [left, right, numberToString];
}
// FIXME: This should probably go in it's own handler.
function mergeFlex(left, right) {
if (left == 0)
return;
return clampedMergeNumbers(0, Infinity)(left, right);
}
function mergePositiveIntegers(left, right) {
return [left, right, function(x) {
return Math.round(clamp(1, Infinity, x));
}];
}
function clampedMergeNumbers(min, max) {
return function(left, right) {
return [left, right, function(x) {
return numberToString(clamp(min, max, x));
}];
};
}
function round(left, right) {
return [left, right, Math.round];
}
scope.clamp = clamp;
scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, Infinity), ['border-image-width', 'line-height']);
scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, 1), ['opacity', 'shape-image-threshold']);
scope.addPropertiesHandler(parseNumber, mergeFlex, ['flex-grow', 'flex-shrink']);
scope.addPropertiesHandler(parseNumber, mergePositiveIntegers, ['orphans', 'widows']);
scope.addPropertiesHandler(parseNumber, round, ['z-index']);
scope.parseNumber = parseNumber;
scope.mergeNumbers = mergeNumbers;
scope.numberToString = numberToString;
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,117 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
function negateDimension(dimension) {
var result = {};
for (var k in dimension) {
result[k] = -dimension[k];
}
return result;
}
function consumeOffset(string) {
return scope.consumeToken(/^(left|center|right|top|bottom)\b/i, string) || scope.consumeLengthOrPercent(string);
}
var offsetMap = {
left: {'%': 0},
center: {'%': 50},
right: {'%': 100},
top: {'%': 0},
bottom: {'%': 100},
};
function parseOrigin(slots, string) {
var result = scope.consumeRepeated(consumeOffset, /^/, string);
if (!result || result[1] != '') return;
var tokens = result[0];
tokens[0] = tokens[0] || 'center';
tokens[1] = tokens[1] || 'center';
if (slots == 3) {
tokens[2] = tokens[2] || {px: 0};
}
if (tokens.length != slots) {
return;
}
// Reorder so that the horizontal axis comes first.
if (/top|bottom/.test(tokens[0]) || /left|right/.test(tokens[1])) {
var tmp = tokens[0];
tokens[0] = tokens[1];
tokens[1] = tmp;
}
// Invalid if not horizontal then vertical.
if (!/left|right|center|Object/.test(tokens[0]))
return;
if (!/top|bottom|center|Object/.test(tokens[1]))
return;
return tokens.map(function(position) {
return typeof position == 'object' ? position : offsetMap[position];
});
}
var mergeOffsetList = scope.mergeNestedRepeated.bind(null, scope.mergeDimensions, ' ');
scope.addPropertiesHandler(parseOrigin.bind(null, 3), mergeOffsetList, ['transform-origin']);
scope.addPropertiesHandler(parseOrigin.bind(null, 2), mergeOffsetList, ['perspective-origin']);
function consumePosition(string) {
var result = scope.consumeRepeated(consumeOffset, /^/, string);
if (!result) {
return;
}
var tokens = result[0];
var out = [{'%': 50}, {'%': 50}];
var pos = 0;
var bottomOrRight = false;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (typeof token == 'string') {
bottomOrRight = /bottom|right/.test(token);
pos = {left: 0, right: 0, center: pos, top: 1, bottom: 1}[token];
out[pos] = offsetMap[token];
if (token == 'center') {
// Center doesn't accept a length offset.
pos++;
}
} else {
if (bottomOrRight) {
// If bottom or right we need to subtract the length from 100%
token = negateDimension(token);
token['%'] = (token['%'] || 0) + 100;
}
out[pos] = token;
pos++;
bottomOrRight = false;
}
}
return [out, result[1]];
}
function parsePositionList(string) {
var result = scope.consumeRepeated(consumePosition, /^,/, string);
if (result && result[1] == '') {
return result[0];
}
}
scope.consumePosition = consumePosition;
scope.mergeOffsetList = mergeOffsetList;
var mergePositionList = scope.mergeNestedRepeated.bind(null, mergeOffsetList, ', ');
scope.addPropertiesHandler(parsePositionList, mergePositionList, ['background-position', 'object-position']);
})(webAnimations1);

View File

@ -0,0 +1,125 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
var propertyHandlers = {};
function addPropertyHandler(parser, merger, property) {
propertyHandlers[property] = propertyHandlers[property] || [];
propertyHandlers[property].push([parser, merger]);
}
function addPropertiesHandler(parser, merger, properties) {
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
WEB_ANIMATIONS_TESTING && console.assert(property.toLowerCase() === property);
addPropertyHandler(parser, merger, property);
if (/-/.test(property)) {
// Add camel cased variant.
addPropertyHandler(parser, merger, property.replace(/-(.)/g, function(_, c) {
return c.toUpperCase();
}));
}
}
}
scope.addPropertiesHandler = addPropertiesHandler;
var initialValues = {
backgroundColor: 'transparent',
backgroundPosition: '0% 0%',
borderBottomColor: 'currentColor',
borderBottomLeftRadius: '0px',
borderBottomRightRadius: '0px',
borderBottomWidth: '3px',
borderLeftColor: 'currentColor',
borderLeftWidth: '3px',
borderRightColor: 'currentColor',
borderRightWidth: '3px',
// Spec says this should be 0 but in practise it is 2px.
borderSpacing: '2px',
borderTopColor: 'currentColor',
borderTopLeftRadius: '0px',
borderTopRightRadius: '0px',
borderTopWidth: '3px',
bottom: 'auto',
clip: 'rect(0px, 0px, 0px, 0px)',
color: 'black', // Depends on user agent.
fontSize: '100%',
fontWeight: '400',
height: 'auto',
left: 'auto',
letterSpacing: 'normal',
lineHeight: '120%',
marginBottom: '0px',
marginLeft: '0px',
marginRight: '0px',
marginTop: '0px',
maxHeight: 'none',
maxWidth: 'none',
minHeight: '0px',
minWidth: '0px',
opacity: '1.0',
outlineColor: 'invert',
outlineOffset: '0px',
outlineWidth: '3px',
paddingBottom: '0px',
paddingLeft: '0px',
paddingRight: '0px',
paddingTop: '0px',
right: 'auto',
textIndent: '0px',
textShadow: '0px 0px 0px transparent',
top: 'auto',
transform: '',
verticalAlign: '0px',
visibility: 'visible',
width: 'auto',
wordSpacing: 'normal',
zIndex: 'auto'
};
function propertyInterpolation(property, left, right) {
if (left == 'initial' || right == 'initial') {
var ucProperty = property.replace(/-(.)/g, function(_, c) {
return c.toUpperCase();
});
if (left == 'initial')
left = initialValues[ucProperty];
if (right == 'initial')
right = initialValues[ucProperty];
}
var handlers = left == right ? [] : propertyHandlers[property];
for (var i = 0; handlers && i < handlers.length; i++) {
var parsedLeft = handlers[i][0](left);
var parsedRight = handlers[i][0](right);
if (parsedLeft !== undefined && parsedRight !== undefined) {
var interpolationArgs = handlers[i][1](parsedLeft, parsedRight);
if (interpolationArgs) {
var interp = scope.Interpolation.apply(null, interpolationArgs);
return function(t) {
if (t == 0) return left;
if (t == 1) return right;
return interp(t);
};
}
}
}
return scope.Interpolation(false, true, function(bool) {
return bool ? right : left;
});
}
scope.propertyInterpolation = propertyInterpolation;
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,35 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
var aliased = {};
function alias(name, aliases) {
aliases.concat([name]).forEach(function(candidate) {
if (candidate in document.documentElement.style) {
aliased[name] = candidate;
}
});
}
alias('transform', ['webkitTransform', 'msTransform']);
alias('transformOrigin', ['webkitTransformOrigin']);
alias('perspective', ['webkitPerspective']);
alias('perspectiveOrigin', ['webkitPerspectiveOrigin']);
scope.propertyName = function(property) {
return aliased[property] || property;
};
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,20 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var webAnimationsShared = {};
var webAnimations1 = {};
var webAnimationsNext = {};
if (!WEB_ANIMATIONS_TESTING)
var webAnimationsTesting = null;

View File

@ -0,0 +1,108 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
function consumeShadow(string) {
var shadow = {
inset: false,
lengths: [],
color: null,
};
function consumePart(string) {
var result = scope.consumeToken(/^inset/i, string);
if (result) {
shadow.inset = true;
return result;
}
var result = scope.consumeLengthOrPercent(string);
if (result) {
shadow.lengths.push(result[0]);
return result;
}
var result = scope.consumeColor(string);
if (result) {
shadow.color = result[0];
return result;
}
}
var result = scope.consumeRepeated(consumePart, /^/, string);
if (result && result[0].length) {
return [shadow, result[1]];
}
}
function parseShadowList(string) {
var result = scope.consumeRepeated(consumeShadow, /^,/, string);
if (result && result[1] == '') {
return result[0];
}
}
function mergeShadow(left, right) {
while (left.lengths.length < Math.max(left.lengths.length, right.lengths.length))
left.lengths.push({px: 0});
while (right.lengths.length < Math.max(left.lengths.length, right.lengths.length))
right.lengths.push({px: 0});
if (left.inset != right.inset || !!left.color != !!right.color) {
return;
}
var lengthReconstitution = [];
var colorReconstitution;
var matchingLeft = [[], 0];
var matchingRight = [[], 0];
for (var i = 0; i < left.lengths.length; i++) {
var mergedDimensions = scope.mergeDimensions(left.lengths[i], right.lengths[i], i == 2);
matchingLeft[0].push(mergedDimensions[0]);
matchingRight[0].push(mergedDimensions[1]);
lengthReconstitution.push(mergedDimensions[2]);
}
if (left.color && right.color) {
var mergedColor = scope.mergeColors(left.color, right.color);
matchingLeft[1] = mergedColor[0];
matchingRight[1] = mergedColor[1];
colorReconstitution = mergedColor[2];
}
return [matchingLeft, matchingRight, function(value) {
var result = left.inset ? 'inset ' : ' ';
for (var i = 0; i < lengthReconstitution.length; i++) {
result += lengthReconstitution[i](value[0][i]) + ' ';
}
if (colorReconstitution) {
result += colorReconstitution(value[1]);
}
return result;
}];
}
function mergeNestedRepeatedShadow(nestedMerge, separator, left, right) {
var leftCopy = [];
var rightCopy = [];
function defaultShadow(inset) {
return {inset: inset, color: [0, 0, 0, 0], lengths: [{px: 0}, {px: 0}, {px: 0}, {px: 0}]};
}
for (var i = 0; i < left.length || i < right.length; i++) {
var l = left[i] || defaultShadow(right[i].inset);
var r = right[i] || defaultShadow(left[i].inset);
leftCopy.push(l);
rightCopy.push(r);
}
return scope.mergeNestedRepeated(nestedMerge, separator, leftCopy, rightCopy);
}
var mergeShadowList = mergeNestedRepeatedShadow.bind(null, mergeShadow, ', ');
scope.addPropertiesHandler(parseShadowList, mergeShadowList, ['box-shadow', 'text-shadow']);
})(webAnimations1);

View File

@ -0,0 +1,85 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
var consumeLengthOrPercent = scope.consumeParenthesised.bind(null, scope.parseLengthOrPercent);
var consumeLengthOrPercentPair = scope.consumeRepeated.bind(undefined, consumeLengthOrPercent, /^/);
var mergeSizePair = scope.mergeNestedRepeated.bind(undefined, scope.mergeDimensions, ' ');
var mergeSizePairList = scope.mergeNestedRepeated.bind(undefined, mergeSizePair, ',');
function parseShape(input) {
var circle = scope.consumeToken(/^circle/, input);
if (circle && circle[0]) {
return ['circle'].concat(scope.consumeList([
scope.ignore(scope.consumeToken.bind(undefined, /^\(/)),
consumeLengthOrPercent,
scope.ignore(scope.consumeToken.bind(undefined, /^at/)),
scope.consumePosition,
scope.ignore(scope.consumeToken.bind(undefined, /^\)/))
], circle[1]));
}
var ellipse = scope.consumeToken(/^ellipse/, input);
if (ellipse && ellipse[0]) {
return ['ellipse'].concat(scope.consumeList([
scope.ignore(scope.consumeToken.bind(undefined, /^\(/)),
consumeLengthOrPercentPair,
scope.ignore(scope.consumeToken.bind(undefined, /^at/)),
scope.consumePosition,
scope.ignore(scope.consumeToken.bind(undefined, /^\)/))
], ellipse[1]));
}
var polygon = scope.consumeToken(/^polygon/, input);
if (polygon && polygon[0]) {
return ['polygon'].concat(scope.consumeList([
scope.ignore(scope.consumeToken.bind(undefined, /^\(/)),
scope.optional(scope.consumeToken.bind(undefined, /^nonzero\s*,|^evenodd\s*,/), 'nonzero,'),
scope.consumeSizePairList,
scope.ignore(scope.consumeToken.bind(undefined, /^\)/))
], polygon[1]));
}
}
function mergeShapes(left, right) {
if (left[0] !== right[0])
return;
if (left[0] == 'circle') {
return scope.mergeList(left.slice(1), right.slice(1), [
'circle(',
scope.mergeDimensions,
' at ',
scope.mergeOffsetList,
')']);
}
if (left[0] == 'ellipse') {
return scope.mergeList(left.slice(1), right.slice(1), [
'ellipse(',
scope.mergeNonNegativeSizePair,
' at ',
scope.mergeOffsetList,
')']);
}
if (left[0] == 'polygon' && left[1] == right[1]) {
return scope.mergeList(left.slice(2), right.slice(2), [
'polygon(',
left[1],
mergeSizePairList,
')']);
}
}
scope.addPropertiesHandler(parseShape, mergeShapes, ['shape-outside']);
})(webAnimations1);

View File

@ -0,0 +1,171 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
var originalRequestAnimationFrame = window.requestAnimationFrame;
var rafCallbacks = [];
var rafId = 0;
window.requestAnimationFrame = function(f) {
var id = rafId++;
if (rafCallbacks.length == 0 && !WEB_ANIMATIONS_TESTING) {
originalRequestAnimationFrame(processRafCallbacks);
}
rafCallbacks.push([id, f]);
return id;
};
window.cancelAnimationFrame = function(id) {
rafCallbacks.forEach(function(entry) {
if (entry[0] == id) {
entry[1] = function() {};
}
});
};
function processRafCallbacks(t) {
var processing = rafCallbacks;
rafCallbacks = [];
if (t < timeline.currentTime)
t = timeline.currentTime;
tick(t);
processing.forEach(function(entry) { entry[1](t); });
if (needsRetick)
tick(t);
applyPendingEffects();
_now = undefined;
}
function compareAnimations(leftAnimation, rightAnimation) {
return leftAnimation._sequenceNumber - rightAnimation._sequenceNumber;
}
function InternalTimeline() {
this._animations = [];
// Android 4.3 browser has window.performance, but not window.performance.now
this.currentTime = window.performance && performance.now ? performance.now() : 0;
};
InternalTimeline.prototype = {
_play: function(effect) {
effect._timing = shared.normalizeTimingInput(effect.timing);
var animation = new scope.Animation(effect);
animation._idle = false;
animation._timeline = this;
this._animations.push(animation);
scope.restart();
scope.invalidateEffects();
return animation;
}
};
var _now = undefined;
if (WEB_ANIMATIONS_TESTING) {
var now = function() { return timeline.currentTime; };
} else {
var now = function() {
if (_now == undefined)
_now = performance.now();
return _now;
};
}
var ticking = false;
var hasRestartedThisFrame = false;
scope.restart = function() {
if (!ticking) {
ticking = true;
requestAnimationFrame(function() {});
hasRestartedThisFrame = true;
}
return hasRestartedThisFrame;
};
var needsRetick = false;
scope.invalidateEffects = function() {
needsRetick = true;
};
var pendingEffects = [];
function applyPendingEffects() {
pendingEffects.forEach(function(f) { f(); });
pendingEffects.length = 0;
}
var t60hz = 1000 / 60;
var originalGetComputedStyle = window.getComputedStyle;
Object.defineProperty(window, 'getComputedStyle', {
configurable: true,
enumerable: true,
value: function() {
if (needsRetick) {
var time = now();
if (time - timeline.currentTime > 0)
timeline.currentTime += t60hz * (Math.floor((time - timeline.currentTime) / t60hz) + 1);
tick(timeline.currentTime);
}
applyPendingEffects();
return originalGetComputedStyle.apply(this, arguments);
},
});
function tick(t) {
hasRestartedThisFrame = false;
var timeline = scope.timeline;
timeline.currentTime = t;
timeline._animations.sort(compareAnimations);
ticking = false;
var updatingAnimations = timeline._animations;
timeline._animations = [];
var newPendingClears = [];
var newPendingEffects = [];
updatingAnimations = updatingAnimations.filter(function(animation) {
animation._inTimeline = animation._tick(t);
if (!animation._inEffect)
newPendingClears.push(animation._effect);
else
newPendingEffects.push(animation._effect);
if (!animation._isFinished && !animation._paused && !animation._idle)
ticking = true;
return animation._inTimeline;
});
// FIXME: Should remove dupliactes from pendingEffects.
pendingEffects.push.apply(pendingEffects, newPendingClears);
pendingEffects.push.apply(pendingEffects, newPendingEffects);
timeline._animations.push.apply(timeline._animations, updatingAnimations);
needsRetick = false;
if (ticking)
requestAnimationFrame(function() {});
};
if (WEB_ANIMATIONS_TESTING) {
testing.tick = function(t) { timeline.currentTime = t; processRafCallbacks(t); };
testing.isTicking = function() { return ticking; };
testing.setTicking = function(newVal) { ticking = newVal; };
}
var timeline = new InternalTimeline();
scope.timeline = timeline;
})(webAnimationsShared, webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,101 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
var originalRequestAnimationFrame = window.requestAnimationFrame;
window.requestAnimationFrame = function(f) {
return originalRequestAnimationFrame(function(x) {
window.document.timeline._updateAnimationsPromises();
f(x);
window.document.timeline._updateAnimationsPromises();
});
};
scope.AnimationTimeline = function() {
this._animations = [];
this.currentTime = undefined;
};
scope.AnimationTimeline.prototype = {
getAnimations: function() {
this._discardAnimations();
return this._animations.slice();
},
_updateAnimationsPromises: function() {
scope.animationsWithPromises = scope.animationsWithPromises.filter(function(animation) {
return animation._updatePromises();
});
},
_discardAnimations: function() {
this._updateAnimationsPromises();
this._animations = this._animations.filter(function(animation) {
return animation.playState != 'finished' && animation.playState != 'idle';
});
},
_play: function(effect) {
var animation = new scope.Animation(effect, this);
this._animations.push(animation);
scope.restartWebAnimationsNextTick();
// Use animation._animation.play() here, NOT animation.play().
//
// Timeline.play calls new scope.Animation(effect) which (indirectly) calls Timeline.play on
// effect's children, and Animation.play is also recursive. We only need to call play on each
// animation in the tree once.
animation._updatePromises();
animation._animation.play();
animation._updatePromises();
return animation;
},
play: function(effect) {
if (effect) {
effect.remove();
}
return this._play(effect);
}
};
var ticking = false;
scope.restartWebAnimationsNextTick = function() {
if (!ticking) {
ticking = true;
requestAnimationFrame(webAnimationsNextTick);
}
};
function webAnimationsNextTick(t) {
var timeline = window.document.timeline;
timeline.currentTime = t;
timeline._discardAnimations();
if (timeline._animations.length == 0)
ticking = false;
else
requestAnimationFrame(webAnimationsNextTick);
}
var timeline = new scope.AnimationTimeline();
scope.timeline = timeline;
try {
Object.defineProperty(window.document, 'timeline', {
configurable: true,
get: function() { return timeline; }
});
} catch (e) { }
try {
window.document.timeline = timeline;
} catch (e) { }
})(webAnimationsShared, webAnimationsNext, webAnimationsTesting);

View File

@ -0,0 +1,341 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, testing) {
var fills = 'backwards|forwards|both|none'.split('|');
var directions = 'reverse|alternate|alternate-reverse'.split('|');
function cloneTimingInput(timingInput) {
if (typeof timingInput == 'number') {
return timingInput;
}
var clone = {};
for (var m in timingInput) {
clone[m] = timingInput[m];
}
return clone;
}
function AnimationEffectTiming() {
this._delay = 0;
this._endDelay = 0;
this._fill = 'none';
this._iterationStart = 0;
this._iterations = 1;
this._duration = 0;
this._playbackRate = 1;
this._direction = 'normal';
this._easing = 'linear';
}
AnimationEffectTiming.prototype = {
_setMember: function(member, value) {
this['_' + member] = value;
if (this._effect) {
this._effect._timingInput[member] = value;
this._effect._timing = shared.normalizeTimingInput(shared.normalizeTimingInput(this._effect._timingInput));
this._effect.activeDuration = shared.calculateActiveDuration(this._effect._timing);
if (this._effect._animation) {
this._effect._animation._rebuildUnderlyingAnimation();
}
}
},
get playbackRate() {
return this._playbackRate;
},
set delay(value) {
this._setMember('delay', value);
},
get delay() {
return this._delay;
},
set endDelay(value) {
this._setMember('endDelay', value);
},
get endDelay() {
return this._endDelay;
},
set fill(value) {
this._setMember('fill', value);
},
get fill() {
return this._fill;
},
set iterationStart(value) {
this._setMember('iterationStart', value);
},
get iterationStart() {
return this._iterationStart;
},
set duration(value) {
this._setMember('duration', value);
},
get duration() {
return this._duration;
},
set direction(value) {
this._setMember('direction', value);
},
get direction() {
return this._direction;
},
set easing(value) {
this._setMember('easing', value);
},
get easing() {
return this._easing;
},
set iterations(value) {
this._setMember('iterations', value);
},
get iterations() {
return this._iterations;
}
};
function makeTiming(timingInput, forGroup, effect) {
var timing = new AnimationEffectTiming();
if (forGroup) {
timing.fill = 'both';
timing.duration = 'auto';
}
if (typeof timingInput == 'number' && !isNaN(timingInput)) {
timing.duration = timingInput;
} else if (timingInput !== undefined) {
Object.getOwnPropertyNames(timingInput).forEach(function(property) {
if (timingInput[property] != 'auto') {
if (typeof timing[property] == 'number' || property == 'duration') {
if (typeof timingInput[property] != 'number' || isNaN(timingInput[property])) {
return;
}
}
if ((property == 'fill') && (fills.indexOf(timingInput[property]) == -1)) {
return;
}
if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
return;
}
if (property == 'playbackRate' && timingInput[property] !== 1 && shared.isDeprecated('AnimationEffectTiming.playbackRate', '2014-11-28', 'Use Animation.playbackRate instead.')) {
return;
}
timing[property] = timingInput[property];
}
});
}
return timing;
}
function numericTimingToObject(timingInput) {
if (typeof timingInput == 'number') {
if (isNaN(timingInput)) {
timingInput = { duration: 0 };
} else {
timingInput = { duration: timingInput };
}
}
return timingInput;
}
function normalizeTimingInput(timingInput, forGroup) {
timingInput = shared.numericTimingToObject(timingInput);
var timing = makeTiming(timingInput, forGroup);
timing._easing = toTimingFunction(timing.easing);
return timing;
}
function cubic(a, b, c, d) {
if (a < 0 || a > 1 || c < 0 || c > 1) {
return linear;
}
return function(x) {
if (x == 0 || x == 1) {
return x;
}
var start = 0, end = 1;
while (1) {
var mid = (start + end) / 2;
function f(a, b, m) { return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m};
var xEst = f(a, c, mid);
if (Math.abs(x - xEst) < 0.001) {
return f(b, d, mid);
}
if (xEst < x) {
start = mid;
} else {
end = mid;
}
}
}
}
var Start = 1;
var Middle = 0.5;
var End = 0;
function step(count, pos) {
return function(x) {
if (x >= 1) {
return 1;
}
var stepSize = 1 / count;
x += pos * stepSize;
return x - x % stepSize;
}
}
var presets = {
'ease': cubic(0.25, 0.1, 0.25, 1),
'ease-in': cubic(0.42, 0, 1, 1),
'ease-out': cubic(0, 0, 0.58, 1),
'ease-in-out': cubic(0.42, 0, 0.58, 1),
'step-start': step(1, Start),
'step-middle': step(1, Middle),
'step-end': step(1, End)
};
var numberString = '\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*';
var cubicBezierRe = new RegExp('cubic-bezier\\(' + numberString + ',' + numberString + ',' + numberString + ',' + numberString + '\\)');
var stepRe = /steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/;
var linear = function(x) { return x; };
function toTimingFunction(easing) {
var cubicData = cubicBezierRe.exec(easing);
if (cubicData) {
return cubic.apply(this, cubicData.slice(1).map(Number));
}
var stepData = stepRe.exec(easing);
if (stepData) {
return step(Number(stepData[1]), {'start': Start, 'middle': Middle, 'end': End}[stepData[2]]);
}
var preset = presets[easing];
if (preset) {
return preset;
}
return linear;
};
function calculateActiveDuration(timing) {
return Math.abs(repeatedDuration(timing) / timing.playbackRate);
}
function repeatedDuration(timing) {
return timing.duration * timing.iterations;
}
var PhaseNone = 0;
var PhaseBefore = 1;
var PhaseAfter = 2;
var PhaseActive = 3;
function calculatePhase(activeDuration, localTime, timing) {
if (localTime == null) {
return PhaseNone;
}
if (localTime < timing.delay) {
return PhaseBefore;
}
if (localTime >= timing.delay + activeDuration) {
return PhaseAfter;
}
return PhaseActive;
}
function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay) {
switch (phase) {
case PhaseBefore:
if (fillMode == 'backwards' || fillMode == 'both')
return 0;
return null;
case PhaseActive:
return localTime - delay;
case PhaseAfter:
if (fillMode == 'forwards' || fillMode == 'both')
return activeDuration;
return null;
case PhaseNone:
return null;
}
}
function calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing) {
return (timing.playbackRate < 0 ? activeTime - activeDuration : activeTime) * timing.playbackRate + startOffset;
}
function calculateIterationTime(iterationDuration, repeatedDuration, scaledActiveTime, startOffset, timing) {
if (scaledActiveTime === Infinity || scaledActiveTime === -Infinity || (scaledActiveTime - startOffset == repeatedDuration && timing.iterations && ((timing.iterations + timing.iterationStart) % 1 == 0))) {
return iterationDuration;
}
return scaledActiveTime % iterationDuration;
}
function calculateCurrentIteration(iterationDuration, iterationTime, scaledActiveTime, timing) {
if (scaledActiveTime === 0) {
return 0;
}
if (iterationTime == iterationDuration) {
return timing.iterationStart + timing.iterations - 1;
}
return Math.floor(scaledActiveTime / iterationDuration);
}
function calculateTransformedTime(currentIteration, iterationDuration, iterationTime, timing) {
var currentIterationIsOdd = currentIteration % 2 >= 1;
var currentDirectionIsForwards = timing.direction == 'normal' || timing.direction == (currentIterationIsOdd ? 'alternate-reverse' : 'alternate');
var directedTime = currentDirectionIsForwards ? iterationTime : iterationDuration - iterationTime;
var timeFraction = directedTime / iterationDuration;
return iterationDuration * timing.easing(timeFraction);
}
function calculateTimeFraction(activeDuration, localTime, timing) {
var phase = calculatePhase(activeDuration, localTime, timing);
var activeTime = calculateActiveTime(activeDuration, timing.fill, localTime, phase, timing.delay);
if (activeTime === null)
return null;
if (activeDuration === 0)
return phase === PhaseBefore ? 0 : 1;
var startOffset = timing.iterationStart * timing.duration;
var scaledActiveTime = calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing);
var iterationTime = calculateIterationTime(timing.duration, repeatedDuration(timing), scaledActiveTime, startOffset, timing);
var currentIteration = calculateCurrentIteration(timing.duration, iterationTime, scaledActiveTime, timing);
return calculateTransformedTime(currentIteration, timing.duration, iterationTime, timing) / timing.duration;
}
shared.cloneTimingInput = cloneTimingInput;
shared.makeTiming = makeTiming;
shared.numericTimingToObject = numericTimingToObject;
shared.normalizeTimingInput = normalizeTimingInput;
shared.calculateActiveDuration = calculateActiveDuration;
shared.calculateTimeFraction = calculateTimeFraction;
shared.calculatePhase = calculatePhase;
shared.toTimingFunction = toTimingFunction;
if (WEB_ANIMATIONS_TESTING) {
testing.normalizeTimingInput = normalizeTimingInput;
testing.toTimingFunction = toTimingFunction;
testing.calculateActiveDuration = calculateActiveDuration;
testing.calculatePhase = calculatePhase;
testing.PhaseNone = PhaseNone;
testing.PhaseBefore = PhaseBefore;
testing.PhaseActive = PhaseActive;
testing.PhaseAfter = PhaseAfter;
testing.calculateActiveTime = calculateActiveTime;
testing.calculateScaledActiveTime = calculateScaledActiveTime;
testing.calculateIterationTime = calculateIterationTime;
testing.calculateCurrentIteration = calculateCurrentIteration;
testing.calculateTransformedTime = calculateTransformedTime;
}
})(webAnimationsShared, webAnimationsTesting);

View File

@ -0,0 +1,262 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
// This returns a function for converting transform functions to equivalent
// primitive functions, which will take an array of values from the
// derivative type and fill in the blanks (underscores) with them.
var _ = null;
function cast(pattern) {
return function(contents) {
var i = 0;
return pattern.map(function(x) { return x === _ ? contents[i++] : x; });
}
}
function id(x) { return x; }
var Opx = {px: 0};
var Odeg = {deg: 0};
// type: [argTypes, convertTo3D, convertTo2D]
// In the argument types string, lowercase characters represent optional arguments
var transformFunctions = {
matrix: ['NNNNNN', [_, _, 0, 0, _, _, 0, 0, 0, 0, 1, 0, _, _, 0, 1], id],
matrix3d: ['NNNNNNNNNNNNNNNN', id],
rotate: ['A'],
rotatex: ['A'],
rotatey: ['A'],
rotatez: ['A'],
rotate3d: ['NNNA'],
perspective: ['L'],
scale: ['Nn', cast([_, _, 1]), id],
scalex: ['N', cast([_, 1, 1]), cast([_, 1])],
scaley: ['N', cast([1, _, 1]), cast([1, _])],
scalez: ['N', cast([1, 1, _])],
scale3d: ['NNN', id],
skew: ['Aa', null, id],
skewx: ['A', null, cast([_, Odeg])],
skewy: ['A', null, cast([Odeg, _])],
translate: ['Tt', cast([_, _, Opx]), id],
translatex: ['T', cast([_, Opx, Opx]), cast([_, Opx])],
translatey: ['T', cast([Opx, _, Opx]), cast([Opx, _])],
translatez: ['L', cast([Opx, Opx, _])],
translate3d: ['TTL', id],
};
function parseTransform(string) {
string = string.toLowerCase().trim();
if (string == 'none')
return [];
// FIXME: Using a RegExp means calcs won't work here
var transformRegExp = /\s*(\w+)\(([^)]*)\)/g;
var result = [];
var match;
var prevLastIndex = 0;
while (match = transformRegExp.exec(string)) {
if (match.index != prevLastIndex)
return;
prevLastIndex = match.index + match[0].length;
var functionName = match[1];
var functionData = transformFunctions[functionName];
if (!functionData)
return;
var args = match[2].split(',');
var argTypes = functionData[0];
if (argTypes.length < args.length)
return;
var parsedArgs = [];
for (var i = 0; i < argTypes.length; i++) {
var arg = args[i];
var type = argTypes[i];
var parsedArg;
if (!arg)
parsedArg = ({a: Odeg,
n: parsedArgs[0],
t: Opx})[type];
else
parsedArg = ({A: function(s) { return s.trim() == '0' ? Odeg : scope.parseAngle(s); },
N: scope.parseNumber,
T: scope.parseLengthOrPercent,
L: scope.parseLength})[type.toUpperCase()](arg);
if (parsedArg === undefined)
return;
parsedArgs.push(parsedArg);
}
result.push({t: functionName, d: parsedArgs});
if (transformRegExp.lastIndex == string.length)
return result;
}
};
function numberToLongString(x) {
return x.toFixed(6).replace('.000000', '');
}
function mergeMatrices(left, right) {
if (left.decompositionPair !== right) {
left.decompositionPair = right;
var leftArgs = scope.makeMatrixDecomposition(left);
}
if (right.decompositionPair !== left) {
right.decompositionPair = left;
var rightArgs = scope.makeMatrixDecomposition(right);
}
if (leftArgs[0] == null || rightArgs[0] == null)
return [[false], [true], function(x) { return x ? right[0].d : left[0].d; }];
leftArgs[0].push(0);
rightArgs[0].push(1);
return [
leftArgs,
rightArgs,
function(list) {
var quat = scope.quat(leftArgs[0][3], rightArgs[0][3], list[5]);
var mat = scope.composeMatrix(list[0], list[1], list[2], quat, list[4]);
var stringifiedArgs = mat.map(numberToLongString).join(',');
return stringifiedArgs;
}
];
}
function typeTo2D(type) {
return type.replace(/[xy]/, '');
}
function typeTo3D(type) {
return type.replace(/(x|y|z|3d)?$/, '3d');
}
function mergeTransforms(left, right) {
var matrixModulesLoaded = scope.makeMatrixDecomposition && true;
var flipResults = false;
if (!left.length || !right.length) {
if (!left.length) {
flipResults = true;
left = right;
right = [];
}
for (var i = 0; i < left.length; i++) {
var type = left[i].t;
var args = left[i].d;
var defaultValue = type.substr(0, 5) == 'scale' ? 1 : 0;
right.push({t: type, d: args.map(function(arg) {
if (typeof arg == 'number')
return defaultValue;
var result = {};
for (var unit in arg)
result[unit] = defaultValue;
return result;
})});
}
}
var isMatrixOrPerspective = function(lt, rt) {
return ((lt == 'perspective') && (rt == 'perspective')) ||
((lt == 'matrix' || lt == 'matrix3d') && (rt == 'matrix' || rt == 'matrix3d'));
};
var leftResult = [];
var rightResult = [];
var types = [];
if (left.length != right.length) {
if (!matrixModulesLoaded)
return;
var merged = mergeMatrices(left, right);
leftResult = [merged[0]];
rightResult = [merged[1]];
types = [['matrix', [merged[2]]]];
} else {
for (var i = 0; i < left.length; i++) {
var leftType = left[i].t;
var rightType = right[i].t;
var leftArgs = left[i].d;
var rightArgs = right[i].d;
var leftFunctionData = transformFunctions[leftType];
var rightFunctionData = transformFunctions[rightType];
var type;
if (isMatrixOrPerspective(leftType, rightType)) {
if (!matrixModulesLoaded)
return;
var merged = mergeMatrices([left[i]], [right[i]]);
leftResult.push(merged[0]);
rightResult.push(merged[1]);
types.push(['matrix', [merged[2]]]);
continue;
} else if (leftType == rightType) {
type = leftType;
} else if (leftFunctionData[2] && rightFunctionData[2] && typeTo2D(leftType) == typeTo2D(rightType)) {
type = typeTo2D(leftType);
leftArgs = leftFunctionData[2](leftArgs);
rightArgs = rightFunctionData[2](rightArgs);
} else if (leftFunctionData[1] && rightFunctionData[1] && typeTo3D(leftType) == typeTo3D(rightType)) {
type = typeTo3D(leftType);
leftArgs = leftFunctionData[1](leftArgs);
rightArgs = rightFunctionData[1](rightArgs);
} else {
if (!matrixModulesLoaded)
return;
var merged = mergeMatrices(left, right);
leftResult = [merged[0]];
rightResult = [merged[1]];
types = [['matrix', [merged[2]]]];
break;
}
var leftArgsCopy = [];
var rightArgsCopy = [];
var stringConversions = [];
for (var j = 0; j < leftArgs.length; j++) {
var merge = typeof leftArgs[j] == 'number' ? scope.mergeNumbers : scope.mergeDimensions;
var merged = merge(leftArgs[j], rightArgs[j]);
leftArgsCopy[j] = merged[0];
rightArgsCopy[j] = merged[1];
stringConversions.push(merged[2]);
}
leftResult.push(leftArgsCopy);
rightResult.push(rightArgsCopy);
types.push([type, stringConversions]);
}
}
if (flipResults) {
var tmp = leftResult;
leftResult = rightResult;
rightResult = tmp;
}
return [leftResult, rightResult, function(list) {
return list.map(function(args, i) {
var stringifiedArgs = args.map(function(arg, j) {
return types[i][1][j](arg);
}).join(',');
if (types[i][0] == 'matrix' && stringifiedArgs.split(',').length == 16)
types[i][0] = 'matrix3d';
return types[i][0] + '(' + stringifiedArgs + ')';
}).join(' ');
}];
}
scope.addPropertiesHandler(parseTransform, mergeTransforms, ['transform']);
if (WEB_ANIMATIONS_TESTING)
testing.parseTransform = parseTransform;
})(webAnimations1, webAnimationsTesting);

View File

@ -0,0 +1,29 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope, testing) {
function merge(left, right) {
if (left != 'visible' && right != 'visible') return;
return [0, 1, function(x) {
if (x <= 0) return left;
if (x >= 1) return right;
return 'visible';
}];
}
scope.addPropertiesHandler(String, merge, ['visibility']);
})(webAnimations1);

View File

@ -0,0 +1,371 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, scope, testing) {
scope.animationsWithPromises = [];
scope.Animation = function(effect, timeline) {
this.effect = effect;
if (effect) {
effect._animation = this;
}
if (!timeline) {
throw new Error('Animation with null timeline is not supported');
}
this._timeline = timeline;
this._sequenceNumber = shared.sequenceNumber++;
this._holdTime = 0;
this._paused = false;
this._isGroup = false;
this._animation = null;
this._childAnimations = [];
this._callback = null;
this._oldPlayState = 'idle';
this._rebuildUnderlyingAnimation();
// Animations are constructed in the idle state.
this._animation.cancel();
this._updatePromises();
};
scope.Animation.prototype = {
_updatePromises: function() {
var oldPlayState = this._oldPlayState;
var newPlayState = this.playState;
if (this._readyPromise && newPlayState !== oldPlayState) {
if (newPlayState == 'idle') {
this._rejectReadyPromise();
this._readyPromise = undefined;
} else if (oldPlayState == 'pending') {
this._resolveReadyPromise();
} else if (newPlayState == 'pending') {
this._readyPromise = undefined;
}
}
if (this._finishedPromise && newPlayState !== oldPlayState) {
if (newPlayState == 'idle') {
this._rejectFinishedPromise();
this._finishedPromise = undefined;
} else if (newPlayState == 'finished') {
this._resolveFinishedPromise();
} else if (oldPlayState == 'finished') {
this._finishedPromise = undefined;
}
}
this._oldPlayState = this.playState;
return (this._readyPromise || this._finishedPromise);
},
_rebuildUnderlyingAnimation: function() {
this._updatePromises();
var oldPlaybackRate;
var oldPaused;
var oldStartTime;
var oldCurrentTime;
var hadUnderlying = this._animation ? true : false;
if (hadUnderlying) {
oldPlaybackRate = this.playbackRate;
oldPaused = this._paused;
oldStartTime = this.startTime;
oldCurrentTime = this.currentTime;
this._animation.cancel();
this._animation._wrapper = null;
this._animation = null;
}
if (!this.effect || this.effect instanceof window.KeyframeEffect) {
this._animation = scope.newUnderlyingAnimationForKeyframeEffect(this.effect);
scope.bindAnimationForKeyframeEffect(this);
}
if (this.effect instanceof window.SequenceEffect || this.effect instanceof window.GroupEffect) {
this._animation = scope.newUnderlyingAnimationForGroup(this.effect);
scope.bindAnimationForGroup(this);
}
if (this.effect && this.effect._onsample) {
scope.bindAnimationForCustomEffect(this);
}
if (hadUnderlying) {
if (oldPlaybackRate != 1) {
this.playbackRate = oldPlaybackRate;
}
if (oldStartTime !== null) {
this.startTime = oldStartTime;
} else if (oldCurrentTime !== null) {
this.currentTime = oldCurrentTime;
} else if (this._holdTime !== null) {
this.currentTime = this._holdTime;
}
if (oldPaused) {
this.pause();
}
}
this._updatePromises();
},
_updateChildren: function() {
if (!this.effect || this.playState == 'idle')
return;
var offset = this.effect._timing.delay;
this._childAnimations.forEach(function(childAnimation) {
this._arrangeChildren(childAnimation, offset);
if (this.effect instanceof window.SequenceEffect)
offset += scope.groupChildDuration(childAnimation.effect);
}.bind(this));
},
_setExternalAnimation: function(animation) {
if (!this.effect || !this._isGroup)
return;
for (var i = 0; i < this.effect.children.length; i++) {
this.effect.children[i]._animation = animation;
this._childAnimations[i]._setExternalAnimation(animation);
}
},
_constructChildAnimations: function() {
if (!this.effect || !this._isGroup)
return;
var offset = this.effect._timing.delay;
this._removeChildAnimations();
this.effect.children.forEach(function(child) {
var childAnimation = window.document.timeline._play(child);
this._childAnimations.push(childAnimation);
childAnimation.playbackRate = this.playbackRate;
if (this._paused)
childAnimation.pause();
child._animation = this.effect._animation;
this._arrangeChildren(childAnimation, offset);
if (this.effect instanceof window.SequenceEffect)
offset += scope.groupChildDuration(child);
}.bind(this));
},
_arrangeChildren: function(childAnimation, offset) {
if (this.startTime === null) {
childAnimation.currentTime = this.currentTime - offset / this.playbackRate;
} else if (childAnimation.startTime !== this.startTime + offset / this.playbackRate) {
childAnimation.startTime = this.startTime + offset / this.playbackRate;
}
},
get timeline() {
return this._timeline;
},
get playState() {
return this._animation ? this._animation.playState : 'idle';
},
get finished() {
if (!window.Promise) {
console.warn('Animation Promises require JavaScript Promise constructor');
return null;
}
if (!this._finishedPromise) {
if (scope.animationsWithPromises.indexOf(this) == -1) {
scope.animationsWithPromises.push(this);
}
this._finishedPromise = new Promise(
function(resolve, reject) {
this._resolveFinishedPromise = function() {
resolve(this);
};
this._rejectFinishedPromise = function() {
reject({type: DOMException.ABORT_ERR, name: 'AbortError'});
};
}.bind(this));
if (this.playState == 'finished') {
this._resolveFinishedPromise();
}
}
return this._finishedPromise;
},
get ready() {
if (!window.Promise) {
console.warn('Animation Promises require JavaScript Promise constructor');
return null;
}
if (!this._readyPromise) {
if (scope.animationsWithPromises.indexOf(this) == -1) {
scope.animationsWithPromises.push(this);
}
this._readyPromise = new Promise(
function(resolve, reject) {
this._resolveReadyPromise = function() {
resolve(this);
};
this._rejectReadyPromise = function() {
reject({type: DOMException.ABORT_ERR, name: 'AbortError'});
};
}.bind(this));
if (this.playState !== 'pending') {
this._resolveReadyPromise();
}
}
return this._readyPromise;
},
get onfinish() {
return this._onfinish;
},
set onfinish(v) {
if (typeof v == 'function') {
this._onfinish = v;
this._animation.onfinish = (function(e) {
e.target = this;
v.call(this, e);
}).bind(this);
} else {
this._animation.onfinish = v;
this.onfinish = this._animation.onfinish;
}
},
get currentTime() {
this._updatePromises();
var currentTime = this._animation.currentTime;
this._updatePromises();
return currentTime;
},
set currentTime(v) {
this._updatePromises();
this._animation.currentTime = isFinite(v) ? v : Math.sign(v) * Number.MAX_VALUE;
this._register();
this._forEachChild(function(child, offset) {
child.currentTime = v - offset;
});
this._updatePromises();
},
get startTime() {
return this._animation.startTime;
},
set startTime(v) {
this._updatePromises();
this._animation.startTime = isFinite(v) ? v : Math.sign(v) * Number.MAX_VALUE;
this._register();
this._forEachChild(function(child, offset) {
child.startTime = v + offset;
});
this._updatePromises();
},
get playbackRate() {
return this._animation.playbackRate;
},
set playbackRate(value) {
this._updatePromises();
var oldCurrentTime = this.currentTime;
this._animation.playbackRate = value;
this._forEachChild(function(childAnimation) {
childAnimation.playbackRate = value;
});
if (this.playState != 'paused' && this.playState != 'idle') {
this.play();
}
if (oldCurrentTime !== null) {
this.currentTime = oldCurrentTime;
}
this._updatePromises();
},
play: function() {
this._updatePromises();
this._paused = false;
this._animation.play();
if (this._timeline._animations.indexOf(this) == -1) {
this._timeline._animations.push(this);
}
this._register();
scope.awaitStartTime(this);
this._forEachChild(function(child) {
var time = child.currentTime;
child.play();
child.currentTime = time;
});
this._updatePromises();
},
pause: function() {
this._updatePromises();
if (this.currentTime) {
this._holdTime = this.currentTime;
}
this._animation.pause();
this._register();
this._forEachChild(function(child) {
child.pause();
});
this._paused = true;
this._updatePromises();
},
finish: function() {
this._updatePromises();
this._animation.finish();
this._register();
this._updatePromises();
},
cancel: function() {
this._updatePromises();
this._animation.cancel();
this._register();
this._removeChildAnimations();
this._updatePromises();
},
reverse: function() {
this._updatePromises();
var oldCurrentTime = this.currentTime;
this._animation.reverse();
this._forEachChild(function(childAnimation) {
childAnimation.reverse();
});
if (oldCurrentTime !== null) {
this.currentTime = oldCurrentTime;
}
this._updatePromises();
},
addEventListener: function(type, handler) {
var wrapped = handler;
if (typeof handler == 'function') {
wrapped = (function(e) {
e.target = this;
handler.call(this, e);
}).bind(this);
handler._wrapper = wrapped;
}
this._animation.addEventListener(type, wrapped);
},
removeEventListener: function(type, handler) {
this._animation.removeEventListener(type, (handler && handler._wrapper) || handler);
},
_removeChildAnimations: function() {
while (this._childAnimations.length)
this._childAnimations.pop().cancel();
},
_forEachChild: function(f) {
var offset = 0;
if (this.effect.children && this._childAnimations.length < this.effect.children.length)
this._constructChildAnimations();
this._childAnimations.forEach(function(child) {
f.call(this, child, offset);
if (this.effect instanceof window.SequenceEffect)
offset += child.effect.activeDuration;
}.bind(this));
if (this.playState == 'pending')
return;
var timing = this.effect._timing;
var t = this.currentTime;
if (t !== null)
t = shared.calculateTimeFraction(shared.calculateActiveDuration(timing), t, timing);
if (t == null || isNaN(t))
this._removeChildAnimations();
},
};
window.Animation = scope.Animation;
if (WEB_ANIMATIONS_TESTING) {
testing.webAnimationsNextAnimation = scope.Animation;
}
})(webAnimationsShared, webAnimationsNext, webAnimationsTesting);