Vissza a webes elemekhez
Galériák és képek

Lebegő képkártyák, mélységi hatással

Élő előnézet – a komponens teljes méretben.

Élő előnézet
HTML · CSS · JS
HTML
<section class="we-float-gallery" data-float-gallery>
  <header class="we-float-gallery__header">
    <span class="we-float-gallery__eyebrow">KÉPEK · TÖRTÉNETEK · ÉLMÉNYEK</span>
    <h2>Más nézőpontból<span>.</span></h2>
    <p>Minden táj egy új világ. Lépj közelebb, és fedezd fel!</p>
  </header>

  <div class="we-float-gallery__scene" data-float-scene>
    <div class="we-float-gallery__glow"></div>
    <div class="we-float-gallery__orbit we-float-gallery__orbit--one"></div>
    <div class="we-float-gallery__orbit we-float-gallery__orbit--two"></div>

    <button class="we-float-gallery__card we-float-gallery__card--one"
            type="button"
            data-float-photo
            data-title="A hegyek nyugalma"
            data-description="Ahol a csend messzebbre ér, mint a tekintet."
            aria-label="A hegyek nyugalma – kép megnyitása">
      <img src="https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=1000&q=85"
           alt="Hegyek és tó"
           loading="lazy">
      <span class="we-float-gallery__card-label">
        <small>01 / TERMÉSZET</small>
        <strong>A hegyek nyugalma</strong>
      </span>
    </button>

    <button class="we-float-gallery__card we-float-gallery__card--two"
            type="button"
            data-float-photo
            data-title="Zöld menedék"
            data-description="Egy ösvény, amely elvezet a mindennapoktól."
            aria-label="Zöld menedék – kép megnyitása">
      <img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=800&q=85"
           alt="Napfényes erdő"
           loading="lazy">
      <span class="we-float-gallery__card-label">
        <small>02 / ERDŐ</small>
        <strong>Zöld menedék</strong>
      </span>
    </button>

    <button class="we-float-gallery__card we-float-gallery__card--three"
            type="button"
            data-float-photo
            data-title="Aranyóra"
            data-description="Az a néhány perc, amikor a táj aranyba öltözik."
            aria-label="Aranyóra – kép megnyitása">
      <img src="https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?auto=format&fit=crop&w=800&q=85"
           alt="Aranyló fények a táj felett"
           loading="lazy">
      <span class="we-float-gallery__card-label">
        <small>03 / FÉNYEK</small>
        <strong>Aranyóra</strong>
      </span>
    </button>

    <button class="we-float-gallery__card we-float-gallery__card--four"
            type="button"
            data-float-photo
            data-title="A végtelen kék"
            data-description="Hullámok, fények és a szabadság érzése."
            aria-label="A végtelen kék – kép megnyitása">
      <img src="https://images.unsplash.com/photo-1518837695005-2083093ee35b?auto=format&fit=crop&w=800&q=85"
           alt="Tenger hullámai"
           loading="lazy">
      <span class="we-float-gallery__card-label">
        <small>04 / TENGER</small>
        <strong>A végtelen kék</strong>
      </span>
    </button>

    <span class="we-float-gallery__index" aria-hidden="true">01 — 04</span>
    <span class="we-float-gallery__hint">MOZDÍTSD AZ EGERET</span>
  </div>

  <footer class="we-float-gallery__footer">
    <span>FELFEDEZÉSRE VÁRÓ VILÁGOK</span>
    <span class="we-float-gallery__footer-line"></span>
    <span>KATTINTS EGY KÉPRE</span>
  </footer>
</section>
CSS
.we-float-gallery {
  --float-bg: #101713;
  --float-text: #f4f0e6;
  --float-muted: #aab6aa;
  --float-gold: #c9aa70;

  position: relative;
  width: 100%;
  padding: clamp(32px, 5vw, 64px) 20px 30px;
  overflow: hidden;
  box-sizing: border-box;
  color: var(--float-text);
  background:
    radial-gradient(ellipse at 50% 52%, #24382c 0%, transparent 55%),
    var(--float-bg);
  font-family: Georgia, "Times New Roman", serif;
}

.we-float-gallery *,
.we-float-gallery *::before,
.we-float-gallery *::after {
  box-sizing: border-box;
}

.we-float-gallery__header {
  position: relative;
  z-index: 2;
  max-width: 760px;
  margin: 0 auto;
  text-align: center;
}

.we-float-gallery__eyebrow {
  color: var(--float-gold);
  font-family: Arial, sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .24em;
}

.we-float-gallery__header h2 {
  margin: 15px 0 12px;
  font-size: clamp(32px, 5vw, 54px);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -.035em;
}

.we-float-gallery__header h2 span {
  color: var(--float-gold);
}

.we-float-gallery__header p {
  margin: 0;
  color: var(--float-muted);
  font-family: Arial, sans-serif;
  font-size: 14px;
  line-height: 1.7;
}

.we-float-gallery__scene {
  position: relative;
  width: 100%;
  max-width: 1000px;
  height: clamp(430px, 57vw, 620px);
  margin: 10px auto 0;
  perspective: 1200px;
  transform-style: preserve-3d;
  isolation: isolate;
}

.we-float-gallery__glow {
  position: absolute;
  top: 18%;
  left: 25%;
  width: 50%;
  height: 65%;
  border-radius: 50%;
  background: rgb(150 174 125 / 12%);
  filter: blur(65px);
  pointer-events: none;
}

.we-float-gallery__orbit {
  position: absolute;
  top: 50%;
  left: 50%;
  border: 1px solid rgb(201 170 112 / 12%);
  border-radius: 50%;
  transform: translate(-50%, -50%) rotate(-18deg);
  pointer-events: none;
}

.we-float-gallery__orbit--one {
  width: 78%;
  height: 66%;
}

.we-float-gallery__orbit--two {
  width: 60%;
  height: 86%;
  transform: translate(-50%, -50%) rotate(24deg);
}

.we-float-gallery__card {
  position: absolute;
  display: block;
  width: 34%;
  height: 55%;
  padding: 0;
  overflow: hidden;
  border: 1px solid rgb(255 255 255 / 20%);
  border-radius: 3px;
  background: #28362d;
  color: #fff;
  text-align: left;
  cursor: pointer;
  transform-style: preserve-3d;
  box-shadow:
    0 24px 60px rgb(0 0 0 / 30%),
    0 2px 5px rgb(0 0 0 / 15%);
  transition:
    left 650ms cubic-bezier(.2, .7, .2, 1),
    top 650ms cubic-bezier(.2, .7, .2, 1),
    transform 650ms cubic-bezier(.2, .7, .2, 1),
    box-shadow 350ms ease;
  will-change: transform;
}

.we-float-gallery__card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 800ms cubic-bezier(.2, .7, .2, 1);
}

.we-float-gallery__card::after {
  position: absolute;
  inset: 0;
  content: "";
  background: linear-gradient(
    180deg,
    transparent 40%,
    rgb(8 15 10 / 80%) 100%
  );
  pointer-events: none;
}

.we-float-gallery__card:hover {
  z-index: 10 !important;
  box-shadow:
    0 35px 80px rgb(0 0 0 / 48%),
    0 0 0 1px rgb(201 170 112 / 65%);
}

.we-float-gallery__card:hover img {
  transform: scale(1.07);
}

.we-float-gallery__card:focus-visible {
  outline: 2px solid var(--float-gold);
  outline-offset: 5px;
  z-index: 11 !important;
}

.we-float-gallery__card-label {
  position: absolute;
  z-index: 2;
  right: 15px;
  bottom: 16px;
  left: 15px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  pointer-events: none;
}

.we-float-gallery__card-label small {
  color: #e7cb93;
  font-family: Arial, sans-serif;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .17em;
}

.we-float-gallery__card-label strong {
  font-size: clamp(15px, 1.8vw, 22px);
  font-weight: 400;
  line-height: 1.2;
}

.we-float-gallery__card--one {
  top: 17%;
  left: 14%;
  z-index: 4;
  transform: rotate(-8deg) translateZ(65px);
}

.we-float-gallery__card--two {
  top: 9%;
  left: 47%;
  z-index: 3;
  transform: rotate(7deg) translateZ(15px);
}

.we-float-gallery__card--three {
  top: 40%;
  left: 37%;
  z-index: 5;
  transform: rotate(-3deg) translateZ(105px);
}

.we-float-gallery__card--four {
  top: 29%;
  left: 63%;
  z-index: 2;
  transform: rotate(9deg) translateZ(-25px);
}

.we-float-gallery__index {
  position: absolute;
  right: 2%;
  bottom: 8%;
  color: rgb(201 170 112 / 60%);
  font-family: Arial, sans-serif;
  font-size: 12px;
  letter-spacing: .18em;
}

.we-float-gallery__hint {
  position: absolute;
  bottom: 8%;
  left: 2%;
  color: var(--float-muted);
  font-family: Arial, sans-serif;
  font-size: 9px;
  letter-spacing: .2em;
}

.we-float-gallery__footer {
  display: flex;
  align-items: center;
  gap: 16px;
  max-width: 1000px;
  margin: 0 auto;
  color: var(--float-muted);
  font-family: Arial, sans-serif;
  font-size: 9px;
  letter-spacing: .12em;
}

.we-float-gallery__footer-line {
  flex: 1;
  height: 1px;
  background: rgb(201 170 112 / 35%);
}

/* Nagyított képnézet */

.we-float-gallery__viewer {
  position: fixed;
  z-index: 99999;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 30px;
  background: rgb(5 9 7 / 95%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 250ms ease, visibility 250ms ease;
}

.we-float-gallery__viewer.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.we-float-gallery__viewer-inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(100%, 1100px);
  height: 100%;
}

.we-float-gallery__viewer-image {
  display: block;
  max-width: 100%;
  max-height: 82vh;
  object-fit: contain;
  box-shadow: 0 25px 80px rgb(0 0 0 / 40%);
}

.we-float-gallery__viewer-close,
.we-float-gallery__viewer-arrow {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid rgb(255 255 255 / 30%);
  border-radius: 50%;
  background: rgb(15 22 17 / 65%);
  color: #fff;
  font-size: 25px;
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease;
}

.we-float-gallery__viewer-close:hover,
.we-float-gallery__viewer-arrow:hover {
  background: var(--float-gold);
  color: #101713;
}

.we-float-gallery__viewer-close {
  top: 20px;
  right: 20px;
}

.we-float-gallery__viewer-arrow {
  top: 50%;
  transform: translateY(-50%);
}

.we-float-gallery__viewer-prev {
  left: 12px;
}

.we-float-gallery__viewer-next {
  right: 12px;
}

.we-float-gallery__viewer-info {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  justify-content: space-between;
  gap: 15px;
  padding: 18px 5px;
  color: var(--float-muted);
  font-family: Arial, sans-serif;
  font-size: 12px;
}

.we-float-gallery__viewer-title {
  color: var(--float-text);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 22px;
}

@media (max-width: 650px) {
  .we-float-gallery {
    padding-right: 12px;
    padding-left: 12px;
  }

  .we-float-gallery__scene {
    height: 450px;
    margin-top: 5px;
  }

  .we-float-gallery__card {
    width: 43%;
    height: 47%;
  }

  .we-float-gallery__card--one {
    top: 12%;
    left: 5%;
  }

  .we-float-gallery__card--two {
    top: 8%;
    left: 49%;
  }

  .we-float-gallery__card--three {
    top: 42%;
    left: 20%;
  }

  .we-float-gallery__card--four {
    top: 37%;
    left: 54%;
  }

  .we-float-gallery__card-label {
    right: 9px;
    bottom: 10px;
    left: 9px;
    gap: 5px;
  }

  .we-float-gallery__card-label small {
    font-size: 8px;
  }

  .we-float-gallery__card-label strong {
    font-size: 15px;
  }

  .we-float-gallery__hint {
    bottom: 1%;
    left: 3%;
    font-size: 8px;
  }

  .we-float-gallery__index {
    right: 3%;
    bottom: 1%;
    font-size: 10px;
  }

  .we-float-gallery__footer {
    gap: 8px;
    font-size: 8px;
  }

  .we-float-gallery__viewer {
    padding: 12px;
  }

  .we-float-gallery__viewer-image {
    max-height: 72vh;
  }

  .we-float-gallery__viewer-close {
    top: 10px;
    right: 10px;
  }

  .we-float-gallery__viewer-arrow {
    top: auto;
    bottom: 55px;
    width: 40px;
    height: 40px;
  }

  .we-float-gallery__viewer-prev {
    left: 5px;
  }

  .we-float-gallery__viewer-next {
    right: 5px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .we-float-gallery__card,
  .we-float-gallery__card img,
  .we-float-gallery__viewer {
    transition-duration: .01ms !important;
  }
}
JavaScript
document.querySelectorAll("[data-float-gallery]").forEach((gallery) => {
  if (gallery.dataset.floatInitialized === "true") {
    return;
  }

  const scene = gallery.querySelector("[data-float-scene]");
  const cards = Array.from(
    gallery.querySelectorAll("[data-float-photo]")
  );

  if (!scene || cards.length === 0) {
    console.warn("Lebegő galéria: hiányzó HTML-elemek.", gallery);
    return;
  }

  gallery.dataset.floatInitialized = "true";

  const reduceMotion = window.matchMedia(
    "(prefers-reduced-motion: reduce)"
  ).matches;

  // Finom mélységi mozgás egérrel.
  if (!reduceMotion) {
    scene.addEventListener("pointermove", (event) => {
      if (event.pointerType === "touch") {
        return;
      }

      const rect = scene.getBoundingClientRect();

      const x = (event.clientX - rect.left) / rect.width;
      const y = (event.clientY - rect.top) / rect.height;

      const rotateY = (x - 0.5) * 7;
      const rotateX = (0.5 - y) * 5;

      scene.style.transform =
        `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
    });

    scene.addEventListener("pointerleave", () => {
      scene.style.transform = "rotateX(0deg) rotateY(0deg)";
    });

    cards.forEach((card, index) => {
      card.addEventListener("pointermove", (event) => {
        if (event.pointerType === "touch") {
          return;
        }

        const rect = card.getBoundingClientRect();
        const x = (event.clientX - rect.left) / rect.width;
        const y = (event.clientY - rect.top) / rect.height;

        const tiltY = (x - 0.5) * 5;
        const tiltX = (0.5 - y) * 5;

        card.style.setProperty("--float-tilt-x", `${tiltX}deg`);
        card.style.setProperty("--float-tilt-y", `${tiltY}deg`);
      });

      card.addEventListener("pointerleave", () => {
        card.style.removeProperty("--float-tilt-x");
        card.style.removeProperty("--float-tilt-y");
      });
    });
  }

  // Képnézegető létrehozása.
  const viewer = document.createElement("div");
  viewer.className = "we-float-gallery__viewer";
  viewer.setAttribute("role", "dialog");
  viewer.setAttribute("aria-modal", "true");
  viewer.setAttribute("aria-label", "Képnagyító");

  viewer.innerHTML = `
    <button type="button"
            class="we-float-gallery__viewer-close"
            aria-label="Bezárás">×</button>

    <div class="we-float-gallery__viewer-inner">
      <button type="button"
              class="we-float-gallery__viewer-arrow we-float-gallery__viewer-prev"
              aria-label="Előző kép">‹</button>

      <img class="we-float-gallery__viewer-image" src="" alt="">

      <button type="button"
              class="we-float-gallery__viewer-arrow we-float-gallery__viewer-next"
              aria-label="Következő kép">›</button>

      <div class="we-float-gallery__viewer-info">
        <span class="we-float-gallery__viewer-title"></span>
        <span class="we-float-gallery__viewer-count"></span>
      </div>
    </div>
  `;

  document.body.appendChild(viewer);

  const image = viewer.querySelector(
    ".we-float-gallery__viewer-image"
  );
  const title = viewer.querySelector(
    ".we-float-gallery__viewer-title"
  );
  const count = viewer.querySelector(
    ".we-float-gallery__viewer-count"
  );
  const closeButton = viewer.querySelector(
    ".we-float-gallery__viewer-close"
  );
  const prevButton = viewer.querySelector(
    ".we-float-gallery__viewer-prev"
  );
  const nextButton = viewer.querySelector(
    ".we-float-gallery__viewer-next"
  );

  let currentIndex = 0;
  let previousFocus = null;

  function showImage(index) {
    currentIndex = (index + cards.length) % cards.length;

    const card = cards[currentIndex];
    const img = card.querySelector("img");

    image.src = img.currentSrc || img.src;
    image.alt = img.alt;
    title.textContent = card.dataset.title || img.alt;
    count.textContent =
      String(currentIndex + 1).padStart(2, "0") +
      " / " +
      String(cards.length).padStart(2, "0");
  }

  function openViewer(index) {
    previousFocus = document.activeElement;
    showImage(index);
    viewer.classList.add("is-open");
    document.body.style.overflow = "hidden";
    closeButton.focus();
  }

  function closeViewer() {
    viewer.classList.remove("is-open");
    document.body.style.overflow = "";

    if (previousFocus && typeof previousFocus.focus === "function") {
      previousFocus.focus();
    }
  }

  cards.forEach((card, index) => {
    card.addEventListener("click", () => openViewer(index));
  });

  closeButton.addEventListener("click", closeViewer);

  prevButton.addEventListener("click", () => {
    showImage(currentIndex - 1);
  });

  nextButton.addEventListener("click", () => {
    showImage(currentIndex + 1);
  });

  viewer.addEventListener("click", (event) => {
    if (event.target === viewer) {
      closeViewer();
    }
  });

  document.addEventListener("keydown", (event) => {
    if (!viewer.classList.contains("is-open")) {
      return;
    }

    if (event.key === "Escape") {
      event.preventDefault();
      closeViewer();
    } else if (event.key === "ArrowLeft") {
      event.preventDefault();
      showImage(currentIndex - 1);
    } else if (event.key === "ArrowRight") {
      event.preventDefault();
      showImage(currentIndex + 1);
    }
  });
});