A Pen by Filip Zrnzevic on CodePen.
Created
September 15, 2025 11:50
-
-
Save rsp2k/b729bacaeaf4969b76f68354593b34a7 to your computer and use it in GitHub Desktop.
[gsap/component] ❍ Full Screen Image Zoom on Hover
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="background-image-container"> | |
<img id="background-image" crossorigin="anonymous" alt=""> | |
</div> | |
<div class="container"> | |
<div class="projects-container"> | |
<!-- Projects will be rendered here by JavaScript --> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Project data with minimalist titles | |
const projects = [ | |
{ | |
id: 1, | |
title: "Silence", | |
year: "2021", | |
image: | |
"https://cdn.cosmos.so/7d47d4e2-0eff-4e2f-9734-9d24a8ba067e?format=jpeg" | |
}, | |
{ | |
id: 2, | |
title: "Resonance", | |
year: "2022", | |
image: | |
"https://cdn.cosmos.so/5eee2d2d-3d4d-4ae5-96d4-cdbae70a2387?format=jpeg" | |
}, | |
{ | |
id: 3, | |
title: "Essence", | |
year: "2022", | |
image: | |
"https://cdn.cosmos.so/def30e8a-34b2-48b1-86e1-07ec5c28f225?format=jpeg" | |
}, | |
{ | |
id: 4, | |
title: "Void", | |
year: "2023", | |
image: | |
"https://cdn.cosmos.so/44d7cb23-6759-49e4-9dc1-acf771b3a0d1?format=jpeg" | |
}, | |
{ | |
id: 5, | |
title: "Presence", | |
year: "2023", | |
image: | |
"https://cdn.cosmos.so/7712fe42-42ca-4fc5-9590-c89f2db99978?format=jpeg" | |
}, | |
{ | |
id: 6, | |
title: "Flow", | |
year: "2024", | |
image: | |
"https://cdn.cosmos.so/cbee1ec5-01b6-4ffe-9f34-7da7980454cf?format=jpeg" | |
}, | |
{ | |
id: 7, | |
title: "Clarity", | |
year: "2024", | |
image: | |
"https://cdn.cosmos.so/2e91a9d1-db85-4499-ad37-6222a6fea23b?format=jpeg" | |
}, | |
{ | |
id: 8, | |
title: "Breath", | |
year: "2024", | |
image: | |
"https://cdn.cosmos.so/ff2ac3d3-fa94-4811-89f6-0d008b27e439?format=jpeg" | |
}, | |
{ | |
id: 9, | |
title: "Stillness", | |
year: "2025", | |
image: | |
"https://cdn.cosmos.so/c39a4043-f489-4406-8018-a103a3f89802?format=jpeg" | |
}, | |
{ | |
id: 10, | |
title: "Surrender", | |
year: "2025", | |
image: | |
"https://cdn.cosmos.so/e5e399f2-4050-463b-a781-4f5a1615f28e?format=jpeg" | |
} | |
]; | |
document.addEventListener("DOMContentLoaded", function () { | |
const projectsContainer = document.querySelector(".projects-container"); | |
const backgroundImage = document.getElementById("background-image"); | |
// Render projects | |
renderProjects(projectsContainer); | |
// Initialize animations | |
initialAnimation(); | |
// Preload images | |
preloadImages(); | |
// Add hover events to project items | |
setupHoverEvents(backgroundImage, projectsContainer); | |
}); | |
// Render project items | |
function renderProjects(container) { | |
projects.forEach((project) => { | |
const projectItem = document.createElement("div"); | |
projectItem.classList.add("project-item"); | |
projectItem.dataset.id = project.id; | |
projectItem.dataset.image = project.image; | |
projectItem.innerHTML = ` | |
<div class="project-title">${project.title}</div> | |
<div class="project-year">${project.year}</div> | |
`; | |
container.appendChild(projectItem); | |
}); | |
} | |
// Initial animation for project items | |
function initialAnimation() { | |
const projectItems = document.querySelectorAll(".project-item"); | |
// Set initial state | |
projectItems.forEach((item, index) => { | |
item.style.opacity = "0"; | |
item.style.transform = "translateY(20px)"; | |
// Animate in with staggered delay | |
setTimeout(() => { | |
item.style.transition = "opacity 0.8s ease, transform 0.8s ease"; | |
item.style.opacity = "1"; | |
item.style.transform = "translateY(0)"; | |
}, index * 60); | |
}); | |
} | |
// Setup hover events for project items | |
function setupHoverEvents(backgroundImage, projectsContainer) { | |
const projectItems = document.querySelectorAll(".project-item"); | |
let currentImage = null; | |
let zoomTimeout = null; | |
// Preload all images to ensure immediate display | |
const preloadedImages = {}; | |
projects.forEach((project) => { | |
const img = new Image(); | |
img.crossOrigin = "anonymous"; | |
img.src = project.image; | |
preloadedImages[project.id] = img; | |
}); | |
projectItems.forEach((item) => { | |
item.addEventListener("mouseenter", function () { | |
const imageUrl = this.dataset.image; | |
// Clear any pending zoom timeout | |
if (zoomTimeout) { | |
clearTimeout(zoomTimeout); | |
} | |
// Reset transform and transition | |
backgroundImage.style.transition = "none"; | |
backgroundImage.style.transform = "scale(1.2)"; | |
// Immediately show the new image | |
backgroundImage.src = imageUrl; | |
backgroundImage.style.opacity = "1"; | |
// Force browser to acknowledge the scale reset before animating | |
// This ensures the zoom effect happens every time | |
requestAnimationFrame(() => { | |
requestAnimationFrame(() => { | |
// Re-enable transition and animate to scale 1.0 | |
backgroundImage.style.transition = | |
"transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94)"; | |
backgroundImage.style.transform = "scale(1.0)"; | |
}); | |
}); | |
// Update current image | |
currentImage = imageUrl; | |
}); | |
}); | |
// Handle mouse leaving the projects container | |
projectsContainer.addEventListener("mouseleave", function () { | |
// Hide the image | |
backgroundImage.style.opacity = "0"; | |
currentImage = null; | |
}); | |
} | |
// Preload images | |
function preloadImages() { | |
projects.forEach((project) => { | |
const img = new Image(); | |
img.crossOrigin = "anonymous"; | |
img.src = project.image; | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@font-face { | |
src: url("https://fonts.cdnfonts.com/css/pp-neue-montreal") format("woff2"); | |
font-family: "PP Neue Montreal", sans-serif; | |
font-weight: 400; | |
} | |
*, | |
*:before, | |
*:after { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
:root { | |
--warm-off-black: #1a1917; | |
--warm-off-white: #f8f5f2; | |
} | |
html, | |
body { | |
height: 100%; | |
overflow: hidden; | |
} | |
body { | |
font-family: "PP Neue Montreal", sans-serif; | |
font-weight: 700; | |
font-size: 18px; | |
text-rendering: optimizeLegibility; | |
-webkit-font-smoothing: antialiased; | |
-moz-osx-font-smoothing: grayscale; | |
background-color: var(--warm-off-black); | |
text-transform: uppercase; | |
letter-spacing: -0.03em; | |
color: var(--warm-off-white); | |
position: relative; | |
margin: 0; | |
padding: 0; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
/* Animated noise effect */ | |
body::before { | |
content: ""; | |
position: fixed; | |
top: -50%; | |
left: -50%; | |
width: 200%; | |
height: 200%; | |
background: transparent | |
url("http://assets.iceable.com/img/noise-transparent.png") repeat 0 0; | |
background-size: 300px 300px; | |
animation: noise-animation 0.3s steps(5) infinite; | |
opacity: 0.9; | |
will-change: transform; | |
z-index: 100; | |
pointer-events: none; | |
} | |
@keyframes noise-animation { | |
0% { | |
transform: translate(0, 0); | |
} | |
10% { | |
transform: translate(-2%, -3%); | |
} | |
20% { | |
transform: translate(-4%, 2%); | |
} | |
30% { | |
transform: translate(2%, -4%); | |
} | |
40% { | |
transform: translate(-2%, 5%); | |
} | |
50% { | |
transform: translate(-4%, 2%); | |
} | |
60% { | |
transform: translate(3%, 0); | |
} | |
70% { | |
transform: translate(0, 3%); | |
} | |
80% { | |
transform: translate(-3%, 0); | |
} | |
90% { | |
transform: translate(2%, 2%); | |
} | |
100% { | |
transform: translate(1%, 0); | |
} | |
} | |
/* Container for centering */ | |
.container { | |
width: 100%; | |
max-width: 1000px; /* Increased width */ | |
height: 100%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
z-index: 10; | |
position: relative; | |
} | |
/* Full-screen background image */ | |
#background-image-container { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 0; | |
overflow: hidden; | |
} | |
#background-image { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
transform: scale(1.2); /* Start zoomed in */ | |
transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
opacity: 0; | |
} | |
/* Projects Container */ | |
.projects-container { | |
width: 100%; | |
position: relative; | |
z-index: 10; | |
max-height: 80vh; | |
overflow-y: auto; | |
padding: 20px; | |
/* Hide scrollbar for Chrome, Safari and Opera */ | |
scrollbar-width: none; /* Firefox */ | |
-ms-overflow-style: none; /* IE and Edge */ | |
} | |
/* Hide scrollbar for Chrome, Safari and Opera */ | |
.projects-container::-webkit-scrollbar { | |
display: none; | |
} | |
/* Project Item Styles */ | |
.project-item { | |
position: relative; | |
display: flex; | |
justify-content: space-between; | |
padding: 0.7rem 0; /* Further reduced padding for smaller line height */ | |
border-bottom: 1px solid rgba(248, 245, 242, 0.1); | |
cursor: pointer; | |
} | |
/* Project title and year */ | |
.project-title, | |
.project-year { | |
font-size: 1.8rem; | |
position: relative; | |
z-index: 2; | |
mix-blend-mode: exclusion; | |
} | |
/* List view hover animation with exclusion blend mode */ | |
.project-item::before { | |
content: ""; | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
width: 100%; | |
height: 0; | |
background-color: var(--warm-off-white); | |
z-index: 1; | |
pointer-events: none; | |
transition: height 0.2s cubic-bezier(0.445, 0.05, 0.55, 0.95) 0s; | |
} | |
.project-item:hover::before { | |
height: 100%; | |
} | |
.project-item:not(:hover)::before { | |
transition-duration: 0.8s; | |
} | |
/* Responsive Styles */ | |
@media (max-width: 768px) { | |
.project-title, | |
.project-year { | |
font-size: 1.4rem; | |
} | |
} | |
@media (max-width: 480px) { | |
.project-item { | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment