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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
|
||
|
@@ -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} | ||
|
@@ -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'); | ||
} | ||
} | ||
|
@@ -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) { | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not fully clear what There was a problem hiding this comment. Choose a reason for hiding this commentThe 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/. | ||
|
@@ -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); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Closest? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there code that handles this? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'; | ||
|
@@ -243,6 +247,10 @@ function: | |
{$$ = $1;} | ||
| translate_function | ||
{$$ = $1;} | ||
| dim_function | ||
{$$ = $1;} | ||
| rand_function | ||
{$$ = $1;} | ||
| any_function | ||
{$$ = $1;} | ||
; | ||
|
@@ -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: | ||
|
There was a problem hiding this comment.
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 expressionsonChange
There was a problem hiding this comment.
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. to10px
and doesn't change even if10%
changes. However,<amp-animation>
itself listens to resize events and restarts the animation. So it's sort of done.