Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Efficient Scroll Zoom

Efficient Scroll Zoom

This method utilizes the Intersection Observer to zoom the image only when it is in view. This cuts down on the processing power required for this effect.

A Pen by Chris Weissenberger on CodePen.

License.

<div class="background">
<div class="container">
<div class="image">
<img src="https://source.unsplash.com/random/1" data-scroll-zoom />
</div>
<div class="image">
<img src="https://source.unsplash.com/random/2" data-scroll-zoom />
</div>
<div class="image">
<img src="https://source.unsplash.com/random/3" data-scroll-zoom />
</div>
<div class="image">
<img src="https://source.unsplash.com/random/4" data-scroll-zoom />
</div>
<div class="image">
<img src="https://source.unsplash.com/random/5" data-scroll-zoom />
</div>
<div class="image">
<img src="https://source.unsplash.com/random/6" data-scroll-zoom />
</div>
<div>
<p>This method utilizes the Intersection Observer to zoom the image <em>only when it is in view,</em> which cuts down on the processing power required for this effect.</p>
</div>
</div>
</div>
// Higher number = more zoom
let scaleAmount = 0.5;
function scrollZoom() {
const images = document.querySelectorAll("[data-scroll-zoom]");
let scrollPosY = 0;
scaleAmount = scaleAmount / 100;
const observerConfig = {
rootMargin: "0% 0% 0% 0%",
threshold: 0
};
// Create separate IntersectionObservers and scroll event listeners for each image so that we can individually apply the scale only if the image is visible
images.forEach(image => {
let isVisible = false;
const observer = new IntersectionObserver((elements, self) => {
elements.forEach(element => {
isVisible = element.isIntersecting;
});
}, observerConfig);
observer.observe(image);
// Set initial image scale on page load
image.style.transform = `scale(${1 + scaleAmount * percentageSeen(image)})`;
// Only fires if IntersectionObserver is intersecting
window.addEventListener("scroll", () => {
if (isVisible) {
scrollPosY = window.pageYOffset;
image.style.transform = `scale(${1 +
scaleAmount * percentageSeen(image)})`;
}
});
});
// Calculates the "percentage seen" based on when the image first enters the screen until the moment it leaves
// Here, we get the parent node position/height instead of the image since it's in a container that has a border, but
// if your container has no extra height, you can simply get the image position/height
function percentageSeen(element) {
const parent = element.parentNode;
const viewportHeight = window.innerHeight;
const scrollY = window.scrollY;
const elPosY = parent.getBoundingClientRect().top + scrollY;
const borderHeight = parseFloat(getComputedStyle(parent).getPropertyValue('border-bottom-width')) + parseFloat(getComputedStyle(element).getPropertyValue('border-top-width'));
const elHeight = parent.offsetHeight + borderHeight;
if (elPosY > scrollY + viewportHeight) {
// If we haven't reached the image yet
return 0;
} else if (elPosY + elHeight < scrollY) {
// If we've completely scrolled past the image
return 100;
} else {
// When the image is in the viewport
const distance = scrollY + viewportHeight - elPosY;
let percentage = distance / ((viewportHeight + elHeight) / 100);
percentage = Math.round(percentage);
return percentage;
}
}
}
scrollZoom();
body {
margin: 0;
}
.background {
align-items: center;
background: #04FEBA;
display: flex;
height: 100%;
justify-content: center;
width: 100vw;
}
.container {
align-items: center;
display: flex;
flex-direction: column;
height: 600vh;
justify-content: space-around;
text-align: center;
text-transform: uppercase;
width: 100vmin;
p {
font-family: Merriweather, sans-serif;
font-size: 20px;
line-height: 1.5;
max-width: 70vmin;
text-align: justify;
text-transform: none;
}
}
.image {
background: white;
box-shadow: 3px 10px 10px rgba(0, 0, 0, 0.25);
border: 15px solid white;
border-width: 1vmin 1vmin 10vmin 1vmin;
height: 70vmin;
overflow: hidden;
width: 70vmin;
img {
height: 100%;
object-fit: cover;
width: 100%;
}
}
<link href="https://fonts.googleapis.com/css?family=Merriweather&amp;display=swap" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.