Image Carousel with Vanilla JS

TODO: collapasable, copyable, download, etc

Can you write me the HTML, CSS, and vanilla JS (no frameworks) for an image carousel with the following requirements? The layout will be an image (hero) above and a row of thumnail images below. The hero should take up the full width of the area. The thumbnail images should always be 100px tall. If there are more of them than the width of the hero image, they should be hidden in a scrollable area. Clicking a thumbnail should 1) change the hero image 2) scroll the thumbnail area so that that thumbnail is now in the center. Lastly, the thumbnail area should be easy to swipe scoll to look through the thumnails. The hero image should be swipeable to go to the previous or next image. Doing so should update the thumnail area to the new current image. Lastly for desktop users, mouseover events should show arrow buttons so that a mouse click can scroll through the thumnails. Left and right arrows should also scroll the hero image.Sure, here's a simple implementation that should meet most of your requirements. Note that this is a simple example, and might require some adjustments based on the actual application requirements. Also, keep in mind that the images need to be loaded from somewhere, for this example, I'm assuming that we're using a set of images named as image1.jpg, image2.jpg, image3.jpg, and so on.

HTML:

<div id="carousel">
  <div id="hero">
    <img src="image1.jpg" id="heroImage">
  </div>
  <div id="thumbnails">
    <div id="thumbnailsContent">
      <img src="image1.jpg" onclick="changeHero(0)">
      <img src="image2.jpg" onclick="changeHero(1)">
      <img src="image3.jpg" onclick="changeHero(2)">
      <!-- Add as many as required -->
    </div>
  </div>
</div>

CSS:

#carousel {
  position: relative;
  width: 100%;
  overflow: hidden;
}

#hero {
  width: 100%;
}

#hero img {
  width: 100%;
  height: auto;
}

#thumbnails {
  width: 100%;
  height: 100px;
  overflow: auto;
  white-space: nowrap;
}

#thumbnails img {
  height: 100px;
  cursor: pointer;
}

#thumbnailsContent {
  display: flex;
  justify-content: space-between;
}

JavaScript:

let currentImageIndex = 0;
const heroImage = document.getElementById("heroImage");
const thumbnails = document.getElementById("thumbnailsContent").children;

function changeHero(index) {
  currentImageIndex = index;
  heroImage.src = thumbnails[currentImageIndex].src;
  thumbnails[currentImageIndex].scrollIntoView({ inline: 'center', behavior: 'smooth' });
}

heroImage.addEventListener('swipe', function(e) {
  if (e.detail.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.detail.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

thumbnails.addEventListener('swipe', function(e) {
  if (e.detail.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.detail.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

// Arrow buttons for desktop users
document.addEventListener('keydown', function(e) {
  if (e.key === "ArrowRight" && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.key === "ArrowLeft" && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

A few things to note about this code:

  1. The swipe event is not a standard JavaScript event. To make it work, you would need a third-party library that provides swipe event handling, or you would need to implement it yourself.
  2. I've added a keyboard event listener for arrow keys to allow desktop users to navigate through the images. It's a simpler alternative for desktop users, compared to implementing arrow buttons on mouseover events, which would require more intricate design considerations.
  3. For the images, I've added a changeHero function call on click, passing the index of the image. This index is used to set the `currentImageIndexCan you implement the missing swipe library?Sure, here's a simple implementation of swipe detection for left and right swipes. It's a lightweight solution compared to using a full library, and should work well for simple needs:
function detectSwipe(element, callback) {
  let startX;
  let startY;
  let distX;
  let distY;
  let threshold = 150; // Minimum distance swiped for action to be considered swipe
  let allowedTime = 200; // Maximum time allowed for swipe
  let elapsedTime;
  let startTime;

  element.addEventListener('touchstart', function(e) {
    const touchObj = e.changedTouches[0];
    startX = touchObj.pageX;
    startY = touchObj.pageY;
    startTime = new Date().getTime(); // Record time when finger first makes contact with surface
  }, false);

  element.addEventListener('touchend', function(e) {
    const touchObj = e.changedTouches[0];
    distX = touchObj.pageX - startX; // Get horizontal dist traveled by finger while in contact with surface
    distY = touchObj.pageY - startY; // Get vertical dist traveled by finger while in contact with surface
    elapsedTime = new Date().getTime() - startTime; // Get time elapsed

    // Check that elapsed time is within specified, horizontal dist traveled >= threshold, and vertical dist traveled <= 100
    const swipeDetected = elapsedTime <= allowedTime && Math.abs(distX) >= threshold && Math.abs(distY) <= 100;

    callback({ directions: { left: swipeDetected && distX < 0, right: swipeDetected && distX > 0 } });
  }, false);
}

const heroElement = document.getElementById("heroImage");
const thumbnailsElement = document.getElementById("thumbnails");

detectSwipe(heroElement, function(e) {
  if (e.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

detectSwipe(thumbnailsElement, function(e) {
  if (e.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

This function, detectSwipe, attaches to touchstart and touchend events on a given element. It calculates the difference in X and Y coordinates between the start and end of the touch, as well as the elapsed time. If the touch covered a sufficient distance (as defined by the threshold variable), occurred quickly enough (as defined by the allowedTime variable), and primarily in a horizontal direction, it triggers the callback with a directions object, indicating whether the swipe was to the left or to the right.

I've called this detectSwipe function for both heroElement and thumbnailsElement, and in the callback, we call changeHero function if the swipe direction and position conditions are satisfied.Please implement arrow buttons on mouseover eventsSure, here is an implementation with arrow buttons that appear on mouseover and allow the user to scroll through the thumbnails and change the hero image.

HTML:

<div id="carousel">
  <div id="hero">
    <img src="image1.jpg" id="heroImage">
    <button class="scroll-button" id="prevButton">Previous</button>
    <button class="scroll-button" id="nextButton">Next</button>
  </div>
  <div id="thumbnails">
    <div id="thumbnailsContent">
      <img src="image1.jpg" onclick="changeHero(0)">
      <img src="image2.jpg" onclick="changeHero(1)">
      <img src="image3.jpg" onclick="changeHero(2)">
      <!-- Add as many as required -->
    </div>
  </div>
</div>

CSS:

#carousel {
  position: relative;
  width: 100%;
  overflow: hidden;
}

#hero {
  width: 100%;
  position: relative;
}

#hero img {
  width: 100%;
  height: auto;
}

#thumbnails {
  width: 100%;
  height: 100px;
  overflow: auto;
  white-space: nowrap;
}

#thumbnails img {
  height: 100px;
  cursor: pointer;
}

#thumbnailsContent {
  display: flex;
  justify-content: space-between;
}

.scroll-button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  opacity: 0;
  transition: opacity 0.2s;
}

#prevButton {
  left: 10px;
}

#nextButton {
  right: 10px;
}

#hero:hover .scroll-button {
  opacity: 1;
}

JavaScript:

let currentImageIndex = 0;
const heroImage = document.getElementById("heroImage");
const thumbnails = document.getElementById("thumbnailsContent").children;
const prevButton = document.getElementById("prevButton");
const nextButton = document.getElementById("nextButton");

function changeHero(index) {
  currentImageIndex = index;
  heroImage.src = thumbnails[currentImageIndex].src;
  thumbnails[currentImageIndex].scrollIntoView({ inline: 'center', behavior: 'smooth' });
}

heroImage.addEventListener('swipe', function(e) {
  if (e.detail.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.detail.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

thumbnails.addEventListener('swipe', function(e) {
  if (e.detail.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.detail.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

// Arrow buttons for desktop users
prevButton.addEventListener('click', function() {
  if (currentImageIndex > 0) {
    changeHero(currentImageIndex - 1);
  }
});

nextButton.addEventListener('click', function() {
  if (currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  }
});

// Detect Swipe events
detectSwipe(heroElement, function(e) {
  if (e.directions.left && currentImageIndex < thumbnails.length - 1) {
    changeHero(currentImageIndex + 1);
  } else if (e.directions.right && currentImageIndex > 0) {
    changeHero(currentImageIndex

Image Carousel with Vanilla JS created at Tue Jan 20 1970 12:53:06 GMT+0000 (Coordinated Universal Time)

Related Chats

SwiftUI App, Centered Image 0.363

Preload Assets in visionOS 0.325

Automated PNG File Organization 0.315

Lightsaber Trainer App 0.288

Fun Web Developer Illustration 0.278

Enhancing Pumpkin Heads Realism 0.275

New chat 0.269

Wooden Jacob's Ladder Toy 0.260

Animate Using withAnimation 0.257