Skip to content

Instantly share code, notes, and snippets.

@tbusser
Last active July 6, 2016 12:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbusser/f81e363b1f128f3546869ece0a8fca56 to your computer and use it in GitHub Desktop.
Save tbusser/f81e363b1f128f3546869ece0a8fca56 to your computer and use it in GitHub Desktop.
Parallax effect, see http://codepen.io/tbusser/pen/QEgmmq for an example
(function() {
var imgData = [],
windowHeight = window.innerHeight;
lastScrollPosition = 0;
function getOffset(element) {
var rect = element.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
}
function updatePosition(image, position) {
requestAnimationFrame(function updateImageStyle() {
image.style.transform = 'translateY(' + position + 'px)';
});
}
function updateParallaxes() {
var index = 0,
ubound = imgData.length;
for (; index < ubound; index++) {
data = imgData[index];
if (data.imageHeight == null || data.imageHeight === 0) {
data.imageHeight = data.imageElement.offsetHeight;
if (data.imageHeight === 0) {
continue;
}
data.parentOffset = getOffset(data.imageElement.parentNode).top;
data.imageOverflow = data.imageHeight - data.parentHeight;
data.travelCorrection = (data.parentOffset < windowHeight) ? (windowHeight - data.parentOffset) : 0;
}
// Calculate the distance from the top of the parallax element to the
// top of the viewport. When this is a positive number the image is
// below the top. A negative number means the top of the parallax
// element is above the viewport.
var frameTop = data.parentOffset - lastScrollPosition;
// If the frame top is more than the window height it means the top of
// parallax item is still beneath the viewport. If the absolute number of
// the frame top is more than the height of the parallax item it means the
// bottom of the parallax item is above the viewport.
// When either of these is true it means the parallax item is outside of
// the viewport and we don't have to bother updating the parallax effect
// for this item.
if (frameTop > windowHeight || -frameTop > data.parentHeight) {
// Continue with the next parallax item, this one can't be seen
// by the visitor
continue;
}
// 1: We need to calculate the window height, taking into account if the parallax
// item starts in the viewport. If the parallax item starts in the viewport we
// need to correct the height by substracting the parallax item's offset. Basically
// we're changing the number of pixels the element has to travel in order to disappear
// off screen.
// 2: First we need to calculate how many pixels of the image we can't
// show in its container. Next we need to know how many pixels the top
// of the parallax container can travel before the entire parallax
// container leaves the viewport. When we divide these two values we have
// the number of pixels the parallax image should move per pixel the document
// moves. This difference in speed is what creates the parallax effect.
// 3: Calculate how many pixels the image should be moved based on the actual
// travelled distance
var travelDistance = windowHeight - data.travelCorrection, // [1]
scrollRatio = (data.imageOverflow) / (travelDistance + data.parentHeight), // [2]
delta = 0 - (travelDistance - frameTop) * scrollRatio; // [3]
// Make sure the delta we have is within the bounds so we don't wind up moving
// the image too much. The chances this happens are minimal (I think) but if it
// happens you might wind up with some ugly whitespace in the UI
if (delta > 0) {
delta = 0
} else if (-delta > data.imageOverlow) {
delta = -data.imageOverflow;
}
// Update the position of the image in the next animation frame
updatePosition(data.imageElement, delta);
}
}
/* ==============
EVENT HANDLERS
============== */
function onScrollHandler(event) {
// Get the current scroll position. We do this here instead of in
// updateParallaxes as Chrome warns of a possible forced repaint
// when we access this property inside that method. scrollY is the
// standard property, pageYOffset is for older IE versions.
lastScrollPosition = window.scrollY || window.pageYOffset;
// Update the parallax effect in the next animation frame, we do this
// to maximize the performance.
requestAnimationFrame(updateParallaxes);
}
/* == EVENT HANDLERS == */
/* ============
INIT METHODS
============ */
function init() {
// 1: Get all the parallax images on the page
// 2: Initialize the loop control variables
var images = document.querySelectorAll('.parallax__image'), // [1]
index = 0, // [2]
ubound = images.length; // [2]
// When there are no parallax images, there is nothing left for us
// to do
if (ubound === 0) {
return;
}
// Loop over all the images
for (; index < ubound; index++) {
// Get an easy reference to the current image
var image = images[index],
parentElement = image.parentNode;
// Store all the information we need for the parallex effect
// in the array so we don't have to access the DOM for these
// values each time we have to apply the parallax effect.
// 1: We need the image element itself, this is the element we will
// apply the transform on to create the parallax effect.
// 2: We need to know the height of the image, we assume this won't
// change over time and is a value we can cache without a problem.
// 3: Calculate the number of pixels of the image that don't fit inside
// the parent container. These are the pixels we will make visible
// with the parallax effect.
// 4: We need to know the height of the parent element, the parallax
// container. We will make the assumption that its height doesn't change
// over time and is thus a safe value to cache.
// 5: The number of pixels the parent container is positioned from the top
// of the document. It is an expensive value to calculate which is why
// we're caching it. We assume the element is always on the same vertical
// position and thus a safe value to cache.
// 6: If the top of the parallax container puts it in the viewport when the
// document is all the way on the top we need to calculate how many pixels
// of the parallax container are visible.
imgData.push({
imageElement : image, // [1]
imageHeight : image.offsetHeight, // [2]
imageOverflow : image.offsetHeight - parentElement.offsetHeight, // [3]
parentHeight : parentElement.offsetHeight, // [4]
parentOffset : getOffset(parentElement).top, // [5]
travelCorrection : (getOffset(parentElement).top < windowHeight) ? (windowHeight - getOffset(parentElement).top) : 0 // [6]
});
}
// Listen to the scroll event, it is our cue to show
// the parallax behaviour
window.addEventListener('scroll', onScrollHandler);
}
/* == INIT METHODS == */
init();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment