Jessica Jessica - 4 months ago 19
Javascript Question

Get value of slider in pixels

I am trying to create a custom range slider. Everything works well besides for when I try getting the current value in pixels.

Here's the relevant code: (In

function sliderMove
line 30)

cursorPosition = ((e.touches && e.touches[0].clientX) || e.clientX) - startPoint.left;
rangePosition = clamp(cursorPosition, _sliderPositions.rangeStart, _sliderPositions.rangeEnd);
value = percentToPixel((rangePosition / _sliderPositions.currentValue) * 100, 0, 500);

function percentToPixel(percent, min, max) {
if (typeof max === 'undefined') return;
return ((percent / 100) * (max - min)) + min;
}


value
doesn't give the correct result. It's supposed to be between 0 - 500, but it gives 1+ - 500+ depending on the size of the window. (If you resize the window, then reload, and try the slider again, you will see it gives a different min and max number for value.)

JSFiddle





console.clear();

function RangeSlider( /** DOM Elem */ parentElem) {
var wrapperElem = document.getElementsByClassName('wrapperElem')[0],
slider = document.getElementsByClassName('slider')[0],
sliderRange = document.getElementsByClassName('sliderRange')[0],
sliderCursor = document.getElementsByClassName('sliderCursor')[0],
output = document.getElementById('output'),
myButton = document.getElementById('myButton');

var sliderDimention = slider.offsetWidth,
cursorRadius = sliderCursor.offsetHeight / 2,
startPoint,
currentTarget,
value = 0,
_sliderPositions = {},
borderWidth = 1;

startPoint = getOrigin(wrapperElem.children[0]);
sliderDimention = slider.offsetWidth;


function sliderDown(e) {
e.preventDefault();

window.addEventListener('mousemove', sliderMove);
sliderMove(e);
}

function sliderMove(e) {
var rangePosition = 0;
var cursorPosition = 0;
reconfigVars();
if (typeof e === 'object') {
cursorPosition = ((e.touches && e.touches[0].clientX) || e.clientX) - startPoint.left;
} else {
cursorPosition = pixelToPercent(value, 0, 500) / 100 * sliderDimention;
}
//console.log(cursorPosition);

rangePosition = clamp(cursorPosition, _sliderPositions.rangeStart, _sliderPositions.rangeEnd);
cursorPosition = clamp(cursorPosition - cursorRadius, _sliderPositions.cursorStart, _sliderPositions.cursorEnd);

sliderCursor.style.transform = 'translateX(' + cursorPosition + 'px)';
sliderRange.style.transform = 'scaleX(' + rangePosition + ')';

if (typeof e === 'object') {
value = percentToPixel((rangePosition / _sliderPositions.currentValue) * 100, 0, 500);
}
output.innerHTML = value;

}


function reconfigVars() {
_sliderPositions.rangeStart = borderWidth * 2;
_sliderPositions.rangeEnd = sliderDimention - borderWidth * 2
_sliderPositions.cursorStart = startPoint.left - cursorRadius + borderWidth * 2;
_sliderPositions.cursorEnd = sliderDimention - borderWidth * 2 - cursorRadius * 2;
_sliderPositions.currentValue = sliderDimention - borderWidth * 2 - startPoint.left - cursorRadius;
}

myButton.addEventListener('click', function() {
sliderMove(500);
value = 500;
});






function mouseUpEvents() {
window.removeEventListener('mousemove', sliderMove);
}
wrapperElem.addEventListener('mousedown', sliderDown);
window.addEventListener('mouseup', mouseUpEvents);
}



var sliderTest = document.getElementById('sliderTest');
var test = new RangeSlider(sliderTest);







function getOrigin(elm) {
var box = (elm.getBoundingClientRect) ? elm.getBoundingClientRect() : {
top: 0,
left: 0
},
doc = elm && elm.ownerDocument,
body = doc.body,
win = doc.defaultView || doc.parentWindow || window,
docElem = doc.documentElement || body.parentNode,
clientTop = docElem.clientTop || body.clientTop || 0, // border on html or body or both
clientLeft = docElem.clientLeft || body.clientLeft || 0;

return {
left: box.left + (win.pageXOffset || docElem.scrollLeft) - clientLeft,
top: box.top + (win.pageYOffset || docElem.scrollTop) - clientTop
};
}

function clamp(val, moreThan, lessThan) {
if (typeof lessThan === 'undefined') lessThan = 1;
if (typeof moreThan === 'undefined') moreThan = 0;
return Math.min(lessThan, Math.max(moreThan, val));
}

function pixelToPercent(pixel, min, max) {
if (typeof max === 'undefined') return;
return ((pixel - min) / (max - min)) * 100;
}

function percentToPixel(percent, min, max) {
if (typeof max === 'undefined') return;
return ((percent / 100) * (max - min)) + min;
}

.wrapperElem {
height: 18px;
width: 100%;
cursor: pointer;
display: flex;
}
.slider {
height: 100%;
width: calc(100% - 62px);
border: 1px solid black;
position: relative;
}
.sliderCursor {
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid black;
}
.sliderRange {
background-color: green;
position: absolute;
width: 1px;
height: 100%;
transform-origin: left;
}
#output {
background-color: orange;
}

<div class="wrapperElem">
<div class="slider">
<div class="sliderRange"></div>
<div class="sliderCursor"></div>
</div>
</div>
<br />
<div id="output"></div>
<button id="myButton">Click Me</button>




Answer

The offset from 0 - 500 -> 1 - 514 (for example) is because the mouse position on the slider background is used to determine the value, rather than the xoffset of the slider cursor. The excess values are going to be proportional to 14 * 500 / sliderwidth on the 500 end, and 500 / sliderwidth on the 0 end.

This however does not give you the value in pixels: because you're obtaining a percentage of the slider's value, you've calculated just that - how much of it is filled. You're then scaling it to the slider's value range (0 - 500). If you need the value in pixels, don't take a percentage or you lose unit information and have to get it back by scaling against the silder's dimensions.

There are 3 areas involved in where the mouse could be and your value-obtaining code needs to factor that into account:

  1. area between leftmost part of the slider and the center of the slider cursor when the cursor is at its leftmost position - in pixels: 0px to 7px (half of your cursor's width).
  2. as above, but with rightmost. in pixels: (sliderwidth-7) px to sliderwidth px.
  3. the area in-between: 7px - (sliderwidth-7)px

To correctly get 0 - 500 (slider value not left-offset in px) you need to measure a percentage of area #3, clamping the mouse position against the slider to those same values. In other words, you need to use cursorPosition rather than rangePosition, but your percentage is going to be against the size of area #3 rather than the width of the slider in full.

Furthermore, the window-resizing issues are because of this snippet not running within reconfigVars():

  sliderDimention = slider.offsetWidth;

At the least element dimensions and positions could change due to resizing, it should go into a resize event which does this and then also calls reconfigVars() and any other events that update the slider based on its dimensions.

If there's more to it I'll update my answer but I think that's all that needs to be adjusted.


The above details aside... The simplest change I can make to get it working is this:

if (typeof e === 'object') {
    value = percentToPixel( (
      (sliderPosition - _sliderPositions.cursorStart) / (_sliderPositions.cursorEnd - _sliderPositions.cursorStart)
    ) * 100, 0, 500);
}

This is taking into account the first and third paragraphs, the second paragraph is more to note if you had wanted a pixel readout.


First paragraph explained

If you imagine the slider layout in its structural form, you have these positions to worry about:

/-------------------------------------\
|  (cursor-start)       (cursor-end)  |
\-------------------------------------/
        slider r-offset - border*2 = ^
 cursor r-most extent = ^
   ^ = cursor left-most extent
 ^ = slider offset + border

The calculation I've modified now calculates the % that the cursor position makes from the left-most extent to the r-most extent. In other words the full range of motion for the cursor is what the % divisor should be, and the cursor position relative to the start of that range of motion is what should be the numerator. Hopefully that makes sense..