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

Add technique 4 #5

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,28 @@ var translateY3d = function(elm, value) {
};
```

### Technique 4: "The Web has moved forward"

Since the last version, the web changed a lot. A new CSS property has been given birth, its name is `contain`. It basically tells the browser we do not want to affect other elements, when we modify the hero.

Also, when inspecting the timeline, we could notice all other techniques move the element even if it's outside the viewport. Does it make sense?

![Chrome Dev Timeline](http://image.prntscr.com/image/e3fcb9af4adb41528d19800a70635697.png)

We can clearly see, that the browser has to calculate (and paint, but the browser must always paint and composite on scoll) - unnecessarily!


To mitigate this, we add a new variable to `updatePosition`. The drawback: `clientTop` and `clientHeight` cause [reflows](https://gist.github.com/paulirish/5d52fb081b3570c81e3a).
But we set the height only once and `pageYOffset` sadly causes a reflow every frame anyway.
```javascript
if (!elemY) elemY = bgElm.clientTop + bgElm.clientHeight; // causes reflow only once
```

In addition, `background-size: cover` is a culprit. We want it for aesthetic purposes, but resizing huge images is not a cheap task. [This](https://www.fourkitchens.com/blog/article/fix-scrolling-performance-css-will-change-property) post explains how to fix it. TL;DR: We promote the element, so it gets its own GPU accelerated layer. At the same time, we tell the browser that we will transform it, so the engine is prepared.

Last but not least, we take a look at z-index. We don't want to accidentally create new layers, as this makes text blurry and higher VRAM usage. See [here](http://output.jsbin.com/efirip/5/quiet).
This means we apply `z-index: 1` to the hero element and `z-index: 2` to the content above it. If you set `z-index` elsewhere on your page, adjust the `z-index` of the parallax element to be higher than your always visible body content.

#### The Results: We Have a Winner!

The performance optimization can be seen very clearly by [looking at the demo](http://dev.form5.is/parallax/asparagus.html) but we'll also need objective measures to see whether `requestAnimationFrame` combined with `translate3d` is the silver bullet for doing parallax animation as we hope.
Expand Down
54 changes: 54 additions & 0 deletions demo4/asparagus.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
body {
margin: 0;
padding: 0;
font-family: -apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen-Sans,
Ubuntu,
Cantarell,
"Helvetica Neue",
sans-serif;
}

.hero {
position: relative;
height: 750px;

overflow: hidden;
contain: strict;
}

.hero-bg {
position: absolute;
width: 100%;
height: 750px;

will-change: transform; /* Promote the layer; older browsers probably need translateZ(0) */
z-index: 1; /* You may need to change this; see readme*/

background-image: url('[email protected]');
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;

contain: strict;
}

.hero-content {
position: absolute;
right: 0;
left: 0;
top: 15em;
padding: 1em;
margin: auto;
width: 300px;
text-align: center;
background: rgba(0,0,0,0.25);
color: white;

contain: content;

z-index: 2; /* so it lays above the hero bg */
}
58 changes: 58 additions & 0 deletions demo4/asparagus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">

<title>Asparagus demo</title>
<link rel="stylesheet" href="asparagus.css">
</head>
<body>
<div class="hero">
<div class="hero-bg" id="hero-bg"></div>

<div class="hero-content">
<h1>rAF + translate3d</h1>
<p>Written in native Javascript using a single ticking requestAnimationFrame() method and translate3d to ensure GPU acceleration.</p>
</div>

</div>

<h1>Demo lorem</h1>

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sagittis accumsan nunc, non interdum est pellentesque ut. Duis semper arcu vel eros congue lacinia non vitae sapien. Morbi porttitor est et est facilisis hendrerit. Phasellus accumsan nibh sit amet aliquam tincidunt. Vivamus dapibus neque eget orci sagittis fringilla. Phasellus dignissim elementum urna id porttitor. Nullam ultricies euismod turpis eget semper. Curabitur tincidunt porta urna a ultrices. Donec eu bibendum nisl. Vivamus sapien tellus, accumsan vitae enim eget, condimentum commodo nisl. Curabitur feugiat tristique lectus, sed sagittis enim scelerisque sit amet. Quisque luctus tellus arcu, eget venenatis mauris fermentum non. Duis eget magna at dolor laoreet vehicula nec a erat. Nullam pellentesque viverra elementum. Cras ac dictum arcu. Fusce ac aliquet tortor.</p>

<p>Integer vehicula quam non egestas pulvinar. Curabitur euismod est vitae tincidunt adipiscing. Curabitur molestie tincidunt mauris, et bibendum urna ultricies non. Aliquam ultrices, purus vitae pellentesque tincidunt, nisi orci dapibus dolor, vitae laoreet massa lectus bibendum nibh. Sed placerat ac nisl sed viverra. Etiam congue purus in aliquet varius. In hac habitasse platea dictumst. Donec eget enim mollis, porttitor nisi suscipit, condimentum nisi. Pellentesque eget tellus ut felis convallis fermentum. Donec et leo sed quam dignissim semper. Phasellus et tellus et massa mattis tempus et vitae est. Nulla quis sollicitudin orci, ac vestibulum lectus. Aliquam dignissim eu ipsum nec fringilla. Cras eget sapien nec nisi sollicitudin aliquam.</p>

<h2>Ipsum awesome</h2>

<div class="hero">
<div class="hero-bg" id="hero-bg2"></div>

<div class="hero-content">
<h1>rAF + translate3d</h1>
<p>Written in native Javascript using a single ticking requestAnimationFrame() method and translate3d to ensure GPU acceleration.</p>
</div>

</div>

<p>Maecenas auctor urna ut ante pulvinar lacinia ac sed leo. Aenean vehicula quis odio sit amet hendrerit. Curabitur fringilla augue eu tincidunt pellentesque. Integer eget nibh sit amet metus ullamcorper consequat. Quisque in pretium elit, vel ornare urna. Cras eu velit tortor. Suspendisse ut sollicitudin lectus. Morbi laoreet metus ut mauris placerat laoreet. Morbi nec eleifend dui.</p>

<p>Aenean nec eros velit. Praesent hendrerit ligula eu purus rhoncus, vitae molestie elit porta. Aliquam nibh turpis, faucibus vitae mollis sit amet, auctor ut augue. Sed eu ultrices velit, a convallis dui. Ut fringilla nibh tortor. Aliquam erat volutpat. Etiam dignissim ullamcorper mollis. Etiam ultrices nunc eget risus tempus elementum. Praesent quis enim vestibulum, fringilla arcu eu, ornare massa. In luctus, dui non interdum semper, eros est hendrerit diam, sed fringilla massa arcu eget quam. Nunc pretium eros in malesuada pretium. Duis id interdum mi. Cras imperdiet, lectus ut ultrices imperdiet, nisl tellus lacinia sapien, ac hendrerit erat lorem a nisl. Donec faucibus orci a neque suscipit fermentum.</p>

<p>Nunc ornare feugiat nisl, eu lacinia nulla malesuada et. Maecenas porta orci nec ipsum faucibus aliquam. Suspendisse potenti. In iaculis eu magna a commodo. Curabitur leo nisl, viverra a justo sed, tincidunt facilisis mi. Aenean neque mi, condimentum vel nisl eu, faucibus dapibus dui. Nulla quis dictum metus, quis ultricies neque. Suspendisse egestas tristique libero, at dapibus ante. Sed eu justo a felis rutrum sollicitudin. Maecenas non quam eget odio iaculis sodales rutrum ut justo. Nulla mi dui, lacinia nec felis nec, convallis gravida erat. Etiam hendrerit viverra quam eget feugiat. In a iaculis lacus. Pellentesque gravida lorem eu congue porttitor. Donec ornare fermentum pulvinar.</p>

<h2>Dolor cool</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sagittis accumsan nunc, non interdum est pellentesque ut. Duis semper arcu vel eros congue lacinia non vitae sapien. Morbi porttitor est et est facilisis hendrerit. Phasellus accumsan nibh sit amet aliquam tincidunt. Vivamus dapibus neque eget orci sagittis fringilla. Phasellus dignissim elementum urna id porttitor. Nullam ultricies euismod turpis eget semper. Curabitur tincidunt porta urna a ultrices. Donec eu bibendum nisl. Vivamus sapien tellus, accumsan vitae enim eget, condimentum commodo nisl. Curabitur feugiat tristique lectus, sed sagittis enim scelerisque sit amet. Quisque luctus tellus arcu, eget venenatis mauris fermentum non. Duis eget magna at dolor laoreet vehicula nec a erat. Nullam pellentesque viverra elementum. Cras ac dictum arcu. Fusce ac aliquet tortor.</p>

<p>Aenean nec eros velit. Praesent hendrerit ligula eu purus rhoncus, vitae molestie elit porta. Aliquam nibh turpis, faucibus vitae mollis sit amet, auctor ut augue. Sed eu ultrices velit, a convallis dui. Ut fringilla nibh tortor. Aliquam erat volutpat. Etiam dignissim ullamcorper mollis. Etiam ultrices nunc eget risus tempus elementum. Praesent quis enim vestibulum, fringilla arcu eu, ornare massa. In luctus, dui non interdum semper, eros est hendrerit diam, sed fringilla massa arcu eget quam. Nunc pretium eros in malesuada pretium. Duis id interdum mi. Cras imperdiet, lectus ut ultrices imperdiet, nisl tellus lacinia sapien, ac hendrerit erat lorem a nisl. Donec faucibus orci a neque suscipit fermentum.</p>

<script src="asparagus.js"></script>
<script>
new Parallax()
new Parallax('hero-bg2')
</script>
</body>
</html>
152 changes: 152 additions & 0 deletions demo4/asparagus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* @license Asparagus v2
* (c) 2016 Form5 http://form5.is + Jacob "kurtextrem" Groß
* License: MIT
*
* Please include a polyfill for requestAnimationFrame if you support older browsers.
*/
(function (window) {
'use strict'

function Plugin(settings, speedDivider) {
var el = typeof settings === 'string' ? settings : 'hero-bg',
speed = speedDivider ? +speedDivider : 1.5

if (~el.indexOf('.')) {
return console.error('Asparagus needs an ID or single DOM node as bgElem.', el) // we don't throw, as this would break other JS in a bundle
}

/**
* Options
*
* bgElem {Node} The parallax element
* speedDivider {int} Speed of the animation
* margin {int} Extends the top boundary of the bgElem to earlier/later start the animation
*/
var options = this.options = {
bgElem: (settings && settings.bgElem) || document.getElementById(el.replace('#', '')), // if first arg is a string, we take it as elem
speedDivider: (settings && settings.speedDivider) || speed,
margin: (settings && settings.margin) || 30,

_elemBounds: {
top: null,
bottom: 0,
right: 0,
left: 0
}
}

if (!(options.bgElem instanceof Node)) {
return console.error('bgElem is not an instance of Node.', options) // we don't throw, as this would break other JS in a bundle
}

this.updateBounds() // causes reflow, but only once

/** Add this instance to the listeners */
listeners.push(function _tick() {
updatePosition(options)
})
}

/**
* If the element has moved, call this function to update the boundaries.
*/
Plugin.prototype.updateBounds = function updateBounds() {
var parent = this.options.bgElem.parentNode
this.options._elemBounds.top = parent.offsetTop
this.options._elemBounds.bottom = this.options._elemBounds.top + parent.offsetHeight
this.options._elemBounds.left = parent.offsetLeft
this.options._elemBounds.right = this.options._elemBounds.left + parent.offsetWidth

if (this.options._elemBounds.top < this.options.margin)
this.options.margin = 0
}

/**
* Updates the background position.
*
* @see options
*/
function updatePosition(options) {
var bounds = options._elemBounds,
translateValue = (lastScrollY - bounds.top + options.margin) / options.speedDivider

// Scenarios where we don't want parallax:
// elem not in viewport, because:
// scrollpos below 0
// it's below
// it's above
// it's left
// it's right
if ((lastScrollY + options.margin) < bounds.top || lastScrollY > bounds.bottom || lastScrollX < bounds.left || lastScrollX > bounds.right || translateValue < 0)
return // maybe remove will-change here?

translateY(options.bgElem, translateValue) // @todo: If it is a horizontal scroll we do nothing currently
}

/**
* Translates an element on the Y axis using translate3d to ensure GPU rendering.
*/
function translateY(elem, value) {
var translate = 'translate3d(0,' + value + 'px,0)'
elem.style.transform = translate
elem.style['-webkit-transform'] = translate
elem.style['-moz-transform'] = translate
elem.style['-ms-transform'] = translate
elem.style['-o-transform'] = translate
}

/** Holds last scroll position. (window.scrollY, scrollX) */
var lastScrollY = 0, lastScrollX = 0
/** rAF tick id */
var tickId = 0

/**
* The scroll event listener calculates the last scroll positions and requests an AF.
* Only runs every 60 fps.
*/
function doScroll() {
if (tickId) return

lastScrollY = window.pageYOffset // causes reflow
lastScrollX = window.pageXOffset
tickId = window.requestAnimationFrame(callListeners)
}

/** Holds the listeners. */
var listeners = []

/**
* Calls all listeners.
*/
function callListeners() {
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
tickId = 0
}

// Initialize on domready
(function () {
var loaded = 0
function bootstrap () {
if (loaded) return
loaded = 1

window.addEventListener('scroll', doScroll, false)
}

if (!window.requestAnimationFrame) return console.error('Please include a polyfill for requestAnimationFrame.')

if (window.document.readyState === 'complete') {
(window.requestIdleCallback && window.requestIdleCallback(bootstrap)) || window.requestAnimationFrame(bootstrap) || setTimeout(bootstrap, 0)
} else {
window.addEventListener('load', bootstrap, false)
}
}());

/**
* Usage: new Parallax(elem, speedDivider) || new Parallax({ bgElem: 'id', speedDivider: 2.1, margin: 50 })
*/
window.Asparagus = window.Parallax = Plugin
}(window));
Binary file added demo4/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.