Parallaxify
A JavaScript module to easily produce a parallax image effect.September 1, 2015 · See the codeMotivation
While building a microsite for a family member I wanted to include an elegant parallax image effect on scroll. A parallax effect is when an image appears to scroll slightly faster than the rest of the page, producing a subtle shifting/drifting sensation to convey depth.
I wasn't able to find a simple, reusable solution so I created my own and published it for anyone to use. The code is super-lightweight and requires zero dependencies.
Lessons and improvements
I hadn't touched the code in years, but I revisited it for this writeup and realized how much I've learned since the original publication. I was able to reduce the lines of codes by 65% (from 55 to 23 lines). The minified version shrunk 59% (from 932 to 374 Bytes).
A diff of the before/after can be found on Github, but I'll share a few key improvements below.
Pure functions
Instead of defining percentSeen inside the for loop and referencing an external variable (element), define percentSeen once and accept an argument (el).
for (let i = 0; i < elements.length; i++) {
let element = elements[i]
let elementOffsetTop = element.offsetTop
function percentSeen () {
let distance = (winScrollTop + viewportHeight) - elementOffsetTop
...
}
}function getPercentSeen(el) {
let distance = window.scrollY + window.innerHeight - el.offsetTop
...
}
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
el.style.backgroundPositionY = `${getPercentSeen(el)}%`;
...
}
Improved clamping
Instead of the naive if (someTrueThing) return true; else return false pattern, I return a clamped number based directly on the source value.
if (percentage < 0) return 0;
else if (percentage > 100) return 100;
else return percentage;return Math.min(Math.max(percentage, 0), 100); // clamp between 0 and 100Reference values directly
Previously, I jumped through hoops to define and maintain the viewportHeight variable when the script was initialized or the window was resized. A simpler approach is to just reference the underlying window.innerHeight value directly as-needed, and not worry at all about syncing another variable. If the window is resized, the up-to-date value is used.
let viewportHeight = 0
let updateWindowCalcs = function () {
viewportHeight = window.innerHeight
}
updateWindowCalcs()
window.addEventListener('resize', function(){
updateWindowCalcs()
})
function percentSeen () {
...
let distance = (winScrollTop + viewportHeight) - elementOffsetTop
...
}function getPercentSeen(el) {
let distance = window.scrollY + window.innerHeight - el.offsetTop;
...
}Function naming
Use naming conventions to indicate if a function is getting or setting a value.
function percentSeen () {...}
element.style.backgroundPositionY = percentSeen()+'%'function getPercentSeen(el) {...}
el.style.backgroundPositionY = `${getPercentSeen(el)}%`;