WEBLEB
Inicio
Editora de código
Iniciar sesión
Pro
Español
English
Français
Español
Português
Deutsch
Italiano
हिंदी
Galería de sitios animada y 3D
1878
Andev.web
Abrir en el editor
Publica tu código
Recomendado
31 August 2024
Página de inicio de sesión
3 July 2025
Ruleta de decisiones
10 December 2024
Clon de Spotify
HTML
Copy
Andev Web
Christ the
Redeemer
Rio de Janeiro
Iconic symbol of Brazil
Pyramids of
Teotihuacan
Mexico
Architectural wonder of ancient Mesoamerica
Machu Picchu
Peru
Lost city of the Incas
Loading...
CSS
Copy
@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800&display=swap"); :root { --card-width: 200px; --card-height: 300px; --card-transition-duration: 800ms; --card-transition-easing: ease; } * { box-sizing: border-box; margin: 0; padding: 0; } body { width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; background: rgba(0, 0, 0, 0.787); overflow: hidden; } button { border: none; background: none; cursor: pointer; } button:focus { outline: none; border: none; } .app { position: relative; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } .app__bg { position: absolute; width: 100%; height: 100%; z-index: -5; filter: blur(8px); pointer-events: none; user-select: none; overflow: hidden; } .app__bg::before { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: #000; z-index: 1; opacity: 0.8; } .app__bg__image { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) translateX(var(--image-translate-offset, 0)); width: 180%; height: 180%; transition: transform 1000ms ease, opacity 1000ms ease; overflow: hidden; } .app__bg__image img { width: 100%; height: 100%; object-fit: cover; } .app__bg__image.current--image { opacity: 1; --image-translate-offset: 0; } .app__bg__image.previous--image, .app__bg__image.next--image { opacity: 0; } .app__bg__image.previous--image { --image-translate-offset: -25%; } .app__bg__image.next--image { --image-translate-offset: 25%; } .cardList { position: absolute; width: calc(3 * var(--card-width)); height: auto; } .cardList__btn { --btn-size: 35px; width: var(--btn-size); height: var(--btn-size); position: absolute; top: 50%; transform: translateY(-50%); z-index: 100; } .cardList__btn.btn--left { left: -5%; } .cardList__btn.btn--right { right: -5%; } .cardList__btn .icon { width: 100%; height: 100%; } .cardList__btn .icon svg { width: 100%; height: 100%; } .cardList .cards__wrapper { position: relative; width: 100%; height: 100%; perspective: 1000px; } .card { --card-translateY-offset: 100vh; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) translateX(var(--card-translateX-offset)) translateY(var(--card-translateY-offset)) rotateY(var(--card-rotation-offset)) scale(var(--card-scale-offset)); display: inline-block; width: var(--card-width); height: var(--card-height); transition: transform var(--card-transition-duration) var(--card-transition-easing); user-select: none; } .card::before { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: #000; z-index: 1; transition: opacity var(--card-transition-duration) var(--card-transition-easing); opacity: calc(1 - var(--opacity)); } /*Andev Web*/ .card__image { position: relative; width: 100%; height: 100%; } .card__image img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; } .card.current--card { --current-card-rotation-offset: 0; --card-translateX-offset: 0; --card-rotation-offset: var(--current-card-rotation-offset); --card-scale-offset: 1.2; --opacity: 0.8; } .card.previous--card { --card-translateX-offset: calc(-1 * var(--card-width) * 1.1); --card-rotation-offset: 25deg; } .card.next--card { --card-translateX-offset: calc(var(--card-width) * 1.1); --card-rotation-offset: -25deg; } .card.previous--card, .card.next--card { --card-scale-offset: 0.9; --opacity: 0.4; } .infoList { position: absolute; width: calc(3 * var(--card-width)); height: var(--card-height); pointer-events: none; } .infoList .info__wrapper { position: relative; width: 100%; height: 100%; display: flex; justify-content: flex-start; align-items: flex-end; perspective: 1000px; transform-style: preserve-3d; } .info { margin-bottom: calc(var(--card-height) / 8); margin-left: calc(var(--card-width) / 1.5); transform: translateZ(2rem); transition: transform var(--card-transition-duration) var(--card-transition-easing); } .info .text { position: relative; font-family: "Montserrat"; font-size: calc(var(--card-width) * var(--text-size-offset, 0.2)); white-space: nowrap; color: #fff; width: fit-content; } .info .name, .info .location { text-transform: uppercase; } .info .location { font-weight: 800; } .info .location { --mg-left: 40px; --text-size-offset: 0.12; font-weight: 600; margin-left: var(--mg-left); margin-bottom: calc(var(--mg-left) / 2); padding-bottom: 0.8rem; } .info .location::before, .info .location::after { content: ""; position: absolute; background: #fff; left: 0%; transform: translate(calc(-1 * var(--mg-left)), -50%); } .info .location::before { top: 50%; width: 20px; height: 5px; } .info .location::after { bottom: 0; width: 60px; height: 2px; } .info .description { --text-size-offset: 0.065; font-weight: 500; } .info.current--info { opacity: 1; display: block; } .info.previous--info, .info.next--info { opacity: 0; display: none; } .loading__wrapper { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; background: #000; z-index: 200; } .loading__wrapper .loader--text { color: #fff; font-family: "Montserrat"; font-weight: 500; margin-bottom: 1.4rem; } .loading__wrapper .loader { position: relative; width: 200px; height: 2px; background: rgba(255, 255, 255, 0.25); } .loading__wrapper .loader span { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: red; transform: scaleX(0); transform-origin: left; } @media only screen and (min-width: 800px) { :root { --card-width: 250px; --card-height: 400px; } } .support { position: absolute; right: 10px; bottom: 10px; padding: 10px; display: flex; } .support a { margin: 0 10px; color: #fff; font-size: 1.8rem; backface-visibility: hidden; transition: all 150ms ease; } .support a:hover { transform: scale(1.1); }
JS
Copy
console.clear(); const { gsap, imagesLoaded } = window; const buttons = { prev: document.querySelector(".btn--left"), next: document.querySelector(".btn--right"), }; const cardsContainerEl = document.querySelector(".cards__wrapper"); const appBgContainerEl = document.querySelector(".app__bg"); const cardInfosContainerEl = document.querySelector(".info__wrapper"); buttons.next.addEventListener("click", () => swapCards("right")); buttons.prev.addEventListener("click", () => swapCards("left")); function swapCards(direction) { const currentCardEl = cardsContainerEl.querySelector(".current--card"); const previousCardEl = cardsContainerEl.querySelector(".previous--card"); const nextCardEl = cardsContainerEl.querySelector(".next--card"); const currentBgImageEl = appBgContainerEl.querySelector(".current--image"); const previousBgImageEl = appBgContainerEl.querySelector(".previous--image"); const nextBgImageEl = appBgContainerEl.querySelector(".next--image"); changeInfo(direction); swapCardsClass(); removeCardEvents(currentCardEl); function swapCardsClass() { currentCardEl.classList.remove("current--card"); previousCardEl.classList.remove("previous--card"); nextCardEl.classList.remove("next--card"); currentBgImageEl.classList.remove("current--image"); previousBgImageEl.classList.remove("previous--image"); nextBgImageEl.classList.remove("next--image"); currentCardEl.style.zIndex = "50"; currentBgImageEl.style.zIndex = "-2"; if (direction === "right") { previousCardEl.style.zIndex = "20"; nextCardEl.style.zIndex = "30"; nextBgImageEl.style.zIndex = "-1"; currentCardEl.classList.add("previous--card"); previousCardEl.classList.add("next--card"); nextCardEl.classList.add("current--card"); currentBgImageEl.classList.add("previous--image"); previousBgImageEl.classList.add("next--image"); nextBgImageEl.classList.add("current--image"); } else if (direction === "left") { previousCardEl.style.zIndex = "30"; nextCardEl.style.zIndex = "20"; previousBgImageEl.style.zIndex = "-1"; currentCardEl.classList.add("next--card"); previousCardEl.classList.add("current--card"); nextCardEl.classList.add("previous--card"); currentBgImageEl.classList.add("next--image"); previousBgImageEl.classList.add("current--image"); nextBgImageEl.classList.add("previous--image"); } } } function changeInfo(direction) { let currentInfoEl = cardInfosContainerEl.querySelector(".current--info"); let previousInfoEl = cardInfosContainerEl.querySelector(".previous--info"); let nextInfoEl = cardInfosContainerEl.querySelector(".next--info"); gsap.timeline() .to([buttons.prev, buttons.next], { duration: 0.2, opacity: 0.5, pointerEvents: "none", }) .to( currentInfoEl.querySelectorAll(".text"), { duration: 0.4, stagger: 0.1, translateY: "-120px", opacity: 0, }, "-=" ) .call(() => { swapInfosClass(direction); }) .call(() => initCardEvents()) .fromTo( direction === "right" ? nextInfoEl.querySelectorAll(".text") : previousInfoEl.querySelectorAll(".text"), { opacity: 0, translateY: "40px", }, { duration: 0.4, stagger: 0.1, translateY: "0px", opacity: 1, } ) .to([buttons.prev, buttons.next], { duration: 0.2, opacity: 1, pointerEvents: "all", }); function swapInfosClass() { currentInfoEl.classList.remove("current--info"); previousInfoEl.classList.remove("previous--info"); nextInfoEl.classList.remove("next--info"); if (direction === "right") { currentInfoEl.classList.add("previous--info"); nextInfoEl.classList.add("current--info"); previousInfoEl.classList.add("next--info"); } else if (direction === "left") { currentInfoEl.classList.add("next--info"); nextInfoEl.classList.add("previous--info"); previousInfoEl.classList.add("current--info"); } } } function updateCard(e) { const card = e.currentTarget; const box = card.getBoundingClientRect(); const centerPosition = { x: box.left + box.width / 2, y: box.top + box.height / 2, }; let angle = Math.atan2(e.pageX - centerPosition.x, 0) * (35 / Math.PI); gsap.set(card, { "--current-card-rotation-offset": `${angle}deg`, }); const currentInfoEl = cardInfosContainerEl.querySelector(".current--info"); gsap.set(currentInfoEl, { rotateY: `${angle}deg`, }); } function resetCardTransforms(e) { const card = e.currentTarget; const currentInfoEl = cardInfosContainerEl.querySelector(".current--info"); gsap.set(card, { "--current-card-rotation-offset": 0, }); gsap.set(currentInfoEl, { rotateY: 0, }); } function initCardEvents() { const currentCardEl = cardsContainerEl.querySelector(".current--card"); currentCardEl.addEventListener("pointermove", updateCard); currentCardEl.addEventListener("pointerout", (e) => { resetCardTransforms(e); }); } initCardEvents(); function removeCardEvents(card) { card.removeEventListener("pointermove", updateCard); } function init() { let tl = gsap.timeline(); tl.to(cardsContainerEl.children, { delay: 0.15, duration: 0.5, stagger: { ease: "power4.inOut", from: "right", amount: 0.1, }, "--card-translateY-offset": "0%", }) .to(cardInfosContainerEl.querySelector(".current--info").querySelectorAll(".text"), { delay: 0.5, duration: 0.4, stagger: 0.1, opacity: 1, translateY: 0, }) .to( [buttons.prev, buttons.next], { duration: 0.4, opacity: 1, pointerEvents: "all", }, "-=0.4" ); } const waitForImages = () => { const images = [...document.querySelectorAll("img")]; const totalImages = images.length; let loadedImages = 0; const loaderEl = document.querySelector(".loader span"); gsap.set(cardsContainerEl.children, { "--card-translateY-offset": "100vh", }); gsap.set(cardInfosContainerEl.querySelector(".current--info").querySelectorAll(".text"), { translateY: "40px", opacity: 0, }); gsap.set([buttons.prev, buttons.next], { pointerEvents: "none", opacity: "0", }); images.forEach((image) => { imagesLoaded(image, (instance) => { if (instance.isComplete) { loadedImages++; let loadProgress = loadedImages / totalImages; gsap.to(loaderEl, { duration: 1, scaleX: loadProgress, backgroundColor: `hsl(${loadProgress * 120}, 100%, 50%`, }); if (totalImages == loadedImages) { gsap.timeline() .to(".loading__wrapper", { duration: 0.8, opacity: 0, pointerEvents: "none", }) .call(() => init()); } } }); }); }; waitForImages();