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

Implement parallax effect extension #7794

Merged
merged 4 commits into from
Mar 1, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build-system/dep-check-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ exports.rules = [
'extensions/amp-youtube/0.1/amp-youtube.js->' +
'src/service/video-manager-impl.js',
'extensions/amp-a4a/0.1/amp-a4a.js->src/service/variable-source.js',
'extensions/amp-fx-parallax/0.1/amp-fx-parallax.js->' +
'src/service/parallax-impl.js',
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.

],
},
{
Expand Down
85 changes: 85 additions & 0 deletions examples/article-parallax.amp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<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">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<style amp-custom>
article, header h1 {
margin: 0px 10px;
}

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

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

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

/*
* Vertically center the tite within the image.
*/
.vertically-center {
transform: translateY(-50%);
}
</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/hero@1x.jpg"></amp-img>
</header>

<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>
</body>
</html>
24 changes: 24 additions & 0 deletions extensions/amp-fx-parallax/0.1/amp-fx-parallax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a article-parallax.amp.html to examples folder.
Maybe: https://gist.github.com/aghassemi/0fde9b1df0ad531dbdfc29588ca27c73 (paths need updating)

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.

* Copyright 2017 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 {ampdocServiceFor} from '../../../src/ampdoc';
import {installParallaxForDoc} from '../../../src/service/parallax-impl';
import {onDocumentReady} from '../../../src/document-ready';

const ampdoc = ampdocServiceFor(AMP.win).getAmpDoc();
onDocumentReady(ampdoc.win.document, () => {
installParallaxForDoc(ampdoc.getRootNode());
});
202 changes: 202 additions & 0 deletions extensions/amp-fx-parallax/0.1/test/test-amp-fx-parallax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/**
* Copyright 2017 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 {createIframePromise} from '../../../../testing/iframe';
import {installParallaxForDoc} from '../../../../src/service/parallax-impl';
import {parallaxForDoc} from '../../../../src/parallax';
import {toggleExperiment} from '../../../../src/experiments';
import {viewportForDoc} from '../../../../src/viewport';
import {vsyncFor} from '../../../../src/vsync';

describes.sandboxed('amp-fx-parallax', {}, () => {
const DEFAULT_FACTOR = 1.7;

function addTextChildren(iframe) {
return [iframe.doc.createTextNode('AMP: Accelerated Mobile Pages')];
}

function getAmpParallaxElement(opt_childrenCallback, opt_factor, opt_top) {
const factor = opt_factor || DEFAULT_FACTOR;
const top = opt_top || 0;
let viewport;
let parallaxElement;

return createIframePromise().then(iframe => {
const bodyResizer = iframe.doc.createElement('div');
bodyResizer.style.height = '4000px';
bodyResizer.style.width = '1px';
iframe.doc.body.appendChild(bodyResizer);

viewport = viewportForDoc(iframe.win.document);
viewport.resize_();

toggleExperiment(iframe.win, 'amp-fx-parallax', true);

parallaxElement = iframe.doc.createElement('div');
parallaxElement.setAttribute('amp-fx-parallax', factor);
if (opt_childrenCallback) {
const children = opt_childrenCallback(iframe, parallaxElement);
children.forEach(child => {
parallaxElement.appendChild(child);
});
}

const parent = iframe.doc.querySelector('#parent');
parent.appendChild(parallaxElement);
installParallaxForDoc(iframe.doc);

return new Promise(resolve => {
vsyncFor(iframe.win).mutate(() => {
resolve({
element: parallaxElement,
iframe,
viewport,
});
});
viewport.setScrollTop(top);
});
}).catch(error => {
return Promise.reject({error, parallaxElement, stack: error.stack});
});
}

it('should move when the user scrolls, if visible', () => {
const scroll = 10;
const expectedParallax = -1 * DEFAULT_FACTOR * scroll;

return getAmpParallaxElement(addTextChildren)
.then(({element, iframe, viewport}) => {
const parallaxService = parallaxForDoc(iframe.doc);
const top = element.getBoundingClientRect().top;
expect(top).to.equal(viewport.getScrollTop());

return new Promise(resolve => {
parallaxService.addScrollListener_(() => {
const top = element.getBoundingClientRect().top;
expect(top).to.equal(expectedParallax);
resolve();
});
viewport.setScrollTop(scroll);
});
});
});

it('should not move after it is outside of the viewport', () => {
const scroll = 100;
const expectedParallax = -1 * DEFAULT_FACTOR * scroll;

return getAmpParallaxElement(addTextChildren, DEFAULT_FACTOR)
.then(({element, iframe, viewport}) => {
const parallaxService = parallaxForDoc(iframe.doc);

return new Promise(resolve => {
parallaxService.addScrollListener_(() => {
const top = element.getBoundingClientRect().top;
expect(top).to.not.equal(expectedParallax);
resolve();
});
viewport.setScrollTop(scroll);
});
});
});

it('should move downward with a negative parallax factor', () => {
const scroll = 10;
const expectedParallax = -1 * DEFAULT_FACTOR * scroll;

return getAmpParallaxElement(addTextChildren, DEFAULT_FACTOR)
.then(({element, iframe, viewport}) => {
const parallaxService = parallaxForDoc(iframe.doc);
return new Promise(resolve => {
parallaxService.addScrollListener_(() => {
const top = element.getBoundingClientRect().top;
expect(top).to.equal(expectedParallax);
resolve();
});
viewport.setScrollTop(scroll);
});
});
});

it('should apply multiple scrolls as if they were one large scroll', () => {
const scroll = 10;
const factor = -1.7; // move downward so it stays in the viewport
const expectedParallax = -1 * factor * 2 * scroll;

return getAmpParallaxElement(addTextChildren, factor)
.then(({element, iframe, viewport}) => {
const parallaxService = parallaxForDoc(iframe.doc);
return new Promise(resolve => {
parallaxService.addScrollListener_(afterFirstScroll);
viewport.setScrollTop(scroll);

function afterFirstScroll() {
parallaxService.removeScrollListener_(afterFirstScroll);
parallaxService.addScrollListener_(afterSecondScroll);
viewport.setScrollTop(2 * scroll);
}

function afterSecondScroll() {
const top = element.getBoundingClientRect().top;
expect(top).to.equal(expectedParallax);
resolve();
}
});
});
});

it('should return to its original position when scrolling back', () => {
const factor = -1.7; // move downward so it stays in the viewport

return getAmpParallaxElement(addTextChildren, factor)
.then(({element, iframe, viewport}) => {
const parallaxService = parallaxForDoc(iframe.doc);
return new Promise(resolve => {
parallaxService.addScrollListener_(afterFirstScroll);
viewport.setScrollTop(10);

function afterFirstScroll() {
parallaxService.removeScrollListener_(afterFirstScroll);
parallaxService.addScrollListener_(afterSecondScroll);
viewport.setScrollTop(200);
}

function afterSecondScroll() {
parallaxService.removeScrollListener_(afterSecondScroll);
parallaxService.addScrollListener_(afterThirdScroll);
viewport.setScrollTop(0);
}

function afterThirdScroll() {
const top = element.getBoundingClientRect().top;
expect(top).to.equal(0);
resolve();
}
});
});
});

it('should render moved if the page loads partially scrolled', () => {
const scroll = 10;
const expectedParallax = -1 * DEFAULT_FACTOR * scroll;

return getAmpParallaxElement(addTextChildren, DEFAULT_FACTOR, scroll)
.then(({element}) => {
const top = element.getBoundingClientRect().top;
expect(top).to.equal(expectedParallax);
});
});
});
53 changes: 53 additions & 0 deletions extensions/amp-fx-parallax/amp-fx-parallax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!---
Copyright 2017 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.
-->

# <a name="amp-fx-parallax"></a> `amp-fx-parallax`

<table>
<tr>
<td class="col-fourty"><strong>Description</strong></td>
<td><code>amp-fx-parallax</code> enables a 3D-perspective effect on elements with the attribute.</td>
</tr>
<tr>
<td class="col-fourty" width="40%"><strong>Availability</strong></td>
<td>In development</td>
</tr>
<tr>
<td class="col-fourty"><strong>Required Script</strong></td>
<td><code>&lt;script async custom-element="amp-fx-parallax" src="https://cdn.ampproject.org/v0/amp-fx-parallax-0.1.js">&lt;/script></code></td>
</tr>
<tr>
<td class="col-fourty"><strong>Examples</strong></td>
<td>In development</td>
</tr>
</table>

## Behavior

The `amp-fx-parallax` attribute causes an element to move as if it is nearer or farther relative to the foreground of the page content. As the user scrolls the page, the element scrolls faster or slower depending on the value assigned to the attribute.

Example:

```html
<amp-img amp-fx-parallax="0.5" height="50vh" layout="fixed-height" src="hero.jpg">
</amp-img>
```

## Attributes

**amp-fx-parallax**

The factor to use when scrolling. A value greater than 1 scrolls the element upward when the user scrolls down the page. A value less than 1 scrolls the element downward when the user scrolls downward. A value of 1 behaves normally. A value of 0 effectively makes the element scroll fixed with the page.
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ declareExtension('amp-font', '0.1', false, 'NO_TYPE_CHECK');
declareExtension('amp-form', '0.1', true);
declareExtension('amp-fresh', '0.1', true);
declareExtension('amp-fx-flying-carpet', '0.1', true);
declareExtension('amp-fx-parallax', '0.1', false);
declareExtension('amp-gfycat', '0.1', false);
declareExtension('amp-hulu', '0.1', false);
declareExtension('amp-iframe', '0.1', false, 'NO_TYPE_CHECK');
Expand Down
Loading