Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animations: support style keyframes #9647

Merged
merged 4 commits into from
Jun 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions examples/animations.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
background: rgba(0, 0, 0, 0.25);
border-radius: 50%;
}

@keyframes rotate {
100% {
transform: rotate(calc(var(--angle) * -3));
}
}
</style>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
Expand Down Expand Up @@ -82,9 +88,7 @@
"selector": ".rot-image",
"delay": "var(--delay)",
"easing": "cubic-bezier(0,0,.21,1)",
"keyframes": {
"transform": "rotate(calc(var(--angle) * -2))"
}
"keyframes": "rotate"
},
{
"animation": "anim2",
Expand Down
2 changes: 1 addition & 1 deletion extensions/amp-animation/0.1/test/test-amp-animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ describes.sandboxed('AmpAnimation', {}, () => {
it('should create runner with args', () => {
const args = {};
anim.triggered_ = true;
createRunnerStub.restore();
createRunnerStub./*OK*/restore();
const stub = sandbox.stub(Builder.prototype, 'createRunner',
() => runner);
return anim.startOrResume_(args).then(() => {
Expand Down
37 changes: 36 additions & 1 deletion extensions/amp-animation/0.1/test/test-web-animations.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
WebAnimationPlayState,
} from '../web-animation-types';
import {isArray, isObject} from '../../../../src/types';
import {poll} from '../../../../testing/iframe';
import {user} from '../../../../src/log';
import * as sinon from 'sinon';

Expand Down Expand Up @@ -305,7 +306,7 @@ describes.realWin('MeasureScanner', {amp: 1}, env => {
'--child5': 'var(--child6)', // Reverse order dependency.
'--child6': '23px',
keyframes: {
transform: 'translate()',
transform: 'translate(var(--child3), var(--child4))',
},
}],
});
Expand All @@ -320,6 +321,8 @@ describes.realWin('MeasureScanner', {amp: 1}, env => {
'--child5': '23px',
'--child6': '23px',
});
expect(requests[0].keyframes.transform[1])
.to.equal('translate(11px,22px)');
});

it('should accept keyframe animation', () => {
Expand Down Expand Up @@ -516,6 +519,38 @@ describes.realWin('MeasureScanner', {amp: 1}, env => {
expect(keyframes.opacity).to.jsonEqual(['0', '0.525']);
});

it('should fail when cannot discover style keyframes', () => {
expect(() => scan({target: target1, keyframes: 'keyframes1'}))
.to.throw(/Keyframes not found/);
});

it('should discover style keyframes', () => {
const name = 'keyframes1';
const css = 'from{opacity: 0} to{opacity: 1}';
const style = doc.createElement('style');
style.setAttribute('amp-custom', '');
style.textContent =
`@-ms-keyframes ${name} {${css}}` +
`@-moz-keyframes ${name} {${css}}` +
`@-webkit-keyframes ${name} {${css}}` +
`@keyframes ${name} {${css}}`;
doc.head.appendChild(style);
return poll('wait for style', () => {
for (let i = 0; i < doc.styleSheets.length; i++) {
if (doc.styleSheets[i].ownerNode == style) {
return true;
}
}
return false;
}).then(() => {
const keyframes = scan({target: target1, keyframes: name})[0].keyframes;
expect(keyframes).to.jsonEqual([
{offset: 0, opacity: '0'},
{offset: 1, opacity: '1'},
]);
});
});

it('should check media in top animation', () => {
const requests = scan({
duration: 500,
Expand Down
2 changes: 1 addition & 1 deletion extensions/amp-animation/0.1/web-animation-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export let WebCompAnimationDef;
* @mixes WebAnimationTimingDef
* @mixes WebAnimationMediaDef
* @typedef {{
* keyframes: !WebKeyframesDef,
* keyframes: (string|!WebKeyframesDef),
* }}
*/
export let WebKeyframeAnimationDef;
Expand Down
13 changes: 11 additions & 2 deletions extensions/amp-animation/0.1/web-animations.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {ScrollboundPlayer} from './scrollbound-player';
import {assertHttpsUrl, resolveRelativeUrl} from '../../../src/url';
import {closestBySelector} from '../../../src/dom';
import {dev, user} from '../../../src/log';
import {extractKeyframes} from './keyframes-extractor';
import {getMode} from '../../../src/mode';
import {getVendorJsPropertyName, computedStyle} from '../../../src/style';
import {isArray, isObject, toArray} from '../../../src/types';
Expand Down Expand Up @@ -354,7 +355,7 @@ class Scanner {
export class Builder {
/**
* @param {!Window} win
* @param {!Node} rootNode
* @param {!Document|!ShadowRoot} rootNode
* @param {string} baseUrl
* @param {!../../../src/service/vsync-impl.Vsync} vsync
* @param {!../../../src/service/resources-impl.Resources} resources
Expand Down Expand Up @@ -581,6 +582,14 @@ export class MeasureScanner extends Scanner {
* @private
*/
createKeyframes_(target, spec) {
if (typeof spec.keyframes == 'string') {
// Keyframes name to be extracted from `<style>`.
const keyframes = extractKeyframes(this.css_.rootNode_, spec.keyframes);
user().assert(keyframes,
`Keyframes not found in stylesheet: "${spec.keyframes}"`);
return /** @type {!WebKeyframesDef} */ (keyframes);
}

if (isObject(spec.keyframes)) {
// Property -> keyframes form.
// The object is cloned, while properties are verified to be
Expand Down Expand Up @@ -852,7 +861,7 @@ export class MeasureScanner extends Scanner {
class CssContextImpl {
/**
* @param {!Window} win
* @param {!Node} rootNode
* @param {!Document|!ShadowRoot} rootNode
* @param {string} baseUrl
*/
constructor(win, rootNode, baseUrl) {
Expand Down
34 changes: 32 additions & 2 deletions extensions/amp-animation/amp-animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ Animation components inherit timing properties specified for the top-level anima

### Keyframes

Keyframes can be specified in numerous ways described in the [keyframes section](https://www.w3.org/TR/web-animations/#processing-a-keyframes-argument) of the Web Animations spec.
Keyframes can be specified in numerous ways described in the [keyframes section](https://www.w3.org/TR/web-animations/#processing-a-keyframes-argument) of the Web Animations spec or as a string refering to the `@keyframes` name in the CSS.

Some typical examples of keyframes definitions are below.

Expand Down Expand Up @@ -282,6 +282,36 @@ For additional keyframes formats refer to [Web Animations spec](https://www.w3.o

The property values allow any valid CSS values, including `calc()`, `var()` and other CSS expressions.

#### Keyframes from CSS

Another way to specify keyframes is in the document's stylesheet (`<style>` tag) as `@keyframes` CSS rule. For instance:
```html
<style amp-custom>
@keyframes keyframes1 {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>

<amp-animation layout="nodisplay">
<script type="application/json">
{
"duration": "1s",
"keyframes": "keyframes1"
}
</script>
</amp-animation>
```

CSS `@keyframes` are mostly equivalent to inlining keyframes definition in the JSON per [Web Animations spec](https://www.w3.org/TR/web-animations/#processing-a-keyframes-argument). However, there are some nuances:
- For broad-platform support, vendor prefixes, e.g. `@-ms-keyframes {}` or `-moz-transform` may be needed. Vendor prefixes are not needed and not allowed in the JSON format, but in CSS they could be necessary.
- Platforms that do not support `calc()` and `var()` will not be able to take advantage of `amp-animation` polyfills when keyframes are specified in CSS. It's thus recommended to always include fallback values in CSS.
- CSS extensions such as [`width()`, `height()` and `rand()`](#css-extensions) cannot be used in CSS.


#### Whitelisted properties for keyframes

Expand Down Expand Up @@ -427,7 +457,7 @@ Both `var()` and `calc()` polyfilled on platforms that do not directly support t
</amp-animation>
```

Animation components can specify their own variables as `--var-name` fields. These variables are propagated into nested animations and override variables of target elements specified via `<style>`. `var()` expressions first try to resolve variable values specified in the animations and then by querying target styles.
Animation components can specify their own variables as `--var-name` fields. These variables are propagated into nested animations and override variables of target elements specified via stylesheet (`<style>` tag). `var()` expressions first try to resolve variable values specified in the animations and then by querying target styles.


### CSS extensions
Expand Down