Mustafa Al-Ani
Mustafa Al-Ani

Reputation: 93

how to get a pointer pointing at centre of slider thumb? CSS and Jquery

I have a feature to show the price range and the most repeated price (Mode). What i did is i used the range input type and deactivated it (so it can't be moved by user as it's only for illustration).

Below is my HTML, CSS and JS which i guess will explain better my issue

<div class="range">
    <div class="sliderValue">
        <span class="show" style="left: ${pinPostion}%;">${mostCommonMaxPrice}</span>
    </div>
    <div class="field">
        <div class="value left">${lowestMinPrice}</div>
        <input type="range" min="${lowestMinPrice}" max="${highestMaxPrice}" value="${mostCommonMaxPrice}" disabled>
        <div class="value right">${highestMaxPrice}</div>
    </div>
</div>
.range{
  height: 40px;
  width: 130px;
  background: #fff;
  border-radius: 10px;
  padding: 0 32.5px 0 22.5px;
}
.field{
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  position: relative;
}
.field .value{
  position: absolute;
  font-size: 12px;
  color: #4638D2;
  font-weight: 600;
}
.field .value.left{
  left: -15px;
}
.field .value.right{
  right: -21.5px;
}
.range input{
  -webkit-appearance: none;
  width: 100%;
  height: 3px;
  background: #ddd;
  border-radius: 5px;
  outline: none;
  border: none;
  z-index: 2222;
}
.range input::-webkit-slider-thumb{
  -webkit-appearance: none;
  width: 10px;
  height: 10px;
  background: #4638D2 !important;
  border-radius: 50%;
  background: #4638D2;
  border: 1px solid #4638D2;
}
 
.range input::-moz-range-progress{
  background: #4638D2;
}
.sliderValue{
  position: relative;
  width: 100%;
  align-items: center;
}
.sliderValue span{
  font-size: 8px;
  position: absolute;
  height: 22.5px;
  width: 22.5px;
  /*transform: translateX(-70%) scale(0);*/
  font-weight: 600;
  top: -15px;
  line-height: 27.5px;
  z-index: 2;
  color: #fff;
  text-align: center;
}
.sliderValue span.show{
  transform: translateX(-70%) scale(1.3);
}
.sliderValue span:after{
  position: absolute;
  content: '';
  height: 100%;
  width: 100%;
  background: #4638D2;
  border: 3px solid #fff;
  z-index: -1;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  border-bottom-left-radius: 50%;
  box-shadow: 0px 0px 8px rgba(0,0,0,0.1);
  border-top-left-radius: 50%;
  border-top-right-radius: 50%;
}
const pinPostion = (((mostCommonMaxPrice - lowestMinPrice)/(highestMaxPrice - lowestMinPrice)) * 100) + lowestMinPrice;

Price range sliders

enter image description here

As you see in the picture am not able to get the bubble that shows the mode number to point at the slider thumb position, it is only showing well when it's 100 but if it's more or less then it goes off position, I tried to calculate the percentage and pass it as a variable but it's still coming off, am not sure if my CSS is wrong or is it the equation am using or am I doing all this altogether wrong and there is an easier way to do it. I really appreciate your help. And would be grateful if you up the question, please :)

Upvotes: 1

Views: 1641

Answers (2)

Mr. Brickowski
Mr. Brickowski

Reputation: 1181

I think the pinpoint position does not consider the border width of the pinpoint itself, therefore it is not aligned to its center of the tip.

Consider this example, the pinpoint should be right at the middle:

variable value
lowestMinPrice 0
highestMaxPrice 100
mostCommonMaxPrice 50
const pinPostion = (((mostCommonMaxPrice - lowestMinPrice)/(highestMaxPrice - lowestMinPrice)) * 100) + lowestMinPrice;
pinPostion = (((50 - 0)/(100 - 0)) * 100) + 0;
pinPostion = 50

The pinpoint is made from a rotated element with border radius. If I take the radius off, this is how it looks

enter image description here

If I further take the transform off, this is how it looks

enter image description here

Even I port the number to your example, the pinpoint is off a little bit.

.range {
  height: 40px;
  width: 130px;
  background: #fff;
  border-radius: 10px;
  padding: 0 32.5px 0 22.5px;
}

.field {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  position: relative;
}

.field .value {
  position: absolute;
  font-size: 12px;
  color: #4638D2;
  font-weight: 600;
}

.field .value.left {
  left: -15px;
}

.field .value.right {
  right: -21.5px;
}

.range input {
  -webkit-appearance: none;
  width: 100%;
  height: 3px;
  background: #ddd;
  border-radius: 5px;
  outline: none;
  border: none;
  z-index: 2222;
}

.range input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 10px;
  height: 10px;
  background: #4638D2 !important;
  border-radius: 50%;
  background: #4638D2;
  border: 1px solid #4638D2;
}

.range input::-moz-range-progress {
  background: #4638D2;
}

.sliderValue {
  position: relative;
  width: 100%;
  align-items: center;
}

.sliderValue span {
  font-size: 8px;
  position: absolute;
  height: 22.5px;
  width: 22.5px;
  /*transform: translateX(-70%) scale(0);*/
  font-weight: 600;
  top: -15px;
  line-height: 27.5px;
  z-index: 2;
  color: #fff;
  text-align: center;
}

.sliderValue span.show {
  transform: translateX(-70%) scale(1.3);
}

.sliderValue span:after {
  position: absolute;
  content: '';
  height: 100%;
  width: 100%;
  background: #4638D2;
  border: 3px solid #fff;
  z-index: -1;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  border-bottom-left-radius: 50%;
  box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1);
  border-top-left-radius: 50%;
  border-top-right-radius: 50%;
}
<div class="range">
  <div class="sliderValue">
    <span class="show" style="left: 50%;">50</span>
  </div>
  <div class="field">
    <div class="value left">0</div>
    <input type="range" min="0" max="100" value="50" disabled>
    <div class="value right">100</div>
  </div>
</div>

Upvotes: 1

true_k
true_k

Reputation: 374

I found some code example here, it uses similar to yours approach with manual position calculation for Value Bubble, try it:

const allRanges = document.querySelectorAll(".range-wrap");
allRanges.forEach(wrap => {
  const range = wrap.querySelector(".range");
  const bubble = wrap.querySelector(".bubble");

  range.addEventListener("input", () => {
    setBubble(range, bubble);
  });
  setBubble(range, bubble);
});

function setBubble(range, bubble) {
  const val = range.value;
  const min = range.min ? range.min : 0;
  const max = range.max ? range.max : 100;
  const newVal = Number(((val - min) * 100) / (max - min));
  bubble.innerHTML = val;

  // Sorta magic numbers based on size of the native UI thumb
  bubble.style.left = `calc(${newVal}% + (${8 - newVal * 0.15}px))`;
}
.range-wrap {
  position: relative;
  margin: 0 auto 3rem;
}
.range {
  width: 100%;
}
.bubble {
  background: red;
  color: white;
  padding: 4px 12px;
  position: absolute;
  border-radius: 4px;
  left: 50%;
  transform: translateX(-50%);
}
.bubble::after {
  content: "";
  position: absolute;
  width: 2px;
  height: 2px;
  background: red;
  top: -1px;
  left: 50%;
}

body {
  margin: 2rem;
}
<div class="range-wrap">
  <input type="range" class="range">
  <output class="bubble"></output>
</div>

<div class="range-wrap">
  <input type="range" class="range" min="20" max="940">
  <output class="bubble"></output>
</div>

<div class="range-wrap" style="width: 75%;">
  <input type="range" class="range" min="50" max="60" step="2">
  <output class="bubble"></output>
</div>

<div class="range-wrap" style="width: 55%;">
  <input type="range" class="range" min="-20" max="20">
  <output class="bubble"></output>
</div>

I found it here https://css-tricks.com/value-bubbles-for-range-inputs/

Upvotes: 1

Related Questions