3

I'm building a range/slider to compare a users results to an average. I'm really struggling to get the text under the slider to behave as I want:

Example

I have managed to get the arrow, percentage, and text to be centered correctly as in the first two images, but when I set the position too high or low, the text overflows into another area of the page. What I want is behaviour like the last two images, where the text stays within its bounds.

I've tried two different ways with the same end result, and now I'm stuck:

<div style="`margin-left:${areaScore}%;margin-right:${100 - areaScore}%;text-align:center`">
    <div style="transform: translateX(-50%)" >^</div>
    <div style="transform: translateX(-50%); width: 50px">
        {{ areaScore }}%
    </div>
    <div style="transform: translateX(-50%); width: 150px">
          The average in your area
    </div>
</div>
<div style="position: relative; text-align: center">
    <div :style="`position:absolute;left:${areaScore}%`">^</div>
    <div :style="`position:absolute;left:${areaScore}%;top:10px`">
        {{ areaScore }}%
    </div>
    <div :style="`position:absolute;left:${areaScore}%;top:24px`">
        The average in your area
    </div>
</div>

I should probably mention that I'm using Vue, hence the moustaches, colon before style, and parameterisation of areaScore, but I don't think it's particularly relevant to my problem.

I'm sure this problem is solvable, but I've wasted so much time trying to find a solution, and I'm hoping someone can point me in the right direction.

Jamey
  • 813
  • 1
  • 12
  • 27

1 Answers1

1

This requires some calculations in JavaScript. I don't know vue so I will give my answer using pure JS. I've put together an example for moving the texts. Let's see the HTML first:

<div class="container">
  <input class="slider" type="range" min="0" max="100">
  <div>
    <span class="percent">50%</span>
  </div>
  <div>
    <span class="text">Some long text</span>
  </div>
</div>

I put the text and the percent (like your score) in span elements inside of div elements. The reason is that the a div by default stretches over the complete width (because of display: block). Inside needs to be an element whose width we can read and whose position we can manipulate.

Now span can't be positioned because it is displayed inline. So, I use display: inline-block on it:

.percent, .text {
  display: inline-block;
}

The last piece is the calculation in JavaScript:

const slider = document.querySelector('.slider');
const percent = document.querySelector('.percent');
const text = document.querySelector('.text');

slider.value = 50;
setPercent(50);
positionElement(text, 50);

slider.addEventListener("input", (event) => {
  setPercent(event.target.value);
  positionElement(text, event.target.value);
});

function setPercent(value) {
  percent.textContent = value + '%';
  positionElement(percent, value);
}
  
function positionElement(element, value) {
  const width = element.offsetWidth;
  const xPosition = Math.min(Math.max(value - (width / 2), 0), 100 - width);
  element.style.transform = `translateX(${xPosition}px)`;
}

The keys are using translateX to move the span elements like you already did. Using Math.min and Math.max, we can set boundaries to this movement, depending on the width of the elements. Important: first set the new text and then get the width and change the translateX. The 100 is based on a width of 100px of the sliding area.

If we want to update the position while dragging, it is necessary to update on input events as found in https://stackoverflow.com/a/19067260/4874075.

I've put the example on codepen: https://codepen.io/andreas-tennert/pen/YzeMoee

Andreas
  • 125
  • 1
  • 9
  • Thank you so much! I was getting lost in CSS and didn't even think to try some javascript! :) – Jamey Jun 20 '22 at 07:48