Ranges

Double Range Slider with floating tooltips

23 Sep 2022

Double Range Slider with floating tooltips.
HTML
SCSS
JS
                            <div class="double-range-container double-range-tooltips">
  <div
    id="double-range-tooltips"
    class="double-range"
    data-min="0"
    data-max="5000"
    data-unit="$"
    data-step="10"
  ></div>
  <input
    class="double-range-hidden-input"
    id="double-range-value-min"
    value="200"
    type="hidden"
  />
  <input
    class="double-range-hidden-input"
    id="double-range-value-max"
    value="2500"
    type="hidden"
  />
</div>
                        
                            .double-range {
  padding-right: 15px;
  margin-bottom: 80px;
  height: 6px;
  border-radius: 12px;
  border: none;
  box-shadow: none;
  background: lightgrey;

  .noUi-tooltip {
    transform: translate(-50%, 100%);
    left: 50%;
    bottom: -10px;
    display: block !important;
    background: lightgrey;
    font-size: 15px;
    font-weight: 400;
    font-family: Arial, Helvetica, sans-serif;
    color: black;
    line-height: 1.2;
    padding: 11px 12px;
    border: none;
    border-radius: 4px;
    &.hidden {
      display: none !important;
    }
  }

  .noUi-connect {
    background: orange;
    margin-right: -4px;
  }

  .noUi-handle {
    background: orange;
    width: 18px;
    height: 18px;
    border: 2px solid white;
    cursor: pointer;
    border-radius: 50%;
    box-shadow: none;

    &:before,
    &:after {
      content: none;
    }
  }
}
                        
                            const doubleRange = document.getElementsByClassName(
    "double-range-tooltips"
)[0];
if (doubleRange) {
  const slider = doubleRange.querySelector("#double-range-tooltips");
  const max = +slider.dataset.max;
  const min = +slider.dataset.min;
  const unit = slider.dataset?.unit || "£";
  const step = +slider.dataset.step;
  const inputsHidden = doubleRange.querySelectorAll(
      ".double-range-hidden-input"
  );
  const startValueMin = +inputsHidden[0].value;
  const startValueMax = +inputsHidden[1].value;

  // how many percentages limit from borders ???? is 8%
  const borderLimit = 8;

  // each step is go backward for this amount of % ???? is 5%
  const borderDiff = 40 / borderLimit;

  noUiSlider.create(slider, {
    start: [startValueMin, startValueMax],
    connect: true,
    tooltips: true,
    margin: 10,
    step: step,
    range: {
      min: min,
      max: max,
    },
  });

  const tooltipsArr = slider.querySelectorAll(".noUi-tooltip");
  const circlesArr = slider.querySelectorAll(".noUi-origin");

  function returnTransform(element) {
    return element
        .replace(/[^0-9][^\d.]/g, "")
        .replace(")", "")
        .split(" ")
        .map((str) => {
          return Number(str);
        });
  }

  function needToMerge() {
    let tooltipOnePosition = tooltipsArr[0].getBoundingClientRect();
    let tooltipTwoPosition = tooltipsArr[1].getBoundingClientRect();

    if (
        tooltipsArr[0].classList.contains("hidden") ||
        tooltipsArr[1].classList.contains("hidden")
    ) {
      return true;
    }

    return (
        tooltipOnePosition.left +
        tooltipOnePosition.width -
        tooltipTwoPosition.left >
        0
    );
  }

  function resetTooltips(values) {
    mergeDiff = null;
    tooltipsArr.forEach((elem, index) => {
      elem.textContent =
          unit + Math.round(values[index]).toLocaleString("en-GB");
      elem.classList.remove("hidden");
    });
  }

  let trueTooltip = false;
  let mergeDiff = null;

  slider.noUiSlider.on("update", function (values, handle) {
    // translate of noUISlider -> 0% is start, 100% is end
    let translate = returnTransform(circlesArr[handle].style.transform)[0];

    // min value of double range slider
    let valueMin = returnTransform(circlesArr[0].style.transform)[0];

    // max value of double range slider
    let valueMax = returnTransform(circlesArr[1].style.transform)[0];

    // difference between min and max value of double range slider
    let difference = valueMax - valueMin;

    inputsHidden[handle].value = Math.round(values[handle]);

    // if tooltips are close to each other
    if (needToMerge()) {
      if (
          !tooltipsArr[+!handle].classList.contains("hidden") &&
          !tooltipsArr[handle].classList.contains("hidden")
      ) {
        trueTooltip = handle;
        mergeDiff = difference;
        tooltipsArr[+!handle].classList.add("hidden");
      }

      if (trueTooltip) {
        // limit left merged tooltip from overflowing
        // borderLimit * 3 === need for 3 times faster limitation because of merged tooltip
        if (translate <= -100 + borderLimit * 3) {
          tooltipsArr[trueTooltip].style.transform = `translate(${
              -50 -
              difference +
              (Math.abs(translate + 100 - borderLimit * 3) * borderDiff) / 2.5
          }%,100%)`;
        } else {
          // position of tooltip in the middle of range
          tooltipsArr[trueTooltip].style.transform = `translate(${
              -50 - difference
          }%,100%)`;
        }
      } else {
        // if left tooltip is grabbed
        if (translate >= -borderLimit * 4) {
          // limit right merged tooltip from overflowing
          tooltipsArr[trueTooltip].style.transform = `translate(${
              -50 +
              difference -
              (Math.abs(translate + borderLimit * 3) * borderDiff) / 2.5
          }%,100%)`;
        } else {
          tooltipsArr[trueTooltip].style.transform = `translate(${
              -50 + difference
          }%,100%)`;
        }
      }

      tooltipsArr[trueTooltip].textContent = `${
          unit + Math.round(values[0]).toLocaleString("en-GB")
      } - ${unit + Math.round(values[1]).toLocaleString("en-GB")}`;

      if (mergeDiff - difference < 0) {
        mergeDiff = null;
        resetTooltips(values);
      }
    } else {
      // limit left solo tooltip from overflowing
      if (translate <= -100 + borderLimit) {
        tooltipsArr[0].style.transform = `translate(${
            -50 + Math.abs(translate + 100 - borderLimit) * borderDiff
        }%,100%)`;
      } else if (translate >= -borderLimit) {
        // limit right solo tooltip from overflowing
        tooltipsArr[1].style.transform = `translate(${
            -50 - Math.abs(translate + borderLimit) * borderDiff
        }%,100%)`;
      } else {
        // position of tooltip in the middle of range
        tooltipsArr[handle].style.transform = "translate(-50%,100%)";
      }

      tooltipsArr[handle].textContent = `${
          unit + Math.round(values[handle]).toLocaleString("en-GB")
      }`;
    }
  });
}
                        

На базі плагіна noUiSlider.

В data-атрибути вносяться мінімальне, максимальне значення, валюта, крок (на скільки в одному кроці буде мінятися значення).

В data-unit вноситься валюта, якщо потрібен фунт (£), то можна просто видалити цей атрибут, дефолтне значення - фунт.

Є два сховані інпути. Один для максимального значення, другий для мінімального. Туди запусуються значення яке вибирається в слайдері.

nouislider.min.rar

0