-
Animated circular gallery
HTML
CSS
JavaScript
Hover an Image
/* Reset & Center Layout */ * { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #0f0f1a; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } /* Gallery Viewport */ .gallery-container { position: relative; width: 600px; height: 600px; display: flex; justify-content: center; align-items: center; } .gallery-center-text { position: absolute; color: #ffffff; font-size: 1.5rem; font-weight: 600; text-transform: uppercase; letter-spacing: 2px; text-align: center; pointer-events: none; z-index: 5; transition: opacity 0.3s ease, transform 0.3s ease; text-shadow: 0 0 10px rgba(255, 53, 102, 0.6); } /* Hide text when an image takes over the center space */ .gallery-center-text.hidden { opacity: 0; transform: scale(0.8); } /* The Spinning Wheel */ .gallery-wheel { position: relative; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; border-radius: 50%; animation: spin 30s linear infinite; z-index: 2; } .gallery-wheel.paused { animation-play-state: paused; } /* Positioning individual items */ .gallery-item { position: absolute; width: 120px; height: 120px; transform-origin: center; transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1), z-index 0s; transform: rotate(calc(var(--i) * 45deg)) translateY(-220px); z-index: 1; } /* Styling and Counter-Rotating the Images */ .gallery-item img { width: 100%; height: 100%; object-fit: cover; border-radius: 15px; border: 4px solid #fff; box-shadow: 0 10px 25px rgba(0,0,0,0.5); transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1), border-color 0.3s; animation: counter-spin 30s linear infinite; } .gallery-wheel.paused .gallery-item img { animation-play-state: paused; } /* Hover effect for non-active items */ .gallery-item:not(.active) img:hover { transform: scale(1.1); border-color: #ff3366; cursor: pointer; } /* --- ACTIVE STATE (ON CLICK) --- */ .gallery-item.active { z-index: 10; transform: rotate(0deg) translateY(0px); } .gallery-item.active img { transform: scale(2.2); border-color: #ff3366; box-shadow: 0 20px 50px rgba(0,0,0,0.7); } /* --- KEYFRAMES FOR ANIMATION --- */ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes counter-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(-360deg); } }
const items = document.querySelectorAll('.gallery-item'); const wheel = document.querySelector('.gallery-wheel'); const centerText = document.querySelector('.gallery-center-text'); const defaultText = "Hover an Image"; items.forEach(item => { const title = item.getAttribute('data-title'); // --- HOVER EVENTS --- item.addEventListener('mouseenter', () => { // Only change text if no image is currently opened/clicked in the center if (!document.querySelector('.gallery-item.active')) { centerText.textContent = title; centerText.classList.add('visible'); } }); item.addEventListener('mouseleave', () => { if (!document.querySelector('.gallery-item.active')) { centerText.textContent = defaultText; } }); // --- CLICK EVENTS --- item.addEventListener('click', (e) => { if (item.classList.contains('active')) { item.classList.remove('active'); wheel.classList.remove('paused'); centerText.textContent = defaultText; centerText.classList.remove('hidden'); } else { items.forEach(i => i.classList.remove('active')); item.classList.add('active'); wheel.classList.add('paused'); // Hide the center text completely so it doesn't clip behind the expanded image centerText.classList.add('hidden'); } e.stopPropagation(); }); }); // Reset everything when clicking outside document.addEventListener('click', () => { items.forEach(i => i.classList.remove('active')); wheel.classList.remove('paused'); centerText.textContent = defaultText; centerText.classList.remove('hidden'); });
Live Preview