WEBLEB
Home
Editor
Login
Pro
English
English
Français
Español
Português
Deutsch
Italiano
हिंदी
F1 Drivers 2024 - circular gallery with thumbs
971
zegarkidawida
Open In Editor
Publish Your Code
Recommended
2 March 2025
Flip-Card
22 November 2024
Landing Page - God of War
30 March 2025
Zig-zag border & cool hover effect
HTML
Copy
CSS
Copy
*,::before,::after{ margin: 0; padding: 0; box-sizing: border-box; } body { height: 100svh; display: grid; place-items: center; background-color: black; font-family: system-ui; font-size: 1rem; } .gallery-wrapper{ width: 80vw; overflow: hidden; } .gallery-nav{ position: absolute; inset: 0; z-index: 1; } .gallery-nav img{ width: 100%; height: 100%; object-fit: cover; border-radius: 50%; } .gallery-nav button{ --_bg: rgba(0, 255,255,.5); position: absolute; inset: 0; transform-origin: center; width: 50px; height: 50px; cursor: pointer; color: white; text-decoration: none; padding: .25rem; font-size: 0.8rem; border: none; outline: none; border-radius: 999vw; background-color: #FFFFFF20; transition: background-color 300ms ease-in-out, opacity 300ms ease-in-out,filter 300ms ease-in-out; opacity: .75; } .gallery-nav button:hover, .gallery-nav button:focus-visible, .gallery-nav button.active{ background-color: rgba(255, 255, 255,.5); opacity: 1; } .gallery-nav:has(:hover) > button:not(:hover){ filter: sepia(100) } .gallery-nav > button::before { content: ''; position: absolute; inset: 0; border: 1px solid var(--_bg); border-radius: 9999px; transition: all 0.3s; z-index: 10; display: grid; place-content: center; } .gallery-nav > button:hover::before { inset: 1rem -1.85rem; background-color: var(--_bg); } .gallery-nav > button::after { content: attr(title); position: absolute; inset: 1rem -1.85rem; transition: opacity 0.3s 150ms,translate 0.3s 150ms,letter-spacing 0.3s 0s; z-index: 10; display: grid; place-content: center; font-size: 0.7rem; text-wrap: nowrap; opacity: 0; /*translate: 0 10px;*/ letter-spacing: 50px; overflow: hidden; } .gallery-nav > button:hover::after { opacity: 1; /*translate: 0;*/ letter-spacing: 0; transition-delay: 150ms; } .gallery { display: grid; grid-auto-flow: column; flex-flow: row nowrap; position: relative; scroll-snap-type: x mandatory; -ms-overflow-style: none; /* for Internet Explorer, Edge */ scrollbar-width: none; /* for Firefox */ overflow-y: scroll; } .gallery::-webkit-scrollbar { display: none; /* for Chrome, Safari, and Opera */ } .gallery::before { --_size: 195px; --_blur: 20px; --_dot-mask: radial-gradient( circle at 50% 50%, transparent var(--_size), white var(--_size) ); content: ""; position: fixed; inset: 0; background-color: #ffffff10; background-image: radial-gradient( circle at 50%, currentColor var(--_size), transparent calc(var(--_size)) ); pointer-events: none; backdrop-filter: blur(var(--_blur)); -webkit-mask-image: var(--_dot-mask); mask-image: var(--_dot-mask); transition: inset var(--_duration) ease-in-out; will-change: inset; } /* images (and spacers)*/ .gallery > * { display: block; width: 400px; aspect-ratio: 1/1; margin-right: 0.5rem; border-radius: 50%; scroll-snap-align: center; z-index: -10; } .gallery > div{ /*background-color: pink;*/ }
JS
Copy
const IMAGES = [ { "id": "image-1", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/M/MAXVER01_Max_Verstappen/maxver01.png", "alt": "Max Verstappen", "title": "Max Verstappen" }, { "id": "image-2", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/C/CHALEC01_Charles_Leclerc/chalec01.png", "alt": "Charles Leclerc", "title": "Charles Leclerc" }, { "id": "image-3", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/L/LANNOR01_Lando_Norris/lannor01.png", "alt": "Lando Norris", "title": "Lando Norris" }, { "id": "image-4", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/C/CARSAI01_Carlos_Sainz/carsai01.png", "alt": "Carlos Sainz", "title": "Carlos Sainz" }, { "id": "image-5", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/S/SERPER01_Sergio_Perez/serper01.png", "alt": "Sergio Perez", "title": "Sergio Perez" }, { "id": "image-6", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/O/OSCPIA01_Oscar_Piastri/oscpia01.png", "alt": "Oscar Piastre", "title": "Oscar Piastre" }, { "id": "image-7", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/G/GEORUS01_George_Russell/georus01.png", "alt": "George Russel", "title": "George Russel" }, { "id": "image-8", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/L/LEWHAM01_Lewis_Hamilton/lewham01.png", "alt": "Lewis Hamilton", "title": "Lewis Hamilton" }, { "id": "image-9", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/F/FERALO01_Fernando_Alonso/feralo01.png", "alt": "Fernando Alonso", "title": "Fernando Alonso" }, { "id": "image-10", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/Y/YUKTSU01_Yuki_Tsunoda/yuktsu01.png", "alt": "Yuki Tsunoda", "title": "Yuki Tsunoda" }, { "id": "image-11", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/L/LANSTR01_Lance_Stroll/lanstr01.png", "alt": "Lance Stroll", "title": "Lance Stroll" }, { "id": "image-12", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/D/DANRIC01_Daniel_Ricciardo/danric01.png", "alt": "Daniel Riccardo", "title": "Daniel Riccardo" }, { "id": "image-13", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/O/OLIBEA01_Oliver_Bearman/olibea01.png", "alt": "Oliver Bearman", "title": "Oliver Bearman" }, { "id": "image-14", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/N/NICHUL01_Nico_Hulkenberg/nichul01.png", "alt": "Nico Hulkenberg", "title": "Nico Hulkenberg" }, { "id": "image-15", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/P/PIEGAS01_Pierre_Gasly/piegas01.png", "alt": "Pierre Gasly", "title": "Pierre Gasly" }, { "id": "image-16", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/A/ALEALB01_Alexander_Albon/alealb01.png", "alt": "Alexander Albon", "title": "Alexander Albon" }, { "id": "image-17", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/E/ESTOCO01_Esteban_Ocon/estoco01.png", "alt": "Eseban Ocon", "title": "Eseban Ocon" }, { "id": "image-18", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/K/KEVMAG01_Kevin_Magnussen/kevmag01.png", "alt": "Kevin Magnussen", "title": "Kevin Magnussen" }, { "id": "image-19", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/G/GUAZHO01_Guanyu_Zhou/guazho01.png", "alt": "Guanyu Zhou", "title": "Guanyu Zhou" }, { "id": "image-20", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/V/VALBOT01_Valtteri_Bottas/valbot01.png", "alt": "Valtteri Botas", "title": "Valtteri Botas" }, { "id": "image-21", "src": "https://media.formula1.com/d_driver_fallback_image.png/content/dam/fom-website/drivers/L/LOGSAR01_Logan_Sargeant/logsar01.png", "alt": "Logan Sargeant", "title": "Logan Sargeant" } ] const navEl = document.querySelector('#gallery-nav'); const galleryEl = document.querySelector("#gallery"); const RADIUS = 250; // distance (radius) from center of gallery const START_INDEX = 2; // index of item for load animation function setupGalleryAndNav() { // add 2 extra elements at beginning to "compensate" for mask positioning const extraElStart1 = document.createElement("div"); galleryEl.appendChild(extraElStart1); IMAGES.forEach(image => { const galleryImg = document.createElement('img'); galleryImg.src = image.src; galleryImg.alt = image.alt; galleryEl.title = image.title; gallery.appendChild(galleryImg); }); // add 2 extra elements at end to "compensate" for mask positioning const extraElEnd1 = document.createElement("div"); galleryEl.appendChild(extraElEnd1); // create button for each image IMAGES.forEach((image, index) => { const button = document.createElement('button'); button.title = image.title; const img = document.createElement('img'); img.src = image.src; img.alt = image.alt; img.title = image.title; button.appendChild(img); navEl.appendChild(button); // position button around the circle const buttonRect = button.getBoundingClientRect(); const buttonSize = buttonRect.width; // calculate position const angle = (index / IMAGES.length) * 360; const x = Math.cos(angle * (Math.PI / 180)) * RADIUS; const y = Math.sin(angle * (Math.PI / 180)) * RADIUS; // center the buttons around the middle of the <nav> container button.style.position = 'absolute'; button.style.left = `calc(50% + ${x - (buttonSize / 2)}px)`; button.style.top = `calc(50% + ${y - (buttonSize / 2)}px)`; // button event listener button.addEventListener('click', () => { // remove active navEl.querySelectorAll("button.active").forEach(btn => btn.classList.remove('active')); button.classList.add('active'); // scroll to selected image const imgToScroll = document.querySelector(`#gallery img[src="${image.src}"]`); imgToScroll.scrollIntoView({ behavior: 'smooth' }); }); // load inital image if (index === START_INDEX) { setTimeout(() => button.click(),150); } }); } setupGalleryAndNav();