Apply smooth image transition on click

by musebe

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Cloudinary View Transition</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
  <main>
    <h1>Smooth Image Switch with Cloudinary + View Transition API</h1>
    <img id="main-image"
         src="https://res.cloudinary.com/demo/image/upload/w_600/sample.jpg"
         alt="Dynamic View Image"
         view-transition-name="cloudinary-image" />
    <button id="next-btn">Next Image</button>
  </main>

  <script src="script.js"></script>
</body>
</html>

CSS

body {
  font-family: system-ui, sans-serif;
  text-align: center;
  padding: 2rem;
  background: #f9f9f9;
}

main {
  max-width: 700px;
  margin: auto;
}

img {
  width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  margin-bottom: 1rem;
  view-transition-name: cloudinary-image;
}

button {
  padding: 0.6rem 1.2rem;
  font-size: 1rem;
  background: #0070f3;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.3s ease;
}

button:hover {
  background: #0051a3;
}

/* View Transition API pseudo-elements */

::view-transition-old(cloudinary-image) {
  animation: fadeOut 0.4s ease forwards;
}

::view-transition-new(cloudinary-image) {
  animation: fadeIn 0.4s ease forwards;
}

@keyframes fadeIn {
  from { opacity: 0; transform: scale(0.98); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes fadeOut {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.02); }
}

JavaScript

const images = [
  "sample.jpg",
  "dog.jpg",
  "balloons.jpg"
];

let currentIndex = 0;

const imgElement = document.getElementById("main-image");
const button = document.getElementById("next-btn");

function updateImage() {
  currentIndex = (currentIndex + 1) % images.length;
  const newSrc = `https://res.cloudinary.com/demo/image/upload/w_600/${images[currentIndex]}`;
  imgElement.src = newSrc;
}

button.addEventListener("click", () => {
  if (document.startViewTransition) {
    const transition = document.startViewTransition(() => {
      updateImage();
    });

    transition.ready.then(() => {
      console.log("Transition ready");
    });

    transition.updateCallbackDone.then(() => {
      console.log("DOM updated");
    });

    transition.finished.then(() => {
      console.log("Transition finished");
    });
  } else {
    updateImage(); // fallback
  }
});