Skip to content

Commit

Permalink
✨ <amp-story-animation> (#27597)
Browse files Browse the repository at this point in the history
Partial for #27420

Later work:

1. Scope `<amp-animation>` selectors to its `<amp-story-page>`.
2. Provide all [`getDims()` values](https://github.com/ampproject/amphtml/blob/b6313e372fdd1298928e2417dcc616b03288e051/extensions/amp-story/1.0/animation.js#L159) as `<amp-animation>` config.
3. Support sequencing with `animate-in-after`.
4. Decide on subanimations API: element by id, `subanimations`, or `<style keyframes>`?
  • Loading branch information
alanorozco committed Apr 16, 2020
1 parent 15258f6 commit fdae4db
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 139 deletions.
2 changes: 1 addition & 1 deletion build-system/test-configs/dep-check-config.js
Expand Up @@ -235,7 +235,7 @@ exports.rules = [

// AMP Story
'extensions/amp-story/0.1/animation.js->extensions/amp-animation/0.1/web-animation-types.js',
'extensions/amp-story/1.0/animation.js->extensions/amp-animation/0.1/web-animation-types.js',
'extensions/amp-story/1.0/animation-types.js->extensions/amp-animation/0.1/web-animation-types.js',
// Story ads
'extensions/amp-story-auto-ads/0.1/amp-story-auto-ads.js->extensions/amp-story/1.0/amp-story-store-service.js',
'extensions/amp-story-auto-ads/0.1/story-ad-page.js->extensions/amp-story/1.0/amp-story-store-service.js',
Expand Down
85 changes: 85 additions & 0 deletions examples/amp-story/amp-story-animation.html
@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html >
<head>
<meta charset="utf-8" />
<title>amp-animation inside amp-story</title>
<link rel="canonical" href="." />
<meta
name="viewport"
content="width=device-width,minimum-scale=1,initial-scale=1"
/>
<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>
<script
async
custom-element="amp-story"
src="https://cdn.ampproject.org/v0/amp-story-1.0.js"
></script>
<style amp-custom>
body {
font-family: sans-serif;
font-size: 18px;
}
</style>
</head>
<body>
<amp-story standalone>
<amp-story-page id="cover">
<amp-story-grid-layer template="vertical">
<strong class="animate">rotate with amp-story-animation</strong>
<div animate-in="fly-in-top" id="first">fly-in with animate-in</div>
<div animate-in="fly-in-top" animate-in-after="first">
fly-in with animate-in, after the previous element
</div>
</amp-story-grid-layer>
<amp-story-animation layout="nodisplay">
<!--
TODO(alanorozco, https://go.amp.dev/issue/27758):
Remove parent for scoping selector.
-->
<script type="application/json">
{
"selector": "#cover .animate",
"duration": "1s",
"keyframes": {"transform": "rotate(360deg)"}
}
</script>
</amp-story-animation>
</amp-story-page>

<amp-story-page id="page1">
<amp-story-grid-layer template="vertical">
<strong class="animate">
bounce forward with amp-story-animation
</strong>
<div animate-in="fly-in-top" id="first">fly-in with animate-in</div>
<div animate-in="fly-in-top" animate-in-after="first">
fly-in with animate-in, after the previous element
</div>
</amp-story-grid-layer>
<amp-story-animation layout="nodisplay">
<!--
TODO(alanorozco, https://go.amp.dev/issue/27758):
Remove parent for scoping selector.
-->
<script type="application/json">
{
"selector": "#page1 .animate",
"duration": "0.5s",
"keyframes": [
{"transform": "scale(1)"},
{"transform": "scale(1.5)"},
{"transform": "scale(1)"}
]
}
</script>
</amp-story-animation>
</amp-story-page>

<amp-story-bookend
src="bookendv1.json"
layout="nodisplay"
></amp-story-bookend>
</amp-story>
</body>
</html>
14 changes: 3 additions & 11 deletions extensions/amp-animation/0.1/amp-animation.js
Expand Up @@ -20,16 +20,15 @@ import {Pass} from '../../../src/pass';
import {Services} from '../../../src/services';
import {WebAnimationPlayState} from './web-animation-types';
import {WebAnimationService} from './web-animation-service';
import {childElementByTag} from '../../../src/dom';
import {clamp} from '../../../src/utils/math';
import {getChildJsonConfig} from '../../../src/json';
import {getDetail, listen} from '../../../src/event-helper';
import {getFriendlyIframeEmbedOptional} from '../../../src/iframe-helper';
import {getParentWindowFrameElement} from '../../../src/service';
import {installWebAnimationsIfNecessary} from './web-animations-polyfill';
import {isFiniteNumber} from '../../../src/types';
import {setInitialDisplay, setStyles, toggle} from '../../../src/style';
import {tryParseJson} from '../../../src/json';
import {user, userAssert} from '../../../src/log';
import {userAssert} from '../../../src/log';

const TAG = 'amp-animation';

Expand Down Expand Up @@ -91,14 +90,7 @@ export class AmpAnimation extends AMP.BaseElement {
);
}

// Parse config.
const scriptElement = userAssert(
childElementByTag(this.element, 'script'),
'"<script type=application/json>" must be present'
);
this.configJson_ = tryParseJson(scriptElement.textContent, (error) => {
throw user().createError('failed to parse animation script', error);
});
this.configJson_ = getChildJsonConfig(this.element);

if (this.triggerOnVisibility_) {
// Make the element minimally displayed to make sure that `layoutCallback`
Expand Down
24 changes: 4 additions & 20 deletions extensions/amp-animation/0.1/test/test-amp-animation.js
Expand Up @@ -82,28 +82,12 @@ describes.sandboxed('AmpAnimation', {}, () => {
expect(anim.configJson_).to.deep.equal({duration: 1001});
});

it('should fail without config', () => {
return createAnim({}, null).then(
() => {
throw new Error('must have failed');
},
(reason) => {
expect(reason.message).to.match(
/\"<script type=application\/json>\" must be present/
);
}
);
it('should fail without config', async () => {
await expect(createAnim({}, null)).to.be.eventually.be.rejected;
});

it('should fail with malformed config', () => {
return createAnim({}, 'broken').then(
() => {
throw new Error('must have failed');
},
(reason) => {
expect(reason.message).to.match(/failed to parse animation script/);
}
);
it('should fail with malformed config', async () => {
await expect(createAnim({}, 'borked')).to.be.eventually.be.rejected;
});

it('should default trigger to none', function* () {
Expand Down
14 changes: 7 additions & 7 deletions extensions/amp-story/1.0/animation-presets-utils.js
Expand Up @@ -19,7 +19,7 @@
* presets.
*/

import {KeyframesDef, StoryAnimationDimsDef} from './animation-types';
import {StoryAnimationDimsDef, WebKeyframesDef} from './animation-types';
import {rotate, scale, translate} from '../../../src/style';

/**
Expand All @@ -28,7 +28,7 @@ import {rotate, scale, translate} from '../../../src/style';
* @param {number} startY Starting point in the ordinate.
* @param {number} endX Ending point in the abscissa.
* @param {number} endY Ending point in the ordinate.
* @return {KeyframesDef} Keyframes that make up the animation.
* @return {WebKeyframesDef} Keyframes that make up the animation.
*/
export function translate2d(startX, startY, endX, endY) {
return [
Expand All @@ -44,7 +44,7 @@ export function translate2d(startX, startY, endX, endY) {
* @param {number} endX Ending point in the abscissa.
* @param {number} endY Ending point in the ordinate.
* @param {number} direction -1 for left, 1 for right
* @return {KeyframesDef} Keyframes that make up the animation.
* @return {WebKeyframesDef} Keyframes that make up the animation.
*/
export function rotateAndTranslate(startX, startY, endX, endY, direction) {
return [
Expand All @@ -59,7 +59,7 @@ export function rotateAndTranslate(startX, startY, endX, endY, direction) {
* @param {number} startY Starting point in the ordinate.
* @param {number} endX Ending point in the abscissa.
* @param {number} endY Ending point in the ordinate.
* @return {KeyframesDef} Keyframes that make up the animation.
* @return {WebKeyframesDef} Keyframes that make up the animation.
*/
export function whooshIn(startX, startY, endX, endY) {
return [
Expand Down Expand Up @@ -112,9 +112,9 @@ export function calculateTargetScalingFactor(dimensions) {

/**
* Scale the image in every frame by a certain factor.
* @param {KeyframesDef} keyframes Keyframes that will be used for the animation.
* @param {WebKeyframesDef} keyframes Keyframes that will be used for the animation.
* @param {number} scalingFactor Scaling factor at which target will be scaled.
* @return {KeyframesDef}
* @return {WebKeyframesDef}
*/
function enlargeKeyFrames(keyframes, scalingFactor) {
keyframes.forEach((frame) => {
Expand All @@ -131,7 +131,7 @@ function enlargeKeyFrames(keyframes, scalingFactor) {
* @param {number} endX Ending point in the abscissa.
* @param {number} endY Ending point in the ordinate.
* @param {number} scalingFactor Factor by which target will be scaled.
* @return {KeyframesDef} Keyframes that make up the animation.
* @return {WebKeyframesDef} Keyframes that make up the animation.
*/
export function scaleAndTranslate(startX, startY, endX, endY, scalingFactor) {
if (scalingFactor === 1) {
Expand Down
17 changes: 10 additions & 7 deletions extensions/amp-story/1.0/animation-types.js
Expand Up @@ -14,14 +14,16 @@
* limitations under the License.
*/

/** @typedef {!Array<!Object<string, *>>} */
export let KeyframesDef;
import {
WebAnimationDef,
WebAnimationPlayState,
WebKeyframesDef,
} from '../../amp-animation/0.1/web-animation-types';

/** @typedef {function(StoryAnimationDimsDef, Object<string, *>):!KeyframesDef} */
export let KeyframesFilterFnDef;
export {WebAnimationDef, WebAnimationPlayState, WebKeyframesDef};

/** @typedef {!KeyframesDef|!KeyframesFilterFnDef} */
export let KeyframesOrFilterFnDef;
/** @typedef {function(StoryAnimationDimsDef, Object<string, *>):!WebKeyframesDef} */
export let WebKeyframesCreateFnDef;

/**
* @typedef {{
Expand All @@ -39,7 +41,7 @@ export let StoryAnimationDimsDef;
* @typedef {{
* duration: number,
* easing: (string|undefined),
* keyframes: !KeyframesOrFilterFnDef,
* keyframes: (!WebKeyframesCreateFnDef|!WebKeyframesDef),
* }}
*/
export let StoryAnimationPresetDef;
Expand All @@ -51,6 +53,7 @@ export let StoryAnimationPresetDef;
* preset: !StoryAnimationPresetDef,
* duration: (number|undefined),
* delay: (number|undefined),
* easing: (string|undefined),
* }}
*/
export let StoryAnimationDef;

0 comments on commit fdae4db

Please sign in to comment.