carousel with clickable card not working properly

Viewed 112

I have this carousel with card divs wrapped inside an href so users can click the card. Right now I can only slide the card but it's not clickable.

If I remove pointer-events: none; from .inner-slider then the card is clickable, but I cannot slide the carousel smoothly and it's all jumpy when I tried to slide it.

How can I fix this?

let sliderContainer = document.querySelector(".slider-container");
let innerSlider = document.querySelector(".inner-slider");
let banner = document.querySelector(".banner");

let pressed = false;
let startX;
let x;
let bannerOpacity = banner.style.opacity
let oldx = 0

sliderContainer.addEventListener("mousedown", (e) => {
  pressed = true;
  startX = e.offsetX - innerSlider.offsetLeft;
  sliderContainer.style.cursor = "grabbing";
});

// sliderContainer.addEventListener("mouseenter", () => {
//     sliderContainer.style.cursor = "grab";
// });

sliderContainer.addEventListener("mouseleave", () => {
  sliderContainer.style.cursor = "default";
});

sliderContainer.addEventListener("mouseup", () => {
  sliderContainer.style.cursor = "grab";
  pressed = false;
});

window.addEventListener("mouseup", () => {
  // pressed = false;
});

sliderContainer.addEventListener("mousemove", (e) => {
  if (!pressed) return;

  console.log(`e.pageX:${e.pageX};oldx:${oldx};op:${banner.style.opacity}`);
  console.log(banner.style.opacity + 1);
  if (e.pageX > oldx && banner.style.opacity < 1) {
    banner.style.opacity = parseFloat(banner.style.opacity) + 0.01;
    console.log('right');
  } else if (e.pageX < oldx && banner.style.opacity > 0) {
    console.log('left');
    banner.style.opacity -= 0.01
  }
  e.preventDefault();

  x = e.offsetX;

  innerSlider.style.left = `${x - startX}px`;
  oldx = e.pageX;
  checkBoundary();
});

const checkBoundary = () => {
  let outer = sliderContainer.getBoundingClientRect();
  let inner = innerSlider.getBoundingClientRect();

  if (parseInt(innerSlider.style.left) > 150) {
    innerSlider.style.left -= 10;
    // bannerOpacity -= 0.01
  }

  if (inner.right < outer.right) {
    innerSlider.style.left = `-${inner.width - outer.width}px`;
  }
};
.card {
  height: 300px;
  width: 400px;
  border-radius: 5px;
}

.banner {
  z-index: 2;
  height: 300px;
  width: 200px;
  border-radius: 5px;
  background-color: red;
}

.card:nth-child(odd) {
  background-color: blue;
}


/* .card:first-child {
        visibility: hidden;
    } */

.card:nth-child(even) {
  background-color: rgb(0, 183, 255);
}

.slider-container {
  width: 80%;
  height: 350px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  overflow: hidden;
}

.inner-slider {
  width: 150%;
  display: flex;
  gap: 10px;
  pointer-events: none;
  position: absolute;
  top: 0;
  left: 250px;
}
<div class="slider-container">
  <div class="banner" style="opacity: 1;"></div>
  <div class="inner-slider">
    <!-- <div class="card"></div> -->
    <a href="google.com">
      <div class="card"></div>
    </a>
    <a href="https://www.google.com/">
      <div class="card"></div>
    </a>
    <a href="https://www.google.com/">
      <div class="card"></div>
    </a>
    <a href="https://www.google.com/">
      <div class="card"></div>
    </a>
    <a href="https://www.google.com/">
      <div class="card"></div>
    </a>
  </div>
</div>

CodePen Link: https://codepen.io/mjkno1/pen/zYWPjEw

1 Answers

Here is a possible solution.

Changes made:

  • make cards as <div> blocks instead of <a> and save the block link in the data attribute
  • whenever a user clicks on the card, check if it's a "mousemove" event or "click" - if mouse moved more then clickDelta value assume it's a move, otherwise - click. If click - make redirect programmatically to the url stored in the data attribute
  • some changes made to the way how we calculate new position for the slider innerSlider.style.left. Memorise the initial position of the slider and add "move delta".

Hope this makes sense

   let sliderContainer = document.querySelector(".slider-container");
    let innerSlider = document.querySelector(".inner-slider");
    let banner = document.querySelector(".banner");

    const clickDelta = 5;
    let pressed = false;

    let startX;
    let blockPos;

    let clickStartX;
    let clickStartY;
    let clickedTarget;

    let x;
    let bannerOpacity = banner.style.opacity;
    let oldx = 0;

    sliderContainer.addEventListener("mousedown", (e) => {
      pressed = true;
      startX = e.pageX;
      blockPos = parseInt(innerSlider.style.left, 10) || 0;
      sliderContainer.style.cursor = "grabbing";
    });

    sliderContainer.addEventListener("mouseleave", () => {
      sliderContainer.style.cursor = "default";
    });

    sliderContainer.addEventListener("mouseup", () => {
      sliderContainer.style.cursor = "grab";
      pressed = false;
    });

    innerSlider.addEventListener("mousedown", (e) => {
      clickedTarget = e.target;
      clickStartX = event.pageX;
      clickStartY = event.pageY;
    });

    innerSlider.addEventListener("mouseup", (e) => {
      const deltaX = Math.abs(event.pageX - clickStartX);
      const deltaY = Math.abs(event.pageY - clickStartY);

      if (deltaX < clickDelta && deltaY < clickDelta && clickedTarget) {
        const href = clickedTarget.dataset.href;
        if (href) {
          window.location.href = href;
        }
      }
    });

    window.addEventListener("mouseup", () => {
      pressed = false;
    });

    sliderContainer.addEventListener("mousemove", (e) => {
      e.preventDefault();

      if (pressed) {
        const newLeft = blockPos + (e.pageX - startX);
        innerSlider.style.left = `${newLeft}px`;

        if (e.pageX > oldx && banner.style.opacity < 1) {
          banner.style.opacity = parseFloat(banner.style.opacity) + 0.01;
        } else if (e.pageX < oldx && banner.style.opacity > 0) {
          banner.style.opacity -= 0.01;
        }

        oldx = e.pageX;

        checkBoundary();
      }
    });

    const checkBoundary = () => {
      let outer = sliderContainer.getBoundingClientRect();
      let inner = innerSlider.getBoundingClientRect();

      if (parseInt(innerSlider.style.left) > 150) {
        innerSlider.style.left -= 10;
      }

      if (inner.right < outer.right) {
        innerSlider.style.left = `-${inner.width - outer.width}px`;
      }
    };
    .card {
      height: 300px;
      width: 400px;
      border-radius: 5px;
    }

    .banner {
      z-index: 2;
      height: 300px;
      width: 200px;
      border-radius: 5px;
      background-color: red;
    }

    .card:nth-child(odd) {
      background-color: blue;
    }

    .card:nth-child(even) {
      background-color: rgb(0, 183, 255);
    }

    .slider-container {
      width: 80%;
      height: 350px;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      overflow: hidden;
    }

    .inner-slider {
      width: 150%;
      display: flex;
      gap: 10px;
      position: absolute;
      top: 0;
    }
  <div class="slider-container">
    <div class="banner" style="opacity: 1"></div>
    <div class="inner-slider" style="left: 250px">
      <div class="card" data-href="https://www.google1.com/"></div>
      <div class="card" data-href="https://www.google2.com/"></div>
      <div class="card" data-href="https://www.google3.com/"></div>
      <div class="card" data-href="https://www.google4.com/"></div>
      <div class="card" data-href="https://www.google5.com/"></div>
    </div>
  </div>

Related