17 |
18 |
19 |
20 |
21 |
84 |
85 |
--------------------------------------------------------------------------------
/test/blink/interpolation/transform-perspective-interpolation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
75 |
76 |
--------------------------------------------------------------------------------
/test/blink/interpolation/box-shadow-interpolation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
20 |
21 |
64 |
65 |
--------------------------------------------------------------------------------
/src/keyframe-effect.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function(shared, scope, testing) {
16 |
17 | function EffectTime(timing) {
18 | var timeFraction = 0;
19 | var activeDuration = shared.calculateActiveDuration(timing);
20 | var effectTime = function(localTime) {
21 | return shared.calculateIterationProgress(activeDuration, localTime, timing);
22 | };
23 | effectTime._totalDuration = timing.delay + activeDuration + timing.endDelay;
24 | return effectTime;
25 | }
26 |
27 | scope.KeyframeEffect = function(target, effectInput, timingInput, id) {
28 | var effectTime = EffectTime(shared.normalizeTimingInput(timingInput));
29 | var interpolations = scope.convertEffectInput(effectInput);
30 | var timeFraction;
31 | var keyframeEffect = function() {
32 | WEB_ANIMATIONS_TESTING && console.assert(typeof timeFraction !== 'undefined');
33 | interpolations(target, timeFraction);
34 | };
35 | // Returns whether the keyframeEffect is in effect or not after the timing update.
36 | keyframeEffect._update = function(localTime) {
37 | timeFraction = effectTime(localTime);
38 | return timeFraction !== null;
39 | };
40 | keyframeEffect._clear = function() {
41 | interpolations(target, null);
42 | };
43 | keyframeEffect._hasSameTarget = function(otherTarget) {
44 | return target === otherTarget;
45 | };
46 | keyframeEffect._target = target;
47 | keyframeEffect._totalDuration = effectTime._totalDuration;
48 | keyframeEffect._id = id;
49 | return keyframeEffect;
50 | };
51 |
52 | if (WEB_ANIMATIONS_TESTING) {
53 | testing.webAnimations1KeyframeEffect = scope.KeyframeEffect;
54 | testing.effectTime = EffectTime;
55 | }
56 |
57 | })(webAnimationsShared, webAnimations1, webAnimationsTesting);
58 |
--------------------------------------------------------------------------------
/web-animations-next.dev.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/test/blink/interpolation/calc-interpolation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
92 |
93 |
--------------------------------------------------------------------------------
/src/color-handler.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function(scope, testing) {
16 |
17 | var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
18 | canvas.width = canvas.height = 1;
19 | var context = canvas.getContext('2d');
20 |
21 | function parseColor(string) {
22 | string = string.trim();
23 | // The context ignores invalid colors
24 | context.fillStyle = '#000';
25 | context.fillStyle = string;
26 | var contextSerializedFillStyle = context.fillStyle;
27 | context.fillStyle = '#fff';
28 | context.fillStyle = string;
29 | if (contextSerializedFillStyle != context.fillStyle)
30 | return;
31 | context.fillRect(0, 0, 1, 1);
32 | var pixelColor = context.getImageData(0, 0, 1, 1).data;
33 | context.clearRect(0, 0, 1, 1);
34 | var alpha = pixelColor[3] / 255;
35 | return [pixelColor[0] * alpha, pixelColor[1] * alpha, pixelColor[2] * alpha, alpha];
36 | }
37 |
38 | function mergeColors(left, right) {
39 | return [left, right, function(x) {
40 | function clamp(v) {
41 | return Math.max(0, Math.min(255, v));
42 | }
43 | if (x[3]) {
44 | for (var i = 0; i < 3; i++)
45 | x[i] = Math.round(clamp(x[i] / x[3]));
46 | }
47 | x[3] = scope.numberToString(scope.clamp(0, 1, x[3]));
48 | return 'rgba(' + x.join(',') + ')';
49 | }];
50 | }
51 |
52 | scope.addPropertiesHandler(parseColor, mergeColors,
53 | ['background-color', 'border-bottom-color', 'border-left-color', 'border-right-color',
54 | 'border-top-color', 'color', 'fill', 'flood-color', 'lighting-color',
55 | 'outline-color', 'stop-color', 'stroke', 'text-decoration-color']);
56 | scope.consumeColor = scope.consumeParenthesised.bind(null, parseColor);
57 | scope.mergeColors = mergeColors;
58 |
59 | if (WEB_ANIMATIONS_TESTING) {
60 | testing.parseColor = parseColor;
61 | }
62 |
63 | })(webAnimations1, webAnimationsTesting);
64 |
--------------------------------------------------------------------------------
/test/js/group-animation-finish-event.js:
--------------------------------------------------------------------------------
1 | suite('group-animation-finish-event', function() {
2 | setup(function() {
3 | document.timeline.currentTime = undefined;
4 | this.element = document.createElement('div');
5 | document.documentElement.appendChild(this.element);
6 | var sequenceEffect = new SequenceEffect([
7 | new KeyframeEffect(this.element, [], 500),
8 | new GroupEffect([
9 | new KeyframeEffect(this.element, [], 250),
10 | new KeyframeEffect(this.element, [], 500),
11 | ]),
12 | ]);
13 | this.animation = document.timeline.play(sequenceEffect, 1000);
14 | });
15 | teardown(function() {
16 | this.element.parentNode.removeChild(this.element);
17 | });
18 |
19 | test('fire when animation completes', function(done) {
20 | var ready = false;
21 | var fired = false;
22 | var animation = this.animation;
23 | animation.onfinish = function(event) {
24 | assert(ready, 'must not be called synchronously');
25 | assert.equal(this, animation);
26 | assert.equal(event.target, animation);
27 | assert.equal(event.currentTime, 1000);
28 | assert.equal(event.timelineTime, 1100);
29 | if (fired)
30 | assert(false, 'must not get fired twice');
31 | fired = true;
32 | done();
33 | };
34 | tick(100);
35 | tick(1100);
36 | tick(2100);
37 | ready = true;
38 | });
39 |
40 | test('fire when reversed animation completes', function(done) {
41 | this.animation.onfinish = function(event) {
42 | assert.equal(event.currentTime, 0);
43 | assert.equal(event.timelineTime, 1001);
44 | done();
45 | };
46 | tick(0);
47 | tick(500);
48 | this.animation.reverse();
49 | tick(501);
50 | tick(1001);
51 | });
52 |
53 | test('multiple event listeners', function(done) {
54 | var count = 0;
55 | function createHandler(expectedCount) {
56 | return function() {
57 | count++;
58 | assert.equal(count, expectedCount);
59 | };
60 | }
61 | var toRemove = createHandler(0);
62 | this.animation.addEventListener('finish', createHandler(1));
63 | this.animation.addEventListener('finish', createHandler(2));
64 | this.animation.addEventListener('finish', toRemove);
65 | this.animation.addEventListener('finish', createHandler(3));
66 | this.animation.removeEventListener('finish', toRemove);
67 | this.animation.onfinish = function() {
68 | assert.equal(count, 3);
69 | done();
70 | };
71 | tick(0);
72 | this.animation.finish();
73 | tick(1000);
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/test/js/animation-cancel-event.js:
--------------------------------------------------------------------------------
1 | suite('animation-cancel-event', function() {
2 | setup(function() {
3 | this.element = document.createElement('div');
4 | document.documentElement.appendChild(this.element);
5 | this.animation = this.element.animate([], 1000);
6 | });
7 | teardown(function() {
8 | if (this.element.parent)
9 | this.element.removeChild(this.target);
10 | });
11 |
12 | test('fire when animation cancelled', function(done) {
13 | var ready = false;
14 | var fired = false;
15 | var animation = this.animation;
16 | animation.oncancel = function(event) {
17 | assert(ready, 'must not be called synchronously');
18 | assert.equal(this, animation);
19 | assert.equal(event.target, animation);
20 | assert.equal(event.currentTime, null);
21 | assert.equal(event.timelineTime, 100);
22 | if (fired)
23 | assert(false, 'must not get fired twice');
24 | fired = true;
25 | done();
26 | };
27 | tick(100);
28 | animation.cancel();
29 | tick(1100);
30 | tick(2100);
31 | ready = true;
32 | });
33 |
34 | test('finish event must not fire when animation cancelled', function(done) {
35 | this.animation.onfinish = function(event) {
36 | assert(false, 'must not get fired');
37 | };
38 | this.animation.oncancel = function(event) {
39 | done();
40 | };
41 | tick(0);
42 | this.animation.cancel();
43 | tick(1100);
44 | });
45 |
46 | test('cancel event must not fire when animation finishes', function(done) {
47 | this.animation.onfinish = function(event) {
48 | done();
49 | };
50 | this.animation.oncancel = function(event) {
51 | assert(false, 'must not get fired');
52 | };
53 | tick(0);
54 | tick(1100);
55 | });
56 |
57 | test('multiple event listeners', function(done) {
58 | var count = 0;
59 | function createHandler(expectedCount) {
60 | return function() {
61 | count++;
62 | assert.equal(count, expectedCount);
63 | };
64 | }
65 | var toRemove = createHandler(0);
66 | this.animation.addEventListener('cancel', createHandler(1));
67 | this.animation.addEventListener('cancel', createHandler(2));
68 | this.animation.addEventListener('cancel', toRemove);
69 | this.animation.addEventListener('cancel', createHandler(3));
70 | this.animation.removeEventListener('cancel', toRemove);
71 | this.animation.oncancel = function() {
72 | assert.equal(count, 3);
73 | done();
74 | };
75 | tick(0);
76 | this.animation.cancel();
77 | tick(1000);
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/test/js/animation-finish-event.js:
--------------------------------------------------------------------------------
1 | suite('animation-finish-event', function() {
2 | setup(function() {
3 | this.element = document.createElement('div');
4 | document.documentElement.appendChild(this.element);
5 | this.animation = this.element.animate([], 1000);
6 | });
7 | teardown(function() {
8 | this.element.parentNode.removeChild(this.element);
9 | });
10 |
11 | test('fire when animation completes', function(done) {
12 | var ready = false;
13 | var fired = false;
14 | var animation = this.animation;
15 | animation.onfinish = function(event) {
16 | assert(ready, 'must not be called synchronously');
17 | assert.equal(this, animation);
18 | assert.equal(event.target, animation);
19 | assert.equal(event.currentTime, 1000);
20 | assert.equal(event.timelineTime, 1100);
21 | if (fired)
22 | assert(false, 'must not get fired twice');
23 | fired = true;
24 | done();
25 | };
26 | tick(100);
27 | tick(1100);
28 | tick(2100);
29 | ready = true;
30 | });
31 |
32 | test('fire when reversed animation completes', function(done) {
33 | this.animation.onfinish = function(event) {
34 | assert.equal(event.currentTime, 0);
35 | assert.equal(event.timelineTime, 1001);
36 | done();
37 | };
38 | tick(0);
39 | tick(500);
40 | this.animation.reverse();
41 | tick(501);
42 | tick(1001);
43 | });
44 |
45 | test('multiple event listeners', function(done) {
46 | var count = 0;
47 | function createHandler(expectedCount) {
48 | return function() {
49 | count++;
50 | assert.equal(count, expectedCount);
51 | };
52 | }
53 | var toRemove = createHandler(0);
54 | this.animation.addEventListener('finish', createHandler(1));
55 | this.animation.addEventListener('finish', createHandler(2));
56 | this.animation.addEventListener('finish', toRemove);
57 | this.animation.addEventListener('finish', createHandler(3));
58 | this.animation.removeEventListener('finish', toRemove);
59 | this.animation.onfinish = function() {
60 | assert.equal(count, 3);
61 | done();
62 | };
63 | tick(0);
64 | this.animation.finish();
65 | tick(1000);
66 | });
67 |
68 | test('can wipe onfinish handler by setting it to null', function(done) {
69 | var finishAnimation = this.element.animate([], 1100);
70 | finishAnimation.onfinish = function(event) {
71 | done();
72 | };
73 |
74 | this.animation.onfinish = function(event) {
75 | assert(false, 'onfinish should be wiped');
76 | };
77 | this.animation.onfinish = null;
78 | tick(0);
79 | tick(1200);
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/test/testharness-runner.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function() {
16 | var assert = chai.assert;
17 | mocha.setup({ ui: 'tdd' });
18 |
19 | var iframe;
20 | function defineTestharnessTest(shouldPass, testFile) {
21 | var name = shouldPass ? testFile : 'Expected Failure: ' + testFile;
22 | test(name, function(done) {
23 | window.initTestHarness = function(child) {
24 | child.add_completion_callback(function(tests, harness_status) {
25 | var failures = tests.filter(function(result) {
26 | return result.status != 0;
27 | }).map(function(failure) {
28 | return failure.name + ':\n' + failure.message;
29 | });
30 | var error;
31 | if (shouldPass && failures.length) {
32 | error = new Error('\n' + failures.join('\n\n'));
33 | error.stack = null;
34 | } else if (!shouldPass && failures.length == 0) {
35 | error = new Error('\nExpected to fail, but passed');
36 | error.stack = null;
37 | }
38 | done(error);
39 | });
40 | };
41 | iframe.src = testFile;
42 | });
43 | }
44 |
45 | suite('testharness tests', function() {
46 | setup(function() {
47 | iframe = document.createElement('iframe');
48 | document.body.appendChild(iframe);
49 | });
50 | teardown(function() {
51 | iframe.parentNode.removeChild(iframe);
52 | });
53 | testHarnessTests.forEach(defineTestharnessTest.bind(null, true));
54 | testHarnessFailures.forEach(defineTestharnessTest.bind(null, false));
55 | });
56 |
57 | suite('interpolation tests', function() {
58 | setup(function() {
59 | iframe = document.createElement('iframe');
60 | document.body.appendChild(iframe);
61 | });
62 | teardown(function() {
63 | iframe.parentNode.removeChild(iframe);
64 | });
65 | interpolationTests.forEach(defineTestharnessTest.bind(null, true));
66 | interpolationFailures.forEach(defineTestharnessTest.bind(null, false));
67 | });
68 |
69 | addEventListener('load', function() {
70 | mocha.run();
71 | });
72 | })();
73 |
--------------------------------------------------------------------------------
/test/js/effect-callback.js:
--------------------------------------------------------------------------------
1 | suite('effect-callback', function() {
2 | setup(function() {
3 | document.timeline._animations = [];
4 | webAnimations1.timeline._animations = [];
5 | });
6 |
7 | test('animations starting in the future are not in effect', function() {
8 | var fractions = [];
9 | tick(100);
10 | var effect = new KeyframeEffect(null, [], 1000);
11 | effect.onsample = function(fraction) {
12 | fractions.push(fraction);
13 | };
14 | var animation = document.timeline.play(effect);
15 | animation.startTime = 1000;
16 | tick(200);
17 | tick(1000);
18 | tick(1100);
19 | assert.deepEqual(fractions, [0, 0.1]);
20 | });
21 |
22 | test('duration 0 animations get sampled at least once', function() {
23 | var timeFraction;
24 | tick(0);
25 | var effect = new KeyframeEffect(null, [], {duration: 0, fill: 'both'});
26 | effect.onsample = function(t) {
27 | timeFraction = t;
28 | };
29 | var animation = document.timeline.play(effect);
30 | tick(100);
31 | assert.equal(timeFraction, 1);
32 | assert.equal(isTicking(), false);
33 | });
34 |
35 | test('animations added during custom effect callbacks get updated in the same tick', function() {
36 | var animation;
37 | var called = false;
38 | tick(0);
39 | var effect = new KeyframeEffect(null, [], 2);
40 | var effect2 = new KeyframeEffect(null, [], 1);
41 | effect.onsample = function() {
42 | animation = document.timeline.play(effect2);
43 | };
44 | effect2.onsample = function() {
45 | called = true;
46 | };
47 | document.timeline.play(effect);
48 | tick(1);
49 | assert.isTrue(animation.startTime >= 0);
50 | assert.isFalse(called);
51 | });
52 |
53 | test('custom effect should be called after cancel', function() {
54 | var fractions = [];
55 | var effect = new KeyframeEffect(null, [], 1000);
56 | effect.onsample = function(fraction) {
57 | fractions.push(fraction);
58 | };
59 | var animation = document.timeline.play(effect);
60 | tick(0);
61 | tick(500);
62 | animation.cancel();
63 | tick(501);
64 | assert.deepEqual(fractions, [0, 0.5, null]);
65 | });
66 |
67 | test('Custom callback is given effect and animation', function() {
68 | var callbackEffect;
69 | var callbackAnim;
70 | var effect = new KeyframeEffect(document.body, [], 100);
71 | effect.onsample = function(t, e, a) {
72 | callbackEffect = e;
73 | callbackAnim = a;
74 | };
75 | var animation = document.timeline.play(effect);
76 | tick(50);
77 | tick(150);
78 | assert.equal(isTicking(), false);
79 | assert.equal(callbackAnim, animation);
80 | assert.equal(callbackEffect.target, document.body);
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/docs/support.md:
--------------------------------------------------------------------------------
1 |
2 | Getting the polyfill
3 | --------------------
4 |
5 | There are three ways to get a copy of the polyfill:
6 |
7 | 1. Download and use the `web-animations.min.js` file directly from this repository
8 | 1. Using npm: Add [`web-animations-js`](https://www.npmjs.com/package/web-animations-js) to your `package.json`
9 | 1. Using Bower: Add `web-animations/web-animations-js` to your `bower.json`
10 |
11 | Browser support
12 | ---------------
13 |
14 | The polyfill is supported on modern versions of all major browsers, including:
15 |
16 | * Chrome 56+
17 | * Firefox 27+
18 | * IE10+ (including Edge)
19 | * Safari (iOS) 7.1+
20 | * Safari (Mac) 9+
21 |
22 | In particular, the polyfill requires requestAnimationFrame. If your browser does not
23 | have requestAnimationFrame, consider loading a requestAnimationFrame polyfill first
24 | ([example](https://gist.githubusercontent.com/paulirish/1579671/raw/682e5c880c92b445650c4880a6bf9f3897ec1c5b/rAF.js)).
25 |
26 | Native fallback
27 | ---------------
28 |
29 | When the polyfill runs on a browser that implements `Element.animate()` and
30 | `Animation` playback control, it will detect and use the underlying native
31 | features for better performance.
32 |
33 | Features
34 | --------
35 |
36 | The `web-animations.min.js` polyfill target tracks the Web Animations features
37 | that are supported natively in browsers. These include:
38 |
39 | * Element.animate()
40 | * Timing input (easings, duration, fillMode, etc.) for animation effects
41 | * Playback control (play, pause, reverse, currentTime, cancel, onfinish)
42 | * Support for animating CSS properties
43 |
44 | Caveat: Prefix handling
45 | -----------------------
46 |
47 | The polyfill will automatically detect the correctly prefixed name to use when
48 | writing animated properties back to the platform. Where possible, the polyfill
49 | will only accept unprefixed versions of experimental features. For example:
50 |
51 | ```js
52 | element.animate({transform: ['none', 'translateX(100px)']}, 1000);
53 | ```
54 |
55 | will work in all browsers that implement a conforming version of transform, but
56 |
57 | ```js
58 | element.animate({webkitTransform: ['none', 'translateX(100px)']}, 1000);
59 | ```
60 |
61 | will not work anywhere.
62 |
63 | Process for breaking changes
64 | ----------------------------
65 |
66 | When we make a potentially breaking change to the polyfill's API
67 | surface (like a rename) we will, where possible, continue supporting the
68 | old version, deprecated, for three months, and ensure that there are
69 | console warnings to indicate that a change is pending. After three
70 | months, the old version of the API surface (e.g. the old version of a
71 | function name) will be removed. *If you see deprecation warnings, you
72 | can't avoid them by not updating*.
73 |
74 |
--------------------------------------------------------------------------------
/test/blink/interpolation/clip-interpolation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
94 |
--------------------------------------------------------------------------------
/test/js/group-animation-cancel-event.js:
--------------------------------------------------------------------------------
1 | suite('group-animation-cancel-event', function() {
2 | setup(function() {
3 | document.timeline.currentTime = undefined;
4 | this.element = document.createElement('div');
5 | document.documentElement.appendChild(this.element);
6 | var sequenceEffect = new SequenceEffect([
7 | new KeyframeEffect(this.element, [], 500),
8 | new GroupEffect([
9 | new KeyframeEffect(this.element, [], 250),
10 | new KeyframeEffect(this.element, [], 500),
11 | ]),
12 | ]);
13 | this.animation = document.timeline.play(sequenceEffect, 1000);
14 | });
15 | teardown(function() {
16 | if (this.element.parent)
17 | this.element.removeChild(this.element);
18 | });
19 |
20 | test('fire when animation cancelled', function(done) {
21 | var ready = false;
22 | var fired = false;
23 | var animation = this.animation;
24 | animation.oncancel = function(event) {
25 | assert(ready, 'must not be called synchronously');
26 | assert.equal(this, animation);
27 | assert.equal(event.target, animation);
28 | assert.equal(event.currentTime, null);
29 | assert.equal(event.timelineTime, 100);
30 | if (fired)
31 | assert(false, 'must not get fired twice');
32 | fired = true;
33 | done();
34 | };
35 | tick(100);
36 | animation.cancel();
37 | tick(1100);
38 | tick(2100);
39 | ready = true;
40 | });
41 |
42 | test('finish event must not fire when animation cancelled', function(done) {
43 | this.animation.onfinish = function(event) {
44 | assert(false, 'must not get fired');
45 | };
46 | this.animation.oncancel = function(event) {
47 | done();
48 | };
49 | tick(0);
50 | this.animation.cancel();
51 | tick(1100);
52 | });
53 |
54 | test('cancel event must not fire when animation finishes', function(done) {
55 | this.animation.onfinish = function(event) {
56 | done();
57 | };
58 | this.animation.oncancel = function(event) {
59 | assert(false, 'must not get fired');
60 | };
61 | tick(0);
62 | tick(1100);
63 | });
64 |
65 | test('multiple event listeners', function(done) {
66 | var count = 0;
67 | function createHandler(expectedCount) {
68 | return function() {
69 | count++;
70 | assert.equal(count, expectedCount);
71 | };
72 | }
73 | var toRemove = createHandler(0);
74 | this.animation.addEventListener('cancel', createHandler(1));
75 | this.animation.addEventListener('cancel', createHandler(2));
76 | this.animation.addEventListener('cancel', toRemove);
77 | this.animation.addEventListener('cancel', createHandler(3));
78 | this.animation.removeEventListener('cancel', toRemove);
79 | this.animation.oncancel = function() {
80 | assert.equal(count, 3);
81 | done();
82 | };
83 | tick(0);
84 | this.animation.cancel();
85 | tick(1000);
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | What is Web Animations?
3 | -----------------------
4 |
5 | A new JavaScript API for driving animated content on the web. By unifying
6 | the animation features of SVG and CSS, Web Animations unlocks features
7 | previously only usable declaratively, and exposes powerful, high-performance
8 | animation capabilities to developers.
9 |
10 | What is in this repository?
11 | ---------------------------
12 |
13 | A JavaScript implementation of the Web Animations API that provides Web
14 | Animation features in browsers that do not support it natively. The polyfill
15 | falls back to the native implementation when one is available.
16 |
17 | Quick start
18 | -----------
19 |
20 | Here's a simple example of an animation that fades and scales a `
`.
21 | [Try it as a live demo.](http://jsbin.com/yageyezabo/edit?html,js,output)
22 |
23 | ```html
24 |
25 |
26 |
27 |
28 |
Hello world!
29 |
30 |
31 |
42 | ```
43 |
44 | Documentation
45 | -------------
46 |
47 | * [Codelab tutorial](https://github.com/web-animations/web-animations-codelabs)
48 | * [Examples of usage](/docs/examples.md)
49 | * [Live demos](https://web-animations.github.io/web-animations-demos)
50 | * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate)
51 | * [W3C specification](https://drafts.csswg.org/web-animations/)
52 |
53 | We love feedback!
54 | -----------------
55 |
56 | * For feedback on the API and the specification:
57 | * File an issue on GitHub:
58 | * Alternatively, send an email to with subject line
59 | "[web-animations] ... message topic ..."
60 | ([archives](http://lists.w3.org/Archives/Public/public-fx/)).
61 |
62 | * For issues with the polyfill, report them on GitHub:
63 | .
64 |
65 | Keep up-to-date
66 | ---------------
67 |
68 | Breaking polyfill changes will be announced on this low-volume mailing list:
69 | [web-animations-changes@googlegroups.com](https://groups.google.com/forum/#!forum/web-animations-changes).
70 |
71 | More info
72 | ---------
73 |
74 | * [Technical details about the polyfill](/docs/support.md)
75 | * [Browser support](/docs/support.md#browser-support)
76 | * [Fallback to native](/docs/support.md#native-fallback)
77 | * [Feature list](/docs/support.md#features)
78 | * [Feature deprecation and removal processes](/docs/support.md#process-for-breaking-changes)
79 | * To test experimental API features, try one of the
80 | [experimental targets](/docs/experimental.md)
81 |
--------------------------------------------------------------------------------
/src/web-animations-bonus-object-form-keyframes.js:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function(shared) {
16 | // If the polyfill is being loaded in a context where Element.animate is
17 | // supported but object-form syntax is not, then creating an animation
18 | // using the new syntax will either have no effect or will throw an exception.
19 | // In either case, we want to proceed to load this part of the polyfill.
20 | //
21 | // The test animation uses an opacity other than the one the element already
22 | // has, and doesn't need to change during the animation for the test to work.
23 | // After the test, the element's opacity will be left how we found it:
24 | // - If the animation is not created, the test will leave the element's
25 | // opacity untouched at originalOpacity.
26 | // - If the animation is created, it will be cancelled, and leave the
27 | // element's opacity at originalOpacity.
28 | // - If the animation is somehow created and runs without being cancelled,
29 | // when it finishes after 1ms, it will cease to have any effect (because
30 | // fill is not specified), and opacity will again be left at originalOpacity.
31 | var element = document.documentElement;
32 | var animation = null;
33 | var animated = false;
34 | try {
35 | var originalOpacity = getComputedStyle(element).getPropertyValue('opacity');
36 | var testOpacity = originalOpacity == '0' ? '1' : '0';
37 | animation = element.animate({'opacity': [testOpacity, testOpacity]},
38 | {duration: 1});
39 | animation.currentTime = 0;
40 | animated = getComputedStyle(element).getPropertyValue('opacity') == testOpacity;
41 | } catch (error) {
42 | } finally {
43 | if (animation)
44 | animation.cancel();
45 | }
46 | if (animated) {
47 | return;
48 | }
49 |
50 | var originalElementAnimate = window.Element.prototype.animate;
51 | window.Element.prototype.animate = function(effectInput, options) {
52 | if (window.Symbol && Symbol.iterator && Array.prototype.from && effectInput[Symbol.iterator]) {
53 | // Handle custom iterables in most browsers by converting to an array
54 | effectInput = Array.from(effectInput);
55 | }
56 |
57 | if (!Array.isArray(effectInput) && effectInput !== null) {
58 | effectInput = shared.convertToArrayForm(effectInput);
59 | }
60 |
61 | return originalElementAnimate.call(this, effectInput, options);
62 | };
63 | })(webAnimationsShared);
64 |
--------------------------------------------------------------------------------
/src/web-animations-bonus-cancel-events.js:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function() {
16 |
17 | if (document.createElement('div').animate([]).oncancel !== undefined) {
18 | return;
19 | }
20 |
21 | if (WEB_ANIMATIONS_TESTING) {
22 | var now = function() { return webAnimations1.timeline.currentTime; };
23 | } else if (window.performance && performance.now) {
24 | var now = function() { return performance.now(); };
25 | } else {
26 | var now = function() { return Date.now(); };
27 | }
28 |
29 | var AnimationCancelEvent = function(target, currentTime, timelineTime) {
30 | this.target = target;
31 | this.currentTime = currentTime;
32 | this.timelineTime = timelineTime;
33 |
34 | this.type = 'cancel';
35 | this.bubbles = false;
36 | this.cancelable = false;
37 | this.currentTarget = target;
38 | this.defaultPrevented = false;
39 | this.eventPhase = Event.AT_TARGET;
40 | this.timeStamp = Date.now();
41 | };
42 |
43 | var originalElementAnimate = window.Element.prototype.animate;
44 | window.Element.prototype.animate = function(effectInput, options) {
45 | var animation = originalElementAnimate.call(this, effectInput, options);
46 |
47 | animation._cancelHandlers = [];
48 | animation.oncancel = null;
49 |
50 | var originalCancel = animation.cancel;
51 | animation.cancel = function() {
52 | originalCancel.call(this);
53 | var event = new AnimationCancelEvent(this, null, now());
54 | var handlers = this._cancelHandlers.concat(this.oncancel ? [this.oncancel] : []);
55 | setTimeout(function() {
56 | handlers.forEach(function(handler) {
57 | handler.call(event.target, event);
58 | });
59 | }, 0);
60 | };
61 |
62 | var originalAddEventListener = animation.addEventListener;
63 | animation.addEventListener = function(type, handler) {
64 | if (typeof handler == 'function' && type == 'cancel')
65 | this._cancelHandlers.push(handler);
66 | else
67 | originalAddEventListener.call(this, type, handler);
68 | };
69 |
70 | var originalRemoveEventListener = animation.removeEventListener;
71 | animation.removeEventListener = function(type, handler) {
72 | if (type == 'cancel') {
73 | var index = this._cancelHandlers.indexOf(handler);
74 | if (index >= 0)
75 | this._cancelHandlers.splice(index, 1);
76 | } else {
77 | originalRemoveEventListener.call(this, type, handler);
78 | }
79 | };
80 |
81 | return animation;
82 | };
83 | })();
84 |
--------------------------------------------------------------------------------
/test/blink/interpolation/background-position-interpolation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
23 |
69 |
70 |
--------------------------------------------------------------------------------
/src/number-handler.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function(scope, testing) {
16 |
17 | function numberToString(x) {
18 | return x.toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
19 | }
20 |
21 | function clamp(min, max, x) {
22 | return Math.min(max, Math.max(min, x));
23 | }
24 |
25 | function parseNumber(string) {
26 | if (/^\s*[-+]?(\d*\.)?\d+\s*$/.test(string))
27 | return Number(string);
28 | }
29 |
30 | function mergeNumbers(left, right) {
31 | return [left, right, numberToString];
32 | }
33 |
34 | // FIXME: This should probably go in it's own handler.
35 | function mergeFlex(left, right) {
36 | if (left == 0)
37 | return;
38 | return clampedMergeNumbers(0, Infinity)(left, right);
39 | }
40 |
41 | function mergePositiveIntegers(left, right) {
42 | return [left, right, function(x) {
43 | return Math.round(clamp(1, Infinity, x));
44 | }];
45 | }
46 |
47 | function clampedMergeNumbers(min, max) {
48 | return function(left, right) {
49 | return [left, right, function(x) {
50 | return numberToString(clamp(min, max, x));
51 | }];
52 | };
53 | }
54 |
55 | function parseNumberList(string) {
56 | var items = string.trim().split(/\s*[\s,]\s*/);
57 | if (items.length === 0) {
58 | return;
59 | }
60 | var result = [];
61 | for (var i = 0; i < items.length; i++) {
62 | var number = parseNumber(items[i]);
63 | if (number === undefined) {
64 | return;
65 | }
66 | result.push(number);
67 | }
68 | return result;
69 | }
70 |
71 | function mergeNumberLists(left, right) {
72 | if (left.length != right.length) {
73 | return;
74 | }
75 | return [left, right, function(numberList) {
76 | return numberList.map(numberToString).join(' ');
77 | }];
78 | }
79 |
80 | function round(left, right) {
81 | return [left, right, Math.round];
82 | }
83 |
84 | scope.clamp = clamp;
85 | scope.addPropertiesHandler(parseNumberList, mergeNumberLists, ['stroke-dasharray']);
86 | scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, Infinity), ['border-image-width', 'line-height']);
87 | scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, 1), ['opacity', 'shape-image-threshold']);
88 | scope.addPropertiesHandler(parseNumber, mergeFlex, ['flex-grow', 'flex-shrink']);
89 | scope.addPropertiesHandler(parseNumber, mergePositiveIntegers, ['orphans', 'widows']);
90 | scope.addPropertiesHandler(parseNumber, round, ['z-index']);
91 |
92 | scope.parseNumber = parseNumber;
93 | scope.parseNumberList = parseNumberList;
94 | scope.mergeNumbers = mergeNumbers;
95 | scope.numberToString = numberToString;
96 |
97 | })(webAnimations1, webAnimationsTesting);
98 |
--------------------------------------------------------------------------------
/docs/experimental.md:
--------------------------------------------------------------------------------
1 |
2 | Experimental build targets
3 | --------------------------
4 |
5 | Most people should use the basic polyfill in `web-animations.min.js`. This
6 | tracks the Web Animations features that are supported natively in browsers.
7 | However, we also provide two additional build targets that contain experimental
8 | features.
9 |
10 | ### web-animations-next.min.js
11 |
12 | Contains all of web-animations.min.js plus features that are still undergoing
13 | discussion or have yet to be implemented natively.
14 |
15 | ### web-animations-next-lite.min.js
16 |
17 | A cut down version of web-animations-next, it removes several lesser used
18 | property handlers and some of the larger and less used features such as matrix
19 | interpolation/decomposition.
20 |
21 | Build target comparison
22 | -----------------------
23 |
24 | | | web-animations | web-animations-next | web-animations-next-lite |
25 | |------------------------|:--------------:|:-------------------:|:------------------------:|
26 | |Size (gzipped) | 15KB | 19KB | 15KB |
27 | |Element.animate | ✔ | ✔ | ✔ |
28 | |Timing input (easings, duration, fillMode, etc.) for animation effects| ✔ | ✔ | ✔ |
29 | |Playback control | ✔ | ✔ | ✔ |
30 | |Support for animating lengths, transforms and opacity| ✔ | ✔ | ✔ |
31 | |Support for animating other CSS properties| ✔ | ✔ | 🚫 |
32 | |Matrix fallback for transform animations | ✔ | ✔ | 🚫 |
33 | |KeyframeEffect constructor | 🚫 | ✔ | ✔ |
34 | |Simple GroupEffects & SequenceEffects | 🚫 | ✔ | ✔ |
35 | |Custom Effects | 🚫 | ✔ | ✔ |
36 | |Timing input (easings, duration, fillMode, etc.) for groups
| 🚫 | 🚫\* | 🚫 |
37 | |Additive animation | 🚫\* | 🚫\* | 🚫 |
38 | |Motion path | 🚫\* | 🚫\* | 🚫 |
39 | |Modifiable keyframe effect timing| 🚫 | 🚫\* | 🚫\* |
40 | |Modifiable group timing | 🚫 | 🚫\* | 🚫\* |
41 | |Usable inline style\*\* | ✔ | ✔ | 🚫 |
42 |
43 | \* support is planned for these features.
44 | \*\* see inline style caveat below.
45 |
46 | Caveat: Inline style
47 | --------------------
48 |
49 | Inline style modification is the mechanism used by the polyfill to animate
50 | properties. Both web-animations and web-animations-next incorporate a module
51 | that emulates a vanilla inline style object, so that style modification from
52 | JavaScript can still work in the presence of animations. However, to keep the
53 | size of web-animations-next-lite as small as possible, the style emulation
54 | module is not included. When using this version of the polyfill, JavaScript
55 | inline style modification will be overwritten by animations.
56 | Due to browser constraints inline style modification is not supported on iOS 7
57 | or Safari 6 (or earlier versions).
58 |
59 |
--------------------------------------------------------------------------------
/externs/web-animations-next.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 |
18 | /**
19 | * @fileoverview Basic externs for the Web Animations API (Level 2 / Groups).
20 | * This is not intended to be exhaustive, and requires the base externs from
21 | * web-animations.js.
22 | * @externs
23 | */
24 |
25 |
26 | /**
27 | * @interface
28 | */
29 | var AnimationEffectReadOnly = function() {};
30 |
31 | /** @type {!AnimationEffectTiming} */
32 | AnimationEffectReadOnly.prototype.timing;
33 |
34 |
35 | /**
36 | * @param {Element} target
37 | * @param {!Array} frames
38 | * @param {(number|AnimationEffectTimingProperties)=} opt_options
39 | * @constructor
40 | * @implements {AnimationEffectReadOnly}
41 | */
42 | var KeyframeEffect = function(target, frames, opt_options) {};
43 |
44 | /**
45 | * @return {!Array}
46 | */
47 | KeyframeEffect.prototype.getFrames = function() {};
48 |
49 | /** @type {!AnimationEffectTiming} */
50 | KeyframeEffect.prototype.timing;
51 |
52 | /** @type {Element} */
53 | KeyframeEffect.prototype.target;
54 |
55 | /** @type {?function(number, !KeyframeEffect, !Animation)} */
56 | KeyframeEffect.prototype.onsample;
57 |
58 |
59 | /**
60 | * @param {!Array} children
61 | * @param {AnimationEffectTimingProperties=} opt_timing
62 | * @constructor
63 | * @implements {AnimationEffectReadOnly}
64 | */
65 | var SequenceEffect = function(children, opt_timing) {};
66 |
67 | /** @type {!AnimationEffectTiming} */
68 | SequenceEffect.prototype.timing;
69 |
70 | /** @type {!Array} */
71 | SequenceEffect.prototype.children;
72 |
73 |
74 | /**
75 | * @param {!Array} children
76 | * @param {AnimationEffectTimingProperties=} opt_timing
77 | * @constructor
78 | * @implements {AnimationEffectReadOnly}
79 | */
80 | var GroupEffect = function(children, opt_timing) {};
81 |
82 | /** @type {!AnimationEffectTiming} */
83 | GroupEffect.prototype.timing;
84 |
85 | /** @type {!Array} */
86 | GroupEffect.prototype.children;
87 |
88 |
89 | /**
90 | * @interface
91 | */
92 | var AnimationTimeline = function() {};
93 |
94 | /** @type {?number} */
95 | AnimationTimeline.prototype.currentTime;
96 |
97 | /**
98 | * @param {!AnimationEffectReadOnly} effect
99 | * @return {!Animation}
100 | */
101 | AnimationTimeline.prototype.play = function(effect) {};
102 |
103 | /**
104 | * @interface
105 | * @extends {AnimationTimeline}
106 | */
107 | var DocumentTimeline = function() {};
108 |
109 | /** @type {AnimationEffectReadOnly|undefined} */
110 | Animation.prototype.effect;
111 |
112 | /** @type {!DocumentTimeline} */
113 | Document.prototype.timeline;
114 |
--------------------------------------------------------------------------------
/test/blink/interpolation/shape-outside.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
86 |
87 |
--------------------------------------------------------------------------------
/externs/web-animations.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 |
18 | /**
19 | * @fileoverview Basic externs for the Web Animations API. This is not
20 | * nessecarily exhaustive. For more information, see the spec-
21 | * https://drafts.csswg.org/web-animations/
22 | * @externs
23 | */
24 |
25 |
26 | /**
27 | * @param {!Array} frames
28 | * @param {(number|AnimationEffectTimingProperties)=} opt_options
29 | * @return {!Animation}
30 | */
31 | Element.prototype.animate = function(frames, opt_options) {};
32 |
33 |
34 | /**
35 | * @interface
36 | * @extends {EventTarget}
37 | */
38 | var Animation = function() {};
39 |
40 | /**
41 | * @return {undefined}
42 | */
43 | Animation.prototype.cancel = function() {};
44 |
45 | /**
46 | * @return {undefined}
47 | */
48 | Animation.prototype.finish = function() {};
49 |
50 | /**
51 | * @return {undefined}
52 | */
53 | Animation.prototype.reverse = function() {};
54 |
55 | /**
56 | * @return {undefined}
57 | */
58 | Animation.prototype.pause = function() {};
59 |
60 | /**
61 | * @return {undefined}
62 | */
63 | Animation.prototype.play = function() {};
64 |
65 | /** @type {number} */
66 | Animation.prototype.startTime;
67 |
68 | /** @type {number} */
69 | Animation.prototype.currentTime;
70 |
71 | /** @type {number} */
72 | Animation.prototype.playbackRate;
73 |
74 | /** @type {string} */
75 | Animation.prototype.playState;
76 |
77 | /** @type {?function(!Event)} */
78 | Animation.prototype.oncancel;
79 |
80 | /** @type {?function(!Event)} */
81 | Animation.prototype.onfinish;
82 |
83 |
84 | /**
85 | * @typedef {{
86 | * delay: (number|undefined),
87 | * endDelay: (number|undefined),
88 | * fillMode: (string|undefined),
89 | * iterationStart: (number|undefined),
90 | * iterations: (number|undefined),
91 | * duration: (number|string|undefined),
92 | * direction: (string|undefined),
93 | * easing: (string|undefined)
94 | * }}
95 | */
96 | var AnimationEffectTimingProperties;
97 |
98 |
99 | /**
100 | * @interface
101 | */
102 | var AnimationEffectTiming = function() {};
103 |
104 | /** @type {number} */
105 | AnimationEffectTiming.prototype.delay;
106 |
107 | /** @type {number} */
108 | AnimationEffectTiming.prototype.endDelay;
109 |
110 | /** @type {string} */
111 | AnimationEffectTiming.prototype.fillMode;
112 |
113 | /** @type {number} */
114 | AnimationEffectTiming.prototype.iterationStart;
115 |
116 | /** @type {number} */
117 | AnimationEffectTiming.prototype.iterations;
118 |
119 | /** @type {number|string} */
120 | AnimationEffectTiming.prototype.duration;
121 |
122 | /** @type {string} */
123 | AnimationEffectTiming.prototype.direction;
124 |
125 | /** @type {string} */
126 | AnimationEffectTiming.prototype.easing;
127 |
--------------------------------------------------------------------------------
/src/shape-handler.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | (function(scope) {
16 |
17 | var consumeLengthOrPercent = scope.consumeParenthesised.bind(null, scope.parseLengthOrPercent);
18 | var consumeLengthOrPercentPair = scope.consumeRepeated.bind(undefined, consumeLengthOrPercent, /^/);
19 |
20 | var mergeSizePair = scope.mergeNestedRepeated.bind(undefined, scope.mergeDimensions, ' ');
21 | var mergeSizePairList = scope.mergeNestedRepeated.bind(undefined, mergeSizePair, ',');
22 |
23 | function parseShape(input) {
24 | var circle = scope.consumeToken(/^circle/, input);
25 | if (circle && circle[0]) {
26 | return ['circle'].concat(scope.consumeList([
27 | scope.ignore(scope.consumeToken.bind(undefined, /^\(/)),
28 | consumeLengthOrPercent,
29 | scope.ignore(scope.consumeToken.bind(undefined, /^at/)),
30 | scope.consumePosition,
31 | scope.ignore(scope.consumeToken.bind(undefined, /^\)/))
32 | ], circle[1]));
33 | }
34 | var ellipse = scope.consumeToken(/^ellipse/, input);
35 | if (ellipse && ellipse[0]) {
36 | return ['ellipse'].concat(scope.consumeList([
37 | scope.ignore(scope.consumeToken.bind(undefined, /^\(/)),
38 | consumeLengthOrPercentPair,
39 | scope.ignore(scope.consumeToken.bind(undefined, /^at/)),
40 | scope.consumePosition,
41 | scope.ignore(scope.consumeToken.bind(undefined, /^\)/))
42 | ], ellipse[1]));
43 | }
44 | var polygon = scope.consumeToken(/^polygon/, input);
45 | if (polygon && polygon[0]) {
46 | return ['polygon'].concat(scope.consumeList([
47 | scope.ignore(scope.consumeToken.bind(undefined, /^\(/)),
48 | scope.optional(scope.consumeToken.bind(undefined, /^nonzero\s*,|^evenodd\s*,/), 'nonzero,'),
49 | scope.consumeSizePairList,
50 | scope.ignore(scope.consumeToken.bind(undefined, /^\)/))
51 | ], polygon[1]));
52 | }
53 | }
54 |
55 | function mergeShapes(left, right) {
56 | if (left[0] !== right[0])
57 | return;
58 | if (left[0] == 'circle') {
59 | return scope.mergeList(left.slice(1), right.slice(1), [
60 | 'circle(',
61 | scope.mergeDimensions,
62 | ' at ',
63 | scope.mergeOffsetList,
64 | ')']);
65 | }
66 | if (left[0] == 'ellipse') {
67 | return scope.mergeList(left.slice(1), right.slice(1), [
68 | 'ellipse(',
69 | scope.mergeNonNegativeSizePair,
70 | ' at ',
71 | scope.mergeOffsetList,
72 | ')']);
73 | }
74 | if (left[0] == 'polygon' && left[1] == right[1]) {
75 | return scope.mergeList(left.slice(2), right.slice(2), [
76 | 'polygon(',
77 | left[1],
78 | mergeSizePairList,
79 | ')']);
80 | }
81 | }
82 |
83 | scope.addPropertiesHandler(parseShape, mergeShapes, ['shape-outside']);
84 |
85 | })(webAnimations1);
86 |
--------------------------------------------------------------------------------
/src/effect-callback.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | (function(shared, scope, testing) {
15 |
16 | var nullTarget = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
17 |
18 | var sequenceNumber = 0;
19 | scope.bindAnimationForCustomEffect = function(animation) {
20 | var target = animation.effect.target;
21 | var effectFunction;
22 | var isKeyframeEffect = typeof animation.effect.getFrames() == 'function';
23 | if (isKeyframeEffect) {
24 | effectFunction = animation.effect.getFrames();
25 | } else {
26 | effectFunction = animation.effect._onsample;
27 | }
28 | var timing = animation.effect.timing;
29 | var last = null;
30 | timing = shared.normalizeTimingInput(timing);
31 | var callback = function() {
32 | var t = callback._animation ? callback._animation.currentTime : null;
33 | if (t !== null) {
34 | t = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), t, timing);
35 | if (isNaN(t))
36 | t = null;
37 | }
38 | // FIXME: There are actually more conditions under which the effectFunction
39 | // should be called.
40 | if (t !== last) {
41 | if (isKeyframeEffect) {
42 | effectFunction(t, target, animation.effect);
43 | } else {
44 | effectFunction(t, animation.effect, animation.effect._animation);
45 | }
46 | }
47 | last = t;
48 | };
49 |
50 | callback._animation = animation;
51 | callback._registered = false;
52 | callback._sequenceNumber = sequenceNumber++;
53 | animation._callback = callback;
54 | register(callback);
55 | };
56 |
57 | var callbacks = [];
58 | var ticking = false;
59 | function register(callback) {
60 | if (callback._registered)
61 | return;
62 | callback._registered = true;
63 | callbacks.push(callback);
64 | if (!ticking) {
65 | ticking = true;
66 | requestAnimationFrame(tick);
67 | }
68 | }
69 |
70 | function tick(t) {
71 | var updating = callbacks;
72 | callbacks = [];
73 | updating.sort(function(left, right) {
74 | return left._sequenceNumber - right._sequenceNumber;
75 | });
76 | updating = updating.filter(function(callback) {
77 | callback();
78 | var playState = callback._animation ? callback._animation.playState : 'idle';
79 | if (playState != 'running' && playState != 'pending')
80 | callback._registered = false;
81 | return callback._registered;
82 | });
83 | callbacks.push.apply(callbacks, updating);
84 |
85 | if (callbacks.length) {
86 | ticking = true;
87 | requestAnimationFrame(tick);
88 | } else {
89 | ticking = false;
90 | }
91 | }
92 |
93 | scope.Animation.prototype._register = function() {
94 | if (this._callback)
95 | register(this._callback);
96 | };
97 |
98 | })(webAnimationsShared, webAnimationsNext, webAnimationsTesting);
99 |
--------------------------------------------------------------------------------
/src/timeline.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 |
16 | (function(shared, scope, testing) {
17 | var originalRequestAnimationFrame = window.requestAnimationFrame;
18 | window.requestAnimationFrame = function(f) {
19 | return originalRequestAnimationFrame(function(x) {
20 | scope.timeline._updateAnimationsPromises();
21 | f(x);
22 | scope.timeline._updateAnimationsPromises();
23 | });
24 | };
25 |
26 | scope.AnimationTimeline = function() {
27 | this._animations = [];
28 | this.currentTime = undefined;
29 | };
30 |
31 | scope.AnimationTimeline.prototype = {
32 | getAnimations: function() {
33 | this._discardAnimations();
34 | return this._animations.slice();
35 | },
36 | _updateAnimationsPromises: function() {
37 | scope.animationsWithPromises = scope.animationsWithPromises.filter(function(animation) {
38 | return animation._updatePromises();
39 | });
40 | },
41 | _discardAnimations: function() {
42 | this._updateAnimationsPromises();
43 | this._animations = this._animations.filter(function(animation) {
44 | return animation.playState != 'finished' && animation.playState != 'idle';
45 | });
46 | },
47 | _play: function(effect) {
48 | var animation = new scope.Animation(effect, this);
49 | this._animations.push(animation);
50 | scope.restartWebAnimationsNextTick();
51 | // Use animation._animation.play() here, NOT animation.play().
52 | //
53 | // Timeline.play calls new scope.Animation(effect) which (indirectly) calls Timeline.play on
54 | // effect's children, and Animation.play is also recursive. We only need to call play on each
55 | // animation in the tree once.
56 | animation._updatePromises();
57 | animation._animation.play();
58 | animation._updatePromises();
59 | return animation;
60 | },
61 | play: function(effect) {
62 | if (effect) {
63 | effect.remove();
64 | }
65 | return this._play(effect);
66 | }
67 | };
68 |
69 | var ticking = false;
70 |
71 | scope.restartWebAnimationsNextTick = function() {
72 | if (!ticking) {
73 | ticking = true;
74 | requestAnimationFrame(webAnimationsNextTick);
75 | }
76 | };
77 |
78 | function webAnimationsNextTick(t) {
79 | var timeline = scope.timeline;
80 | timeline.currentTime = t;
81 | timeline._discardAnimations();
82 | if (timeline._animations.length == 0)
83 | ticking = false;
84 | else
85 | requestAnimationFrame(webAnimationsNextTick);
86 | }
87 |
88 | var timeline = new scope.AnimationTimeline();
89 | scope.timeline = timeline;
90 |
91 | try {
92 | Object.defineProperty(window.document, 'timeline', {
93 | configurable: true,
94 | get: function() { return timeline; }
95 | });
96 | } catch (e) { }
97 | try {
98 | window.document.timeline = timeline;
99 | } catch (e) { }
100 |
101 | })(webAnimationsShared, webAnimationsNext, webAnimationsTesting);
102 |
--------------------------------------------------------------------------------
/test/blink/eased-keyframes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
96 |
--------------------------------------------------------------------------------
/.travis-setup.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2014 Google Inc. All rights reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | #! /bin/bash
16 |
17 | set -x
18 | set -e
19 |
20 | # Make sure /dev/shm has correct permissions.
21 | ls -l /dev/shm
22 | sudo chmod 1777 /dev/shm
23 | ls -l /dev/shm
24 |
25 | uname -a
26 | cat /etc/lsb-release
27 |
28 | npm install
29 | # npm may update the package-lock.json. Revert this to avoid failing grunt-checkrepo.
30 | git checkout HEAD package-lock.json
31 |
32 | npm install -g grunt-cli
33 |
34 | sudo apt-get update --fix-missing
35 |
36 | # Install python-imaging from the environment rather then build it.
37 | # If the below fails, pip will build it via the requirements.txt
38 | # sudo apt-get install python-imaging
39 | # VIRTUAL_ENV_site_packages=$(echo $VIRTUAL_ENV/lib/*/site-packages)
40 | # VIRTUAL_ENV_python_version=$(echo $VIRTUAL_ENV_site_packages | sed -e's@.*/\(.*\)/site-packages@\1@')
41 | # ln -s /usr/lib/$VIRTUAL_ENV_python_version/dist-packages/PIL.pth $VIRTUAL_ENV_site_packages/PIL.pth
42 | # ln -s /usr/lib/$VIRTUAL_ENV_python_version/dist-packages/PIL $VIRTUAL_ENV_site_packages/PIL
43 |
44 | export VERSION=$(echo $BROWSER | sed -e's/[^-]*-//')
45 | export BROWSER=$(echo $BROWSER | sed -e's/-.*//')
46 |
47 | echo BROWSER=$BROWSER
48 | echo VERSION=$VERSION
49 |
50 | sudo ln -sf $(which true) $(which xdg-desktop-menu)
51 |
52 | case $BROWSER in
53 | Android)
54 | sudo apt-get install -qq --force-yes \
55 | libc6:i386 libgcc1:i386 gcc-4.6-base:i386 libstdc++5:i386 \
56 | libstdc++6:i386 lib32z1 libreadline6-dev:i386 \
57 | libncurses5-dev:i386
58 | bash tools/android/setup.sh
59 | ;;
60 |
61 | Chrome)
62 | echo "Getting $VERSION of $BROWSER"
63 | export CHROME=google-chrome-${VERSION}_current_amd64.deb
64 | wget https://dl.google.com/linux/direct/$CHROME
65 | sudo dpkg --install $CHROME || sudo apt-get -f install
66 | which google-chrome
67 | ls -l `which google-chrome`
68 |
69 | if [ -f /opt/google/chrome/chrome-sandbox ]; then
70 | export CHROME_SANDBOX=/opt/google/chrome/chrome-sandbox
71 | else
72 | export CHROME_SANDBOX=$(ls /opt/google/chrome*/chrome-sandbox)
73 | fi
74 |
75 | # Download a custom chrome-sandbox which works inside OpenVC containers (used on travis).
76 | sudo rm -f $CHROME_SANDBOX
77 | sudo wget https://googledrive.com/host/0B5VlNZ_Rvdw6NTJoZDBSVy1ZdkE -O $CHROME_SANDBOX
78 | sudo chown root:root $CHROME_SANDBOX; sudo chmod 4755 $CHROME_SANDBOX
79 | sudo md5sum $CHROME_SANDBOX
80 |
81 | google-chrome --version
82 | ;;
83 |
84 | Firefox)
85 | sudo rm -f /usr/local/bin/firefox
86 | case $VERSION in
87 | stable)
88 | ;;
89 | beta)
90 | yes "\n" | sudo add-apt-repository -y ppa:mozillateam/firefox-next
91 | ;;
92 | # TODO(alancutter): Add support for firefox-aurora.
93 | # This will need to download the binary manually as the ubuntu-mozilla-daily
94 | # ppa doesn't support the version of linux that Travis uses.
95 | esac
96 | sudo apt-get update --fix-missing
97 | sudo apt-get install firefox
98 | which firefox
99 | ls -l `which firefox`
100 | firefox --version
101 | ;;
102 | esac
103 |
104 | # R=tools/python/requirements.txt
105 | # pip install -r $R --use-mirrors || pip install -r $R
106 |
--------------------------------------------------------------------------------
/test/blink/get-animations.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |