Northskull: Building a parallax scrolling system with vanilla Javascript
Having finished the job of building the front-end on the new Northskull website at Nation, I thought I would take a section of the site which was an interesting challenge, and explain how it went down - the βNorthskull Discoverβ page.
The parallax effect of the different elements on this page went through 3 main versions. You can see the final result on this page as you scroll around π (except on mobile, sorry π)
Version 1 began with empty coloured blocks travelling up the page at a slightly slower speed than the users scroll speed. This achieved the technical side of the parallax effect, but wasnβt exactly, smooth.
Version 2 we started playing with CSS transitions on the translation that was happening to the element. This started to look a bit nicer, but was causing quite a lot of performance issues, mainly because there were so many blocks gliding around at once.
Version 3 was basically a clean slate. After a few different ideas with design, the code needed a bit of a scrub. Having figured out what did and didn't work, I focused on the scrolling performance by dividing the page up into βcontainersβ and only scrolling the child elements when that container was inside the viewport. Here is how was that was achieved with code:
Obtain the top and bottom positions of each container:
for (var i = 0; i < CONTAINERS.length; i++) {
var container = CONTAINERS[i];
var rect = container.getBoundingClientRect();
containerData.push({
top: rect.top + window.pageYOffset - (document.documentElement.clientTop || 0),
bottom: (rect.top + window.pageYOffset - (document.documentElement.clientTop || 0)) + container.clientHeight
})
}
Loop containers and decide when to move the elements inside that container based on the each container position:
for (var j = 0; j < CONTAINERS.length; j++) {
var container = CONTAINERS[j];
var containerTop = containerData[j].top;
var containerBottom = containerData[j].bottom;
// If this container is inside the viewport, move the items inside
if (containerTop < windowBottom && containerBottom > windowScrollTop) {
// Get the progress of how far the current container has travelled up the viewport.
// Number from 0 to 100.
var distance = (windowScrollTop + window.innerHeight) - containerTop;
var progress = Math.round(distance / ((window.innerHeight + container.clientHeight) / 100));
adjustParallaxElements(j, progress);
}
}
Finally, move the child elements inside the container that is within the viewport:
function adjustParallaxElements(containerID, progress) {
var container = CONTAINERS[containerID];
var item = container.querySelectorAll('article');
for (var i = 0; i < item.length; i++) {
var newTop = (progress / 100) * item[i].getAttribute('data-percentage-to-travel');
var value = 'translateY(-'+newTop+'%)';
item[i].style.webkitTransform = value;
item[i].style.msTransform = value;
item[i].style.transform = value;
}
}
As you can see the translateY
of the child element is being adjusted. To add the smoothness we were looking for previously, we added a 400ms transition to each element to mimic our inertia.
Oh yeah, each element has a data-attribute called data-percentage-to-travel
. Nice naming, I know. This defines how far the element travels within it's parent container π
There are a million (roughly) ways to do everything with front-end so if you want to pull my idea apart and tell me I am an idiot, I'm right here β @tomsansome ππ