Parallaxify
A JavaScript module to easily produce a parallax image effect.September 1, 2015 · See the codeWhile 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.
Notice in the above gif that the mountains and horses' heads approach the top of the image faster than the image approaches the top of the frame.
I wasn't able to find a simple, reusable solution so I created my own and published it for anyone to use.
This little snippet is super-lightweight and requires zero dependencies. I hadn't touched the code in years, but I revisited it for this writeup and really realized how much I've learned. 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
).
Before
for (let i = 0; i < elements.length; i++) {
let element = elements[i]
let elementOffsetTop = element.offsetTop
function percentSeen () {
let distance = (winScrollTop + viewportHeight) - elementOffsetTop
...
}
}
After
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 classic if (someTrueThing) return true; else return false
pattern, I return a clamped number based directly on the source value.
Before
if (percentage < 0) return 0;
else if (percentage > 100) return 100;
else return percentage;
After
return Math.min(Math.max(percentage, 0), 100); // clamp between 0 and 100
Reference 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.
Before
let viewportHeight = 0
let updateWindowCalcs = function () {
viewportHeight = window.innerHeight
}
updateWindowCalcs()
window.addEventListener('resize', function(){
updateWindowCalcs()
})
function percentSeen () {
...
let distance = (winScrollTop + viewportHeight) - elementOffsetTop
...
}
After
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.
Before
function percentSeen () {...}
element.style.backgroundPositionY = percentSeen()+'%'
After
function getPercentSeen(el) {...}
el.style.backgroundPositionY = `${getPercentSeen(el)}%`;