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: width, height and rand functions #9539

Merged
merged 5 commits into from May 26, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/animations.amp.html
Expand Up @@ -63,7 +63,7 @@
"selector": "#image2",
"easing": "cubic-bezier(0,0,.21,1)",
"keyframes": [
{"transform": "translateY(var(--start-y))", "opacity": "var(--start-o)"},
{"transform": "translateX(calc(width('#image1') / 2)) translateY(var(--start-y))", "opacity": "var(--start-o)"},
{"transform": "translateY(calc(10% + 10px))", "opacity": "calc(var(--start-o) * 7)"}
]
}
Expand Down
30 changes: 2 additions & 28 deletions extensions/amp-animation/0.1/amp-animation.js
Expand Up @@ -27,7 +27,6 @@ import {isExperimentOn} from '../../../src/experiments';
import {installWebAnimations} from 'web-animations-js/web-animations.install';
import {listen} from '../../../src/event-helper';
import {setStyles} from '../../../src/style';
import {toArray} from '../../../src/types';
import {tryParseJson} from '../../../src/json';
import {user, dev} from '../../../src/log';
import {viewerForDoc} from '../../../src/services';
Expand Down Expand Up @@ -251,10 +250,8 @@ export class AmpAnimation extends AMP.BaseElement {
const hostWin = this.embed_ ? this.embed_.win : this.win;
const baseUrl = this.embed_ ? this.embed_.getUrl() : ampdoc.getUrl();
return readyPromise.then(() => {
const measurer = new MeasureScanner(hostWin, baseUrl, {
resolveTarget: this.resolveTarget_.bind(this),
queryTargets: this.queryTargets_.bind(this),
}, /* validate */ true);
const measurer = new MeasureScanner(
hostWin, this.getRootNode_(), baseUrl, /* validate */ true);
return vsync.measurePromise(() => {
measurer.scan(configJson);
return measurer.createRunner(this.element.getResources());
Expand All @@ -272,29 +269,6 @@ export class AmpAnimation extends AMP.BaseElement {
this.getAmpDoc().getRootNode();
}

/**
* @param {string} id
* @return {?Element}
* @private
* TODO(dvoytenko, #9129): cleanup deprecated string targets.
*/
resolveTarget_(id) {
return this.getRootNode_().getElementById(id);
}

/**
* @param {string} selector
* @return {!Array<!Element>}
* @private
*/
queryTargets_(selector) {
try {
return toArray(this.getRootNode_().querySelectorAll(selector));
} catch (e) {
throw user().createError('Invalid selector: ', selector);
}
}

/** @private */
pause_() {
if (this.runner_) {
Expand Down
142 changes: 137 additions & 5 deletions extensions/amp-animation/0.1/css-expr-ast.js
Expand Up @@ -17,7 +17,7 @@
const FINAL_URL_RE = /^(data|https)\:/i;
const DEG_TO_RAD = 2 * Math.PI / 360;
const GRAD_TO_RAD = Math.PI / 200;
const VAR_CSS_RE = /(calc|var|url)\(/i;
const VAR_CSS_RE = /(calc|var|url|rand|width|height)\(/i;
const INFINITY_RE = /^(infinity|infinite)$/i;


Expand Down Expand Up @@ -78,6 +78,14 @@ export class CssContext {
*/
getCurrentElementSize() {}

/**
* Returns the specified element's size.
* @param {string} unusedSelector
* @param {?string} unusedSelectionMethod
* @return {!{width: number, height: number}}
*/
getElementSize(unusedSelector, unusedSelectionMethod) {}

/**
* Returns the dimension: "w" for width or "h" for height.
* @return {?string}
Expand Down Expand Up @@ -418,10 +426,7 @@ export class CssLengthNode extends CssNumericNode {
calcPercent(percent, context) {
const dim = context.getDimension();
const size = context.getCurrentElementSize();
const side =
dim == 'w' ? size.width :
dim == 'h' ? size.height :
0;
const side = getDimSide(dim, size);
return new CssLengthNode(side * percent / 100, 'px');
}
}
Expand Down Expand Up @@ -595,6 +600,115 @@ export class CssTranslateNode extends CssFuncNode {
}


/**
* AMP-specific `width()` and `height()` functions.
*/
export class CssDimSizeNode extends CssNode {
/**
* @param {string} dim
* @param {?string=} opt_selector
* @param {?string=} opt_selectionMethod Either `undefined` or "closest".
*/
constructor(dim, opt_selector, opt_selectionMethod) {
super();
/** @const @private */
this.dim_ = dim;
/** @const @private */
this.selector_ = opt_selector || null;
/** @const @private */
this.selectionMethod_ = opt_selectionMethod || null;
}

/** @override */
css() {
throw noCss();
}

/** @override */
isConst() {
return false;
}

/** @override */
calc(context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we handle size changes? looks like now we need to make sure amp-animation reevaluates the expressions onChange

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All parts of animations (both CSS3 and Web Animations) explicitly only measure things once before starting and do not change in the course of the animation. That's by design. E.g. if you have a keyframe with transform: translateX(10%) - it's resolved once, e.g. to 10px and doesn't change even if 10% changes. However, <amp-animation> itself listens to resize events and restarts the animation. So it's sort of done.

const size =
this.selector_ ?
context.getElementSize(this.selector_, this.selectionMethod_) :
context.getCurrentElementSize();
return new CssLengthNode(getDimSide(this.dim_, size), 'px');
}
}


/**
* AMP-specific `rand()` function. Has two forms:
* - `rand()` - returns a random number value between 0 and 1.
* - `rand(left, right)` - returns a random value between `left` and
* `right`. The `left` and `right` are any number-based values in this
* case, such as a length (`10px`), a time (`1s`), an angle (`1rad`), etc.
* The returned value is the same type - a length, time angle, etc. Thus,
* `rand(1s, 5s)` may return a value of `rand(2.1s)`.
*/
export class CssRandNode extends CssNode {
/**
* @param {?CssNode=} left
* @param {?CssNode=} right
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not fully clear what left and right mean. It looks like it's the threshold for the rand() result but it's not clear how it goes from node -> threshold. Documenting these would be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*/
constructor(left = null, right = null) {
super();
/** @const @private */
this.left_ = left;
/** @const @private */
this.right_ = right;
}

/** @override */
css() {
throw noCss();
}

/** @override */
isConst() {
return false;
}

/** @override */
calc(context) {
// No arguments: return a random node between 0 and 1.
if (this.left_ == null || this.right_ == null) {
return new CssNumberNode(Math.random());
}

// Arguments: do a min/max random math.
let left = this.left_.resolve(context);
let right = this.right_.resolve(context);
if (left == null || right == null) {
return null;
}
if (!(left instanceof CssNumericNode) ||
!(right instanceof CssNumericNode)) {
throw new Error('left and right must be both numerical');
}
if (left.type_ != right.type_) {
throw new Error('left and right must be the same type');
}

// Units are the same, the math is simple: numerals are summed. Otherwise,
// the units neeed to be normalized first.
if (left.units_ != right.units_) {
left = left.norm(context);
right = right.norm(context);
}
const min = Math.min(left.num_, right.num_);
const max = Math.max(left.num_, right.num_);
const rand = Math.random();
// Formula: rand(A, B) = A * (1 - R) + B * R
const num = min * (1 - rand) + max * rand;
return left.createSameUnits(num);
}
}


/**
* A CSS `var()` expression: `var(--name)`, `var(--name, 100px)`, etc.
* See https://www.w3.org/TR/css-variables/.
Expand Down Expand Up @@ -823,3 +937,21 @@ export class CssCalcProductNode extends CssNode {
function unknownUnits(units) {
return new Error('unknown units: ' + units);
}


/**
* @return {!Error}
*/
function noCss() {
return new Error('no css');
}


/**
* @param {?string} dim
* @param {!{width: number, height: number}} size
* @return {number}
*/
function getDimSide(dim, size) {
return (dim == 'w' ? size.width : dim == 'h' ? size.height : 0);
}
44 changes: 44 additions & 0 deletions extensions/amp-animation/0.1/css-expr-impl.jison
Expand Up @@ -74,6 +74,10 @@ ident \-?[a-zA-Z_][\-a-zA-Z0-9_]*
{T}{R}{A}{N}{S}{L}{A}{T}{E}{Y}\( return 'TRANSLATE_Y_START'
{T}{R}{A}{N}{S}{L}{A}{T}{E}{Z}\( return 'TRANSLATE_Z_START'
{T}{R}{A}{N}{S}{L}{A}{T}{E}3{D}\( return 'TRANSLATE_3D_START'
{R}{A}{N}{D}\( return 'RAND_START'
{W}{I}{D}{T}{H}\( return 'WIDTH_START'
{H}{E}{I}{G}{H}{T}\( return 'HEIGHT_START'
{C}{L}{O}{S}{E}{S}{T}\( return 'CLOSEST_START'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there code that handles this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - in the ast class. And there are rules in this file on parsing.

{ident}\( return 'FUNCTION_START'
{ident} return 'IDENT'
\-\-{ident} return 'VAR_NAME';
Expand Down Expand Up @@ -243,6 +247,10 @@ function:
{$$ = $1;}
| translate_function
{$$ = $1;}
| dim_function
{$$ = $1;}
| rand_function
{$$ = $1;}
| any_function
{$$ = $1;}
;
Expand Down Expand Up @@ -307,6 +315,42 @@ translate_function:
;


/**
* AMP-specific `width()` and `height()` functions:
* - `width(".selector")`
* - `height(".selector")`
* - `width(closest(".selector"))`
* - `height(closest(".selector"))`
*/
dim_function:
WIDTH_START ')'
{$$ = new ast.CssDimSizeNode('w');}
| HEIGHT_START ')'
{$$ = new ast.CssDimSizeNode('h');}
| WIDTH_START STRING ')'
{$$ = new ast.CssDimSizeNode('w', $2.slice(1, -1));}
| HEIGHT_START STRING ')'
{$$ = new ast.CssDimSizeNode('h', $2.slice(1, -1));}
| WIDTH_START CLOSEST_START STRING ')' ')'
{$$ = new ast.CssDimSizeNode('w', $3.slice(1, -1), 'closest');}
| HEIGHT_START CLOSEST_START STRING ')' ')'
{$$ = new ast.CssDimSizeNode('h', $3.slice(1, -1), 'closest');}
;


/**
* AMP-specific `rand()` functions:
* - `rand()` - a random value between 0 and 1
* - `rand(min, max)` - a random value between min and max
*/
rand_function:
RAND_START ')'
{$$ = new ast.CssRandNode();}
| RAND_START literal_or_function ',' literal_or_function ')'
{$$ = new ast.CssRandNode($2, $4);}
;


/**
* A `var()` function: https://www.w3.org/TR/css-variables/
* Examples:
Expand Down