Why is my range slider exceeding my desired maximum width?

Viewed 75

I created a range slider using just a single div with ::pseudo elements since I cannot access ::pseudo directly with JavaScript I took help of CSS variables. But there are certain problems I faced along and was not able to solve.

With max-width

const range = document.querySelector(".range");

function setRange(event) {
  const rect = range.getBoundingClientRect();

  range.style.setProperty(
    "--progress",
    `${(event.clientX / rect.width) * 100}%`
  );
}

range.addEventListener("pointerdown", (event) => {
  range.classList.add("active");
  range.setPointerCapture(event.pointerId);
  setRange(event);

  range.addEventListener("pointermove", setRange);

  range.addEventListener(
    "pointerup",
    (event) => {
      range.classList.remove("active");
      range.releasePointerCapture(event.pointerId);
      range.removeEventListener("pointermove", setRange);
    }, {
      once: true
    }
  );
});
@import "https://cdn.jsdelivr.net/gh/KunalTanwar/normalize/css/normalize.inter.min.css";
body {
  height: 100%;
  display: grid;
  place-items: center;
}

.range {
  width: 100%;
  cursor: pointer;
  height: 0.25rem;
  max-width: 450px;
  border-radius: 60rem;
  background-color: rgba(120, 120, 128, 0.2);
}

.range::before,
.range::after {
  content: "";
  position: absolute;
}

.range::before {
  width: var(--progress);
  height: 100%;
  border-radius: inherit;
  background-color: #007aff;
}

.range::after {
  top: 50%;
  cursor: grab;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  aspect-ratio: 1/1;
  left: var(--progress);
  background-color: white;
  transform: translate(-50%, -50%);
  box-shadow: 0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12);
}

.range.active {
  cursor: grabbing;
}
<div class="range"></div>

Without max-width

const range = document.querySelector(".range");

function setRange(event) {
  const rect = range.getBoundingClientRect();

  range.style.setProperty(
    "--progress",
    `${(event.clientX / rect.width) * 100}%`
  );
}

range.addEventListener("pointerdown", (event) => {
  range.classList.add("active");
  range.setPointerCapture(event.pointerId);
  setRange(event);

  range.addEventListener("pointermove", setRange);

  range.addEventListener(
    "pointerup",
    (event) => {
      range.classList.remove("active");
      range.releasePointerCapture(event.pointerId);
      range.removeEventListener("pointermove", setRange);
    }, {
      once: true
    }
  );
});
@import "https://cdn.jsdelivr.net/gh/KunalTanwar/normalize/css/normalize.inter.min.css";
body {
  height: 100%;
  display: grid;
  place-items: center;
}

.range {
  width: 100%;
  cursor: pointer;
  height: 0.25rem;
  border-radius: 60rem;
  background-color: rgba(120, 120, 128, 0.2);
}

.range::before,
.range::after {
  content: "";
  position: absolute;
}

.range::before {
  width: var(--progress);
  height: 100%;
  border-radius: inherit;
  background-color: #007aff;
}

.range::after {
  top: 50%;
  cursor: grab;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  aspect-ratio: 1/1;
  left: var(--progress);
  background-color: white;
  transform: translate(-50%, -50%);
  box-shadow: 0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12);
}

.range.active {
  cursor: grabbing;
}
<div class="range"></div>

My Approach

What my initial thought was that I will put an if condition inside setRange() function :

function setRange(event) {
  const rect = range.getBoundingClientRect();

  if (
    !(window.getComputedStyle(range).getPropertyValue("--progress") > "100%")
  ) {
    range.style.setProperty(
      "--progress",
      `${(event.clientX / rect.width) * 100}%`
    );
  }
}

But the above code is not working after first pointerdown although when I am console logging the if condition it is working just fine.

I also tried :

if (!(parseFloat(window.getComputedStyle(range).getPropertyValue('--progress')) > 100)) {
  range.style.setProperty('--progress', `${(event.clientX / rect.width) * 100}%`)
}

and again after reaching 100% I am not able to move it back to below 100% in simple words I can't move the slider after reaching 100%.

There are 2 main questions

  1. Why after setting max-width the range slider not working like it should.
  2. Why the if condition I put inside setRange() working on console.log() not inside DOM?
  3. How can I set max-value of var(--progress) so it should not increase beyond 100% ?

P.S - suggest an appropriate title for this question!

1 Answers

Finally I solved my problem. So, initially it is working when I am not setting max-width on it. And the second problem was that the range is increasing beyond 100%. In order to solve both problems what I did was I found a helper function which can minimized and maximized the value on SO itself. You can find the same function here

Now the second problem was that after setting max-width my slider is sliding ahead of cursor position. It was a silly mistake by me that I am not subtracting the left of the slider because the function setRange calculating cursor position respective to body not the slider.

Final Code :

const range = document.querySelector('.range')

function limitRange(number, min, max) {
  return Math.min(Math.max(parseFloat(number), min), max)
}

function setRange(event) {
  const rect = range.getBoundingClientRect()

  range.style.setProperty('--progress', `${limitRange(((event.clientX - rect.left) / rect.width) * 100, 0, 100)}%`)
}

range.addEventListener('pointerdown', (event) => {
  range.classList.add('active')
  range.setPointerCapture(event.pointerId)

  setRange(event)

  range.addEventListener('pointermove', setRange)

  range.addEventListener(
    'pointerup',
    (event) => {
      range.classList.remove('active')
      range.releasePointerCapture(event.pointerId)
      range.removeEventListener('pointermove', setRange)
    }, {
      once: true
    }
  )
})
@import "https://cdn.jsdelivr.net/gh/KunalTanwar/normalize/css/normalize.inter.min.css";
body {
  height: 100%;
  display: grid;
  place-items: center;
}

.range {
  width: 100%;
  height: 0.25rem;
  cursor: pointer;
  max-width: 450px;
  border-radius: 60rem;
  background-color: rgba(120, 120, 128, 0.2);
}

.range::before,
.range::after {
  content: "";
  position: absolute;
}

.range::before {
  width: var(--progress);
  height: 100%;
  border-radius: inherit;
  background-color: #007aff;
}

.range::after {
  top: 50%;
  cursor: grab;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  aspect-ratio: 1/1;
  left: var(--progress);
  background-color: white;
  transform: translate(-50%, -50%);
  box-shadow: 0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12);
}

.range.active {
  cursor: grabbing;
}
<div class="range"></div>

You can find this Pen on CodePen and many more like these.

Related