Skip to content

Instantly share code, notes, and snippets.

@dnordby
Created July 10, 2025 11:29
Show Gist options
  • Save dnordby/bd90e76b249f87fda186cc04ae5521a3 to your computer and use it in GitHub Desktop.
Save dnordby/bd90e76b249f87fda186cc04ae5521a3 to your computer and use it in GitHub Desktop.
Fade images up on load and scroll
const scrollListenersAdded = /* @__PURE__ */ new WeakSet();
function imageFadeUp() {
const imageWrappers = document.querySelectorAll(".js-fade-up");
imageWrappers.forEach((wrapper) => {
const rect = wrapper.getBoundingClientRect();
if (rect.top <= window.innerHeight - 45) {
const image = wrapper.querySelector("img");
if (image) {
if (!image.complete) {
image.addEventListener(
"load",
() => wrapper.classList.add("is-visible"),
{ once: true }
);
} else {
wrapper.classList.add("is-visible");
}
}
}
});
}
function updateImageZoom() {
const imageWrappers = document.querySelectorAll(".js-fade-up");
const viewportCenter = window.innerHeight / 2;
imageWrappers.forEach((wrapper) => {
const rect = wrapper.getBoundingClientRect();
const image = wrapper.querySelector("img");
if (image) {
const elementCenter = rect.top + rect.height / 2;
const distanceFromCenter = Math.abs(elementCenter - viewportCenter);
const maxDistance = window.innerHeight / 2;
const zoomFactor = Math.max(
1,
1.05 - distanceFromCenter / maxDistance * 0.05
);
image.style.transform = `scale(${zoomFactor})`;
image.style.transition = "transform 0.1s ease-out";
}
});
}
function addScrollListener(element) {
if (!scrollListenersAdded.has(element)) {
element.addEventListener("scroll", () => {
imageFadeUp();
updateImageZoom();
});
scrollListenersAdded.add(element);
}
}
function setupScrollListeners() {
if (!scrollListenersAdded.has(window)) {
window.addEventListener("scroll", () => {
imageFadeUp();
updateImageZoom();
});
scrollListenersAdded.add(window);
}
const scrollableElements = document.querySelectorAll(
'[style*="overflow"], [class*="overflow"]'
);
scrollableElements.forEach((element) => {
if (element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth) {
addScrollListener(element);
}
});
}
window.addEventListener("load", () => {
imageFadeUp();
updateImageZoom();
setupScrollListeners();
});
const observer = new MutationObserver(setupScrollListeners);
observer.observe(document.body, { childList: true, subtree: true });
//# sourceMappingURL=custom.js.map
@dnordby
Copy link
Author

dnordby commented Jul 10, 2025

Usage:

  • Add js-fade-up class to image wrapper. This triggers the JS listener.
  • Add fade-up class to image wrapper to implement the scroll styles
<div class="flex aspect-1 items-center justify-center bg-gray-50 js-fade-up fade-up">
  {% render 'responsive-image-v2',
    image: media,
    class: 'h-full w-full',
    cover: true,
    sizes: '(min-width: 1280px) 418px, 336px',
    image_alt: media.alt
  %}
</div>

CSS Updates Needed:

.fade-up {
  opacity: 0;
  overflow: hidden;
  transition:
    transform 0.45s ease,
    opacity 0.45s ease;
  transform: translateY(15px);
}

.fade-up.is-visible {
  opacity: 1;
  transform: translateY(0);
}

NOTE: this should be generalized to anything that has the js-fade-up and fade-up classes, once the CSS has been included. It can then be used anywhere on the site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment