Skip to content

Commit

Permalink
amp-fx-collection: parallax (ampproject#13073)
Browse files Browse the repository at this point in the history
  • Loading branch information
aghassemi authored and protonate committed Mar 15, 2018
1 parent 7f2a572 commit 9bdc2bc
Show file tree
Hide file tree
Showing 11 changed files with 563 additions and 52 deletions.
4 changes: 4 additions & 0 deletions build-system/dep-check-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ exports.rules = [
'src/service/position-observer/position-observer-impl.js',
'extensions/amp-position-observer/0.1/amp-position-observer.js->' +
'src/service/position-observer/position-observer-worker.js',
'extensions/amp-fx-collection/0.1/providers/parallax.js->' +
'src/service/position-observer/position-observer-impl.js',
'extensions/amp-fx-collection/0.1/providers/parallax.js->' +
'src/service/position-observer/position-observer-worker.js',
'extensions/amp-analytics/0.1/amp-analytics.js->' +
'src/service/cid-impl.js',
// TODO(calebcordry) remove this once experiment is launched
Expand Down
1 change: 1 addition & 0 deletions build-system/tasks/presubmit-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ const forbiddenTerms = {
whitelist: [
'src/service/position-observer/position-observer-impl.js',
'extensions/amp-position-observer/0.1/amp-position-observer.js',
'extensions/amp-fx-collection/0.1/providers/parallax.js',
'src/service/video-manager-impl.js',
],
},
Expand Down
130 changes: 78 additions & 52 deletions examples/article-parallax.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,107 @@
<title>AMP Article with parallax title</title>
<link rel="canonical" href="amps.html">
<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-fx-collection" src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js"></script>
<style amp-custom>
article, header h1 {
.spacer {
margin-top: 10px;
height: 70vh;
width: 100%;
background-image: -webkit-linear-gradient(#EEE 50%, white 50%);
background-image: linear-gradient(#EEE 50%, white 50%);
background-size: 100% 3em;
}

header {
position: relative;
}

h1 {
margin: 0px 10px;
}

header h1 {
position: absolute;
top: 25vh;
top: 20vh;
padding: 5px;
z-index: 1;
max-width: 70vw;
}

header h1 span {
header h1 span, .title {
background-color: black;
color: white;
line-height: 1.2em;
}

header amp-img img {
amp-img img {
object-fit: cover;
}

/*
* Vertically center the tite within the image.
*/
.vertically-center {
transform: translateY(-50%);
.overlay-top {
position: absolute;
top: 0;
}

.overlay-container {
position: relative;
overflow: hidden;
}

main {
font-family: Helvetica, sans-serif;
padding: 10px;
max-width: 412px;
margin: auto;
}

a, a:visited, a:active {
color: #2196F3;
text-decoration: none;
display: block;
padding: 5px;
}

.overflow-window {
overflow: hidden;
}

.overflow-window amp-img {
margin-bottom: -80%;
}
</style>
<script async custom-element="amp-fx-parallax" src="https://cdn.ampproject.org/v0/amp-fx-parallax-0.1.js"></script>
<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>
</head>
<body>
<header>
<h1 amp-fx-parallax="1.7">
<div class="vertically-center">
<span>Lorem Ipsum Dolor Sit Amet Consectetur Adipiscing<span>
</div>
</h1>
<amp-img height="50vh" layout="fixed-height" src="img/[email protected]"></amp-img>
</header>
<main>
<header>
<h1 amp-fx="parallax" data-parallax-factor="1.5">
<span class="title">Lorem Ipsum Dolor Sit Lorem Ipsum<span>
</h1>
<amp-img height="50vh" layout="fixed-height" src="https://picsum.photos/1600/900?image=1069"></amp-img>
</header>

<article>
<p>
<a href="#scrollToMe" on="tap:scrollToMe.scrollTo(duration=3000)">Tap to auto scroll slowly</a>
<a href="#scrollToMe">Tap to auto scroll fast</a>
</p>

<article>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper turpis vel commodo scelerisque. Phasellus
luctus nunc ut elit cursus, et imperdiet diam vehicula. Duis et nisi sed urna blandit bibendum et sit amet erat. Suspendisse
potenti. Curabitur consequat volutpat arcu nec elementum. Etiam a turpis ac libero varius condimentum. Maecenas sollicitudin
felis aliquam tortor vulputate, ac posuere velit semper.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper turpis vel commodo scelerisque. Phasellus
luctus nunc ut elit cursus, et imperdiet diam vehicula. Duis et nisi sed urna blandit bibendum et sit amet erat. Suspendisse
potenti. Curabitur consequat volutpat arcu nec elementum. Etiam a turpis ac libero varius condimentum. Maecenas sollicitudin
felis aliquam tortor vulputate, ac posuere velit semper.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper turpis vel commodo scelerisque. Phasellus
luctus nunc ut elit cursus, et imperdiet diam vehicula. Duis et nisi sed urna blandit bibendum et sit amet erat. Suspendisse
potenti. Curabitur consequat volutpat arcu nec elementum. Etiam a turpis ac libero varius condimentum. Maecenas sollicitudin
felis aliquam tortor vulputate, ac posuere velit semper.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper turpis vel commodo scelerisque. Phasellus
luctus nunc ut elit cursus, et imperdiet diam vehicula. Duis et nisi sed urna blandit bibendum et sit amet erat. Suspendisse
potenti. Curabitur consequat volutpat arcu nec elementum. Etiam a turpis ac libero varius condimentum. Maecenas sollicitudin
felis aliquam tortor vulputate, ac posuere velit semper.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper turpis vel commodo scelerisque. Phasellus
luctus nunc ut elit cursus, et imperdiet diam vehicula. Duis et nisi sed urna blandit bibendum et sit amet erat. Suspendisse
potenti. Curabitur consequat volutpat arcu nec elementum. Etiam a turpis ac libero varius condimentum. Maecenas sollicitudin
felis aliquam tortor vulputate, ac posuere velit semper.
</p>
</article>
<div class="spacer"></div>
<div class="overlay-container">
<amp-img layout=responsive width=1600 height=900 src="https://picsum.photos/1600/900?image=981"></amp-img>
<h1 class="overlay-top" amp-fx="parallax" data-parallax-factor="0.8">
<span class="title">Slow Lorem Ipsum Dolor Sit Lorem Ipsum<span>
</h1>
</div>
<div class="spacer"></div>
<div class="overflow-window" id="scrollToMe">
<amp-img amp-fx="parallax" data-parallax-factor="1.2" layout=responsive width=900 height=1600 src="https://picsum.photos/900/1600?image=736"></amp-img>
</div>
<div class="spacer"></div>
<div class="spacer"></div>
</article>
</main>
</body>

</html>
179 changes: 179 additions & 0 deletions extensions/amp-fx-collection/0.1/amp-fx-collection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {AmpEvents} from '../../../src/amp-events';
import {listen} from '../../../src/event-helper';
import {iterateCursor} from '../../../src/dom';
import {dev, user, rethrowAsync} from '../../../src/log';
import {map} from '../../../src/utils/object';
import {Services} from '../../../src/services';
import {ParallaxProvider} from './providers/parallax';

const TAG = 'amp-fx-collection';

/**
* Enum for list of supported visual effects.
* @enum {string}
*/
const FxType = {
PARALLAX: 'parallax',
};

/**
* Map of fx type to fx provider class.
* @type {Object<FxType, function(new:FxProviderInterface, !../../../src/service/ampdoc-impl.AmpDoc)>}
*/
const fxProviders = map({
[FxType.PARALLAX]: ParallaxProvider,
});

/**
* Bootstraps elements that have `amp-fx=<fx1 fx2>` attribute and installs
* the specified effects on them.
*/
class AmpFxCollection {

/**
* @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc
*/
constructor(ampdoc) {

/** @private @const {!../../../src/service/ampdoc-impl.AmpDoc} */
this.ampdoc_ = ampdoc;

/** @private @const {!Document|!ShadowRoot} */
this.root_ = ampdoc.getRootNode();

/** @private @const {!Array<!Element>} */
this.seen_ = [];

/** @private @const {!../../../src/service/viewer-impl.Viewer} */
this.viewer_ = Services.viewerForDoc(ampdoc);

/** @private @const {!Object<FxType, FxProviderInterface>} */
this.fxProviderInstances_ = map();

Promise.all([
ampdoc.whenReady(),
this.viewer_.whenFirstVisible(),
]).then(() => {
// Scan when page becomes visible.
this.scan_();
// Rescan as DOM changes happen.
listen(this.root_, AmpEvents.DOM_UPDATE, this.scan_.bind(this));
});
}

/**
* Scans the root for fx-enabled elements and registers them with the
* fx provider.
*/
scan_() {
const fxElements = this.root_.querySelectorAll('[amp-fx]');
iterateCursor(fxElements, fxElement => {
if (this.seen_.includes(fxElement)) {
return;
}

// Don't break for all components if only a subset are misconfigured.
try {
this.register_(fxElement);
this.seen_.push(fxElement);
} catch (e) {
rethrowAsync(e);
}
});
}

/**
* Registers an fx-enabled element with its requested fx providers.
* @param {!Element} fxElement
*/
register_(fxElement) {
dev().assert(fxElement.hasAttribute('amp-fx'));
dev().assert(!this.seen_.includes(fxElement));
dev().assert(this.viewer_.isVisible());

const fxTypes = this.getFxTypes_(fxElement);

fxTypes.forEach(fxType => {
const fxProvider = this.getFxProvider_(fxType);
fxProvider.installOn(fxElement);
});
}

/**
* Returns the array of fx types this component has specified as a
* space-separated list in the value of `amp-fx` attribute.
* e.g. `amp-fx="parallax fade-in"
*
* @param {!Element} fxElement
* @returns {!Array<!FxType>}
*/
getFxTypes_(fxElement) {
dev().assert(fxElement.hasAttribute('amp-fx'));
const fxTypes = fxElement.getAttribute('amp-fx')
.trim()
.toLowerCase()
.split(/\s+/);

user().assert(fxTypes.length, 'No value provided for `amp-fx` attribute');

// Validate that we support the requested fx types.
fxTypes.forEach(fxType => {
user().assertEnumValue(FxType, fxType, 'amp-fx');
});

return fxTypes;
}

/**
* Given an fx type, instantiates the appropriate provider if needed and
* returns it.
* @param {FxType} fxType
*/
getFxProvider_(fxType) {
dev().assert(fxProviders[fxType],
`No provider for ${fxType} found, did you forget to register it?`);

if (!this.fxProviderInstances_[fxType]) {
this.fxProviderInstances_[fxType] = new fxProviders[fxType](this.ampdoc_);
}
return this.fxProviderInstances_[fxType];
}
}

/**
* Defines the expected interface all FxProviders need to implement.
* @interface
*/
export class FxProviderInterface {

/**
* @param {!../../../src/service/ampdoc-impl.AmpDoc} unusedAmpDoc
*/
constructor(unusedAmpDoc) {}

/**
*
* @param {!Element} unusedElement
*/
installOn(unusedElement) {}
};

AMP.extension(TAG, '0.1', AMP => {
AMP.registerServiceForDoc(TAG, AmpFxCollection);
});
Loading

0 comments on commit 9bdc2bc

Please sign in to comment.