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.
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.
<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&display=swap" rel="stylesheet" /> |