Search code examples
javascripthtmlcssslider

Building an image slider using HTML, CSS, and JS. How can I space out images to make one image appear in the viewport at a time?


I'm building an image slider using HTML, CSS, and JS. It's running on a timer using JS and can also be scrolled through using navigation arrows when hovered over. I feel like it's almost done, but right now all the images are stuck next to each other in the viewport and I can't figure out how to space out the images so that only one shows in the viewport at a time.

I don't want the images to be 100% width, or else they'll be too big and they'll look bad (the slider is for brand logos). I just want each logo to be spaced out and centered within the viewport so there's only one logo showing at a time.

HTML:

<div class="image-slider">
  <div class="image-slider__viewport">
    <div class="image-slider__container">
      <img src="https://i.postimg.cc/7hH1XLfK/checkout-logo-3-200x.webp" alt="eye logo" />
      <img src="https://i.postimg.cc/Yh7J36fV/reef.webp" alt="reef logo" />
      <img src="https://i.postimg.cc/F13M3Wc3/manduka.webp" alt="manduka logo" />
    </div>
    <div class="image-slider__navigation">
      <button class="image-slider__navigation--prev">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
        </svg>
      </button>
      <button class="image-slider__navigation--next">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
        </svg>
      </button>
    </div>
  </div>
</div>

CSS:

.image-slider {
  width: 60%;
  margin: auto;
}

.image-slider__viewport {
  overflow: hidden;
  position: relative;
  width: 100%;
  height: auto;
}

.image-slider__container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: auto;
  width: 100%;
  transition: all 0.4s ease-in-out;
}

.image-slider__container img {
  width: 100%;
  height: auto;
  max-width: 300px;
}

.image-slider__navigation {
  width: 100%;
  position: absolute;
  left: 0;
  top: 50%;
}

.image-slider__navigation--prev,
.image-slider__navigation--next {
  position: absolute;
  border: 0;
  outline: 0;
  width: 80px;
  height: 80px;
  cursor: pointer;
}

.image-slider__navigation--prev {
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

.image-slider__navigation--next {
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

JS:

const sliderViewport = document.querySelector(
  '.image-slider__viewport'
);

const sliderImageContainer = sliderViewport.querySelector(
  '.image-slider__container'
);

const numberOfSliderImages =
  sliderImageContainer.querySelectorAll('img').length;

let slideOffset = 0;

const moveSlides = offset => {
  const imageWidth =
    sliderImageContainer.querySelector('img').offsetWidth;
  sliderImageContainer.style.transform = `translateX(-${
    offset * imageWidth
  }px)`;
};

let timer;

const setTimer = () => {
  timer = setInterval(() => {
    slideOffset =
      slideOffset < numberOfSliderImages - 1 ?
      slideOffset + 1 :
      0;
    moveSlides(slideOffset);
  }, 2000);
};

setTimer();

sliderViewport.addEventListener('mouseenter', () => {
  clearInterval(timer);
});

sliderViewport.addEventListener('mouseleave', () => {
  setTimer();
});

const prevButton = document.querySelector(
  '.image-slider__navigation--prev'
);
const nextButton = document.querySelector(
  '.image-slider__navigation--next'
);

prevButton.addEventListener('click', () => {
  slideOffset =
    slideOffset > 1 ?
    slideOffset - 1 :
    numberOfSliderImages - 1;
  moveSlides(slideOffset);
});

nextButton.addEventListener('click', () => {
  slideOffset =
    slideOffset < numberOfSliderImages - 1 ?
    slideOffset + 1 :
    0;
  moveSlides(slideOffset);
});

Here's the code for the slider. My guess is that it's a styling issue, and the CSS needs to be adjusted to make the images spaced out enough to show one at a time in the viewport. But I've tried messing around with the CSS and haven't had any success. I'm not exactly sure which part of the CSS needs to be adjusted either.

Here's a link to the JSFiddle: https://jsfiddle.net/t07mxkvw/3/


Solution

  • in your code, image-slider__container has the same width of image-slider__viewport. It means that you display all 3 images at once, and just transforming the width of image. So images are stuck. I think this is what you want.

          const sliderViewport = document.querySelector(".image-slider__viewport")
    
          const sliderImageContainer = sliderViewport.querySelector(
            ".image-slider__container"
          )
    
          const numberOfSliderImages =
            sliderImageContainer.querySelectorAll("img").length
    
          let slideOffset = 0
    
          const moveSlides = (offset) => {
            const imageWidth =
              sliderImageContainer.querySelector("img").parentElement.offsetWidth
            sliderImageContainer.style.transform = `translateX(-${
              offset * imageWidth
            }px)`
          }
    
          let timer
    
          const setTimer = () => {
            timer = setInterval(() => {
              slideOffset =
                slideOffset < numberOfSliderImages - 1 ? slideOffset + 1 : 0
              moveSlides(slideOffset)
            }, 2000)
          }
    
          setTimer()
    
          sliderViewport.addEventListener("mouseenter", () => {
            clearInterval(timer)
          })
    
          sliderViewport.addEventListener("mouseleave", () => {
            setTimer()
          })
    
          const prevButton = document.querySelector(
            ".image-slider__navigation--prev"
          )
          const nextButton = document.querySelector(
            ".image-slider__navigation--next"
          )
    
          prevButton.addEventListener("click", () => {
            slideOffset =
              slideOffset > 1 ? slideOffset - 1 : numberOfSliderImages - 1
            moveSlides(slideOffset)
          })
    
          nextButton.addEventListener("click", () => {
            slideOffset =
              slideOffset < numberOfSliderImages - 1 ? slideOffset + 1 : 0
            moveSlides(slideOffset)
          })
    
          const setImageSliderContainerWidth = () => {
            const sliderViewport = document.querySelector(".image-slider__viewport")
            const sliderImageContainer = sliderViewport.querySelector(
              ".image-slider__container"
            )
            const imageCount =
              sliderImageContainer.getElementsByTagName("img").length
    
            sliderImageContainer.style.width = `${
              sliderViewport.offsetWidth * imageCount
            }px`
          }
    
          window.onload = setImageSliderContainerWidth
          window.onresize = setImageSliderContainerWidth
          .image-slider {
            width: 60%;
            margin: auto;
          }
    
          .image-slider__viewport {
            overflow: hidden;
            position: relative;
            width: 100%;
            height: auto;
          }
    
          .image-slider__container {
            display: flex;
            align-items: center;
            justify-content: center;
            height: auto;
            transition: all 0.4s ease-in-out;
          }
    
          .image-slider__container div {
            width: 100%;
            justify-content: center;
            display: flex;
          }
    
          .image-slider__container img {
            width: 100%;
            height: auto;
            max-width: 300px;
          }
    
          .image-slider__navigation {
            width: 100%;
            position: absolute;
            left: 0;
            top: 50%;
          }
    
          .image-slider__navigation--prev,
          .image-slider__navigation--next {
            position: absolute;
            border: 0;
            outline: 0;
            width: 80px;
            height: 80px;
            cursor: pointer;
          }
    
          .image-slider__navigation--prev {
            left: 0;
            top: 50%;
            transform: translateY(-50%);
          }
    
          .image-slider__navigation--next {
            right: 0;
            top: 50%;
            transform: translateY(-50%);
          }
        <div class="image-slider">
          <div class="image-slider__viewport">
            <div class="image-slider__container" id="container">
              <div>
                <img
                  src="https://i.postimg.cc/7hH1XLfK/checkout-logo-3-200x.webp"
                  alt="eye logo"
                />
              </div>
              <div>
                <img
                  src="https://i.postimg.cc/Yh7J36fV/reef.webp"
                  alt="reef logo"
                />
              </div>
              <div>
                <img
                  src="https://i.postimg.cc/F13M3Wc3/manduka.webp"
                  alt="manduka logo"
                />
              </div>
              <div>
                <img
                  src="https://i.postimg.cc/7hH1XLfK/checkout-logo-3-200x.webp"
                  alt="eye logo"
                />
              </div>
              <div>
                <img
                  src="https://i.postimg.cc/Yh7J36fV/reef.webp"
                  alt="reef logo"
                />
              </div>
              <div>
                <img
                  src="https://i.postimg.cc/F13M3Wc3/manduka.webp"
                  alt="manduka logo"
                />
              </div>
            </div>
            <div class="image-slider__navigation">
              <button class="image-slider__navigation--prev">
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M15.75 19.5L8.25 12l7.5-7.5"
                  />
                </svg>
              </button>
              <button class="image-slider__navigation--next">
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M8.25 4.5l7.5 7.5-7.5 7.5"
                  />
                </svg>
              </button>
            </div>
          </div>
        </div>

    I added JS function to set image-slider__container's width.

          const setImageSliderContainerWidth = () => {
            const sliderViewport = document.querySelector(".image-slider__viewport")
            const sliderImageContainer = sliderViewport.querySelector(
              ".image-slider__container"
            )
            const imageCount =
              sliderImageContainer.getElementsByTagName("img").length
    
            sliderImageContainer.style.width = `${
              sliderViewport.offsetWidth * imageCount
            }px`
          }