Skip to content

Commit

Permalink
Animations: subtargets format (#9655)
Browse files Browse the repository at this point in the history
* Animations: subtargets format

* fix docs
  • Loading branch information
Dima Voytenko committed Jun 1, 2017
1 parent cfd3a87 commit 4e8d6e4
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 5 deletions.
13 changes: 12 additions & 1 deletion examples/animations.amp.html
Expand Up @@ -122,6 +122,17 @@
"--delay": "rand(0.1s, var(--max-delay))",
"delay": "var(--delay)",
"direction": "normal",
"subtargets": [
{
"index": 0,
"direction": "reverse"
},
{
"selector": ".antigrav",
"direction": "reverse",
"--delay": "0s"
}
],
"keyframes": {"transform": "translateY(120vh)"}
}
</script>
Expand All @@ -130,7 +141,7 @@
<div class="rain">
<div class="drop"></div>
<div class="drop"></div>
<div class="drop"></div>
<div class="drop antigrav"></div>
<div class="drop"></div>
<div class="drop"></div>
<div class="drop"></div>
Expand Down
71 changes: 71 additions & 0 deletions extensions/amp-animation/0.1/test/test-web-animations.js
Expand Up @@ -325,6 +325,77 @@ describes.realWin('MeasureScanner', {amp: 1}, env => {
.to.equal('translate(11px,22px)');
});

it('should override vars in subtargets with index', () => {
const requests = scan({
'--parent1': '11px',
'--parent2': '12px',
animations: [{
selector: '.target',
'--child1': '21px',
'--parent2': '22px', // Override parent.
'--child2': 'var(--child1)',
'--child3': 'var(--parent1)',
'--child4': 'var(--parent2)',
'--child5': 'var(--child6)', // Reverse order dependency.
'--child6': '23px',
subtargets: [
// By index.
{
index: 0,
'--child6': '31px',
},
{
index: 1,
'--child6': '32px',
},
// By selector.
{
selector: '#target1',
'--child1': '33px',
},
{
selector: '#target2',
'--child1': '34px',
},
{
selector: 'div',
'--child2': '35px',
},
],
keyframes: {
transform: 'translate(var(--child6), var(--child1))',
},
}],
});
expect(requests).to.have.length(2);

// `#target1`
expect(requests[0].vars).to.jsonEqual({
'--parent1': '11px',
'--parent2': '22px',
'--child1': '33px', // Overriden via `#target1`
'--child2': '35px', // Overriden via `div`
'--child3': '11px',
'--child4': '22px',
'--child5': '31px', // Overriden via `index: 0`
'--child6': '31px', // Overriden via `index: 0`
});
expect(requests[0].keyframes.transform[1]).to.equal('translate(31px,33px)');

// `#target2`
expect(requests[1].vars).to.jsonEqual({
'--parent1': '11px',
'--parent2': '22px',
'--child1': '34px', // Overriden via `#target2`
'--child2': '35px', // Overriden via `div`
'--child3': '11px',
'--child4': '22px',
'--child5': '32px', // Overriden via `index: 1`
'--child6': '32px', // Overriden via `index: 1`
});
expect(requests[1].keyframes.transform[1]).to.equal('translate(32px,34px)');
});

it('should accept keyframe animation', () => {
const requests = scan({
target: target1,
Expand Down
26 changes: 26 additions & 0 deletions extensions/amp-animation/0.1/web-animation-types.js
Expand Up @@ -24,6 +24,7 @@ export let WebAnimationDef;
/**
* @mixes WebAnimationSelectorDef
* @mixes WebAnimationTimingDef
* @mixes WebAnimationVarsDef
* @mixes WebAnimationMediaDef
* @typedef {{
* animations: !Array<!WebAnimationDef>,
Expand All @@ -35,6 +36,7 @@ export let WebMultiAnimationDef;
/**
* @mixes WebAnimationSelectorDef
* @mixes WebAnimationTimingDef
* @mixes WebAnimationVarsDef
* @mixes WebAnimationMediaDef
* @typedef {{
* animation: string,
Expand All @@ -46,6 +48,7 @@ export let WebCompAnimationDef;
/**
* @mixes WebAnimationSelectorDef
* @mixes WebAnimationTimingDef
* @mixes WebAnimationVarsDef
* @mixes WebAnimationMediaDef
* @typedef {{
* keyframes: (string|!WebKeyframesDef),
Expand Down Expand Up @@ -79,6 +82,16 @@ export let WebKeyframesDef;
export let WebAnimationTimingDef;


/**
* Indicates an extension to a type that allows specifying vars. Vars are
* specified as properties with the name in the format of `--varName`.
*
* @mixin
* @typedef {Object}
*/
export let WebAnimationVarsDef;


/**
* Defines media parameters for an animation.
*
Expand All @@ -94,11 +107,24 @@ export let WebAnimationMediaDef;
* @typedef {{
* target: (!Element|undefined),
* selector: (string|undefined),
* subtargets: (!Array<!WebAnimationSubtargetDef>|undefined),
* }}
*/
export let WebAnimationSelectorDef;


/**
* @mixes WebAnimationTimingDef
* @mixes WebAnimationVarsDef
* @typedef {{
* matcher: (function(!Element, number):boolean|undefined),
* index: (number|undefined),
* selector: (string|undefined),
* }}
*/
export let WebAnimationSubtargetDef;


/**
* See https://developer.mozilla.org/en-US/docs/Web/API/Animation/playState
* @enum {string}
Expand Down
67 changes: 63 additions & 4 deletions extensions/amp-animation/0.1/web-animations.js
Expand Up @@ -18,7 +18,7 @@ import {CssNumberNode, CssTimeNode, isVarCss} from './css-expr-ast';
import {Observable} from '../../../src/observable';
import {ScrollboundPlayer} from './scrollbound-player';
import {assertHttpsUrl, resolveRelativeUrl} from '../../../src/url';
import {closestBySelector} from '../../../src/dom';
import {closestBySelector, matches} from '../../../src/dom';
import {dev, user} from '../../../src/log';
import {extractKeyframes} from './keyframes-extractor';
import {getMode} from '../../../src/mode';
Expand All @@ -29,6 +29,8 @@ import {parseCss} from './css-expr';
import {
WebAnimationDef,
WebAnimationPlayState,
WebAnimationSelectorDef,
WebAnimationSubtargetDef,
WebAnimationTimingDef,
WebAnimationTimingDirection,
WebAnimationTimingFill,
Expand Down Expand Up @@ -690,12 +692,16 @@ export class MeasureScanner extends Scanner {
(spec.target || spec.selector) ?
this.resolveTargets_(spec) :
[null];
targets.forEach(target => {
targets.forEach((target, index) => {
this.target_ = target || prevTarget;
this.css_.withTarget(this.target_, () => {
this.vars_ = this.mergeVars_(spec, prevVars);
const subtargetSpec =
this.target_ ?
this.matchSubtargets_(this.target_, index, spec) :
spec;
this.vars_ = this.mergeVars_(subtargetSpec, prevVars);
this.css_.withVars(this.vars_, () => {
this.timing_ = this.mergeTiming_(spec, prevTiming);
this.timing_ = this.mergeTiming_(subtargetSpec, prevTiming);
callback();
});
});
Expand Down Expand Up @@ -739,6 +745,59 @@ export class MeasureScanner extends Scanner {
return targets;
}

/**
* @param {!Element} target
* @param {number} index
* @param {!WebAnimationSelectorDef} spec
* @return {!WebAnimationSelectorDef}
*/
matchSubtargets_(target, index, spec) {
if (!spec.subtargets || spec.subtargets.length == 0) {
return spec;
}
const result = map(spec);
spec.subtargets.forEach(subtargetSpec => {
const matcher = this.getMatcher_(subtargetSpec);
if (matcher(target, index)) {
Object.assign(result, subtargetSpec);
}
});
return result;
}

/**
* @param {!WebAnimationSubtargetDef} spec
* @return {function(!Element, number):boolean}
*/
getMatcher_(spec) {
if (spec.matcher) {
return spec.matcher;
}
user().assert(
(spec.index !== undefined || spec.selector !== undefined) &&
(spec.index === undefined || spec.selector === undefined),
'Only one "index" or "selector" must be specified');

let matcher;
if (spec.index !== undefined) {
// Match by index, e.g. `index: 0`.
const specIndex = Number(spec.index);
matcher = (target, index) => index === specIndex;
} else {
// Match by selector, e.g. `:nth-child(2n+1)`.
const specSelector = /** @type {string} */ (spec.selector);
matcher = target => {
try {
return matches(target, specSelector);
} catch (e) {
throw user().createError(
`Bad subtarget selector: "${specSelector}"`, e);
}
};
}
return spec.matcher = matcher;
}

/**
* Merges vars by defaulting values from the previous vars.
* @param {!Object<string, *>} newVars
Expand Down
30 changes: 30 additions & 0 deletions extensions/amp-animation/amp-animation.md
Expand Up @@ -88,6 +88,7 @@ and is comprised of:
"media": "(min-width:300px)",
// Variables
// Timing properties
// Subtargets
...
"keyframes": []
}
Expand Down Expand Up @@ -214,6 +215,35 @@ An example of timing properties in JSON:

Animation components inherit timing properties specified for the top-level animation.


### Subtargets

Everywhere where `selector` can be specified, it's possible to also specify `subtargets: []`. Subtargets can override timing properties or variables defined in the animation for specific subtargets indicated via either an index or a CSS selector.

For instance:
```text
{
"selector": ".target",
"delay": 100,
"--y": "100px",
"subtargets": [
{
"index": 0,
"delay": 200,
},
{
"selector": ":nth-child(2n+1)",
"--y": "200px"
}
]
}
```

In this example, by default all targets matched by the ".target" have delay of 100ms and "--y" of 100px. However, the first target (`index: 0`) is overriden to have delay of 200ms; and odd targets are overriden to have "--y" of 200px.

Notice, that multiple subtargets can match one target element.


### 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 or as a string refering to the `@keyframes` name in the CSS.
Expand Down

0 comments on commit 4e8d6e4

Please sign in to comment.