<h2>Choose a character</h2>

<div class="gallery">
  <div class="gallery__large">
    <img src="https://assets.codepen.io/89905/matroshka-01.svg" alt="" title="" width="222" height="184" draggable="false">
  </div>

  <ul class="gallery__previews">
    <li><a href="#" class="active"><img src="https://assets.codepen.io/89905/matroshka-01.svg" alt="" title="" width="222" height="184" draggable="false"></a></li>
    <li><a href="#"><img src="https://assets.codepen.io/89905/matroshka-02.svg" alt="" title="" width="222" height="184" draggable="false"></a></li>
    <li><a href="#"><img src="https://assets.codepen.io/89905/matroshka-03.svg" alt="" title="" width="222" height="184" draggable="false"></a></li>
    <li><a href="#"><img src="https://assets.codepen.io/89905/matroshka-04.svg" alt="" title="" width="222" height="184" draggable="false"></a></li>
    <li><a href="#"><img src="https://assets.codepen.io/89905/matroshka-05.svg" alt="" title="" width="222" height="184" draggable="false"></a></li>
  </ul>
</div>

<p><label><input type="checkbox" id="skipViewTransitions"> Skip View Transitions</label></p>

<footer>
  <p>This demo is part of <a href="https://goo.gle/css-wrapped-2023" target="_top">#CSSWrapped2023</a></p>
</footer>
:root {
  view-transition-name: none;
}
::view-transition-group(*) {
  animation-duration: 0.5s;
}

.gallery {
  display: flex;
  flex-direction: column;
  gap: 0.5em;
  padding: 1em;
  border-radius: 0.5em;
  background: transparent
    linear-gradient(to bottom right, #ff00fa 0%, #0ff 100%);
  border: 0.25em solid #6300ff;
}

.gallery__large {
  & img {
    width: 100%;
    height: auto;
  }
}

.gallery__previews {
  display: flex;
  flex-direction: row;
  gap: 0.5em;

  & a {
    display: block;
    border-radius: 0.5em;
    background: #f4f4f4;
    color: #6300ff;
    border: 0.25em solid currentcolor;
    text-decoration: none;

    &:hover {
      color: #eb16c4;
    }

    &.active {
      color: #eb16c4;
      position: relative;

      &::before {
        content: "✔";
        position: absolute;
        inset: 0;
        font-size: 1.5em;
        display: grid;
        place-content: center;
      }

      & img {
        opacity: 0;
      }
    }
  }

  & img {
    max-width: 100%;
    height: auto;
    display: block;
  }
}

@layer reset {
  *,
  *:after,
  *:before {
    box-sizing: border-box;
  }

  * {
    margin: 0;
    padding: 0;
  }

  ul[class] {
    list-style: none;
  }

  label {
    cursor: pointer;
    max-width: max-content;
    user-select: none;
  }
}

@layer baselayout {
  html {
    margin: auto;
    line-height: 1.75;
    font-size: 1.25em;
    font-family: "Syne", sans-serif;
    min-height: 100%;
    background: white;
  }

  body {
    width: 100vmin;
    max-width: 75ch;
    margin: 0 auto;
    min-height: 100dvh;
    display: grid;
    gap: 2em;
    padding: 2em;
    place-content: safe center;
  }

  footer {
    font-style: italic;
  }

  h2 {
    font-family: "Anybody", sans-serif;

    text-decoration: underline;
    text-decoration-color: hsl(156deg 100% 50% / 50%);
    text-decoration-thickness: 0.2rem;
    text-decoration-style: wavy;
    text-decoration-skip-ink: none;
  }
}
const loadImage = (path) => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    // img.crossOrigin = "Anonymous";
    img.src = path;
    img.onload = () => {
      resolve(img);
    };
    img.onerror = (e) => {
      reject(e);
    };
  });
};

const handleClick = async (e) => {
  // Get references to the elements
  const $link = e.target.closest("a");
  const $gallery = e.target.closest(".gallery");

  // Bail out
  if (!$link || !$gallery) return false;

  // Remove active from previous active link
  $gallery.querySelector("a.active")?.classList.remove("active");

  // Add .active to clicked link
  $link.classList.add("active");

  // Update main image
  const $img = $link.querySelector("img");
  const url = $img.getAttribute("src");
  const img = await loadImage(url);
  $gallery.querySelector(".gallery__large img").setAttribute("src", url);
};

document.querySelectorAll(".gallery").forEach(($gallery) => {
  $gallery.querySelectorAll("a").forEach(($link) => {
    $link.addEventListener("click", async (e) => {
      // Prevent double clicks
      if ($link.classList.contains("active")) return false;

      e.preventDefault();
      e.stopPropagation();

      if (!document.startViewTransition || skipViewTransitions?.checked) {
        handleClick(e);
      } else {
        const $curImage = $gallery.querySelector("a.active img");
        const $newImage = $link.querySelector("img");
        const $largeImage = $gallery.querySelector(".gallery__large img");

        $newImage.style.viewTransitionName = "grow";
        $largeImage.style.viewTransitionName = "shrink";

        const t = document.startViewTransition(async () => {
          $largeImage.style.viewTransitionName = "grow";
          $curImage.style.viewTransitionName = "shrink";
          $newImage.style.viewTransitionName = "none";
          await handleClick(e);
        });

        await t.finished;
        $curImage.style.viewTransitionName = "none";
        $newImage.style.viewTransitionName = "none";
        $largeImage.style.viewTransitionName = "none";
      }
    });
  });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.