I am trying to display the value of a range item centered above the thumb. This works quite well by reading out the value and positioning the element above the range element as a percentage of its clientWidth
.
There is however a growing offset away from the middle (see pictures) which causes the value not to be centered on the thumb. I am convinced that this is caused because the thumb actually moves less than the clientWidth
because of the size of the thumb itself and maybe some room around the track.
How do I take this into account?
The JSFiddle can be found here: /
var myRange = document.querySelector('#myRange');
var myValue = document.querySelector('#myValue');
var off = myRange.clientWidth / (parseInt(myRange.max) - parseInt(myRange.min));
var px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2);
myValue.style.left = px + 'px';
myValue.style.top = -myRange.offsetHeight - 5 + 'px';
myValue.innerHTML = myRange.value;
myRange.oninput = function() {
let px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2);
myValue.innerHTML = myRange.value;
myValue.style.left = px + 'px';
};
<div style="position:relative; margin:auto; width:90%; margin-top:80px;">
<output style="position:absolute; min-width:100px; text-align: center;" id="myValue"></output>
<input type="range" id="myRange" min="0" max="200" step="5" style="width:80%" value="80">
</div>
I am trying to display the value of a range item centered above the thumb. This works quite well by reading out the value and positioning the element above the range element as a percentage of its clientWidth
.
There is however a growing offset away from the middle (see pictures) which causes the value not to be centered on the thumb. I am convinced that this is caused because the thumb actually moves less than the clientWidth
because of the size of the thumb itself and maybe some room around the track.
How do I take this into account?
The JSFiddle can be found here: https://jsfiddle/jd4kncuk/
var myRange = document.querySelector('#myRange');
var myValue = document.querySelector('#myValue');
var off = myRange.clientWidth / (parseInt(myRange.max) - parseInt(myRange.min));
var px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2);
myValue.style.left = px + 'px';
myValue.style.top = -myRange.offsetHeight - 5 + 'px';
myValue.innerHTML = myRange.value;
myRange.oninput = function() {
let px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2);
myValue.innerHTML = myRange.value;
myValue.style.left = px + 'px';
};
<div style="position:relative; margin:auto; width:90%; margin-top:80px;">
<output style="position:absolute; min-width:100px; text-align: center;" id="myValue"></output>
<input type="range" id="myRange" min="0" max="200" step="5" style="width:80%" value="80">
</div>
Share
Improve this question
edited Feb 7, 2020 at 10:28
Aleksandr Belugin
2,2271 gold badge16 silver badges20 bronze badges
asked Jan 9, 2017 at 14:08
Roy PrinsRoy Prins
3,0803 gold badges32 silver badges48 bronze badges
7
- Can you please post the relevant code into the question and if possible add a jsFiddle, Plunkr or similar as well to demonstrate the problem. – Nope Commented Jan 9, 2017 at 14:18
- Not to be pedantic, but I think that my description and supplied images should give the clearest picture of my approach and the problem at hand. – Roy Prins Commented Jan 9, 2017 at 14:20
- 1 You are not pedantic :) Images do not allow us to help you as we can't see your code and not knowing what your CSS, HTML or script (if relevant) does leaves anything open to a guess. In addition if the images get removed or change for some reason the question is of no use to any future user with similar issues. In the end its up to you how easy or difficult you want to make it for us to help you. – Nope Commented Jan 9, 2017 at 14:27
- 1 @RoyPrins dude did you found out the courellation? its damn unclear how it moves – godblessstrawberry Commented Feb 6, 2020 at 15:22
- 1 I only have the vaguest memory of even asking this question and I sure did not solve it at the time. I do not have time, but just for the heck of it I will add a bounty to the question. Honey attracts the flies, right :) – Roy Prins Commented Feb 7, 2020 at 9:00
3 Answers
Reset to default 3 +200Check this For More Using CSS: link
var myRange = document.querySelector('#myRange');
var myValue = document.querySelector('#myValue');
var thumbWidth = 18;
var off = (myRange.clientWidth - thumbWidth) / (parseInt(myRange.max) - parseInt(myRange.min));
var px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2) + (thumbWidth / 2);
myValue.style.left = px + 'px';
myValue.style.top = -myRange.offsetHeight - 5 + 'px';
myValue.innerHTML = myRange.value;
myRange.oninput = function() {
let px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2) + (thumbWidth / 2);
myValue.innerHTML = myRange.value;
myValue.style.left = px + 'px';
};
<div style="position:relative; margin:auto; width:90%; margin-top:80px;">
<output style="position:absolute; min-width:100px; text-align: center;" id="myValue"></output>
<input type="range" id="myRange" min="0" max="200" step="5" style="width:80%" value="80">
</div>
I think I've solved it. The issue was caused by the width of the thumb not being taken into account. The thumb doesn't move across the slider 100%, but rather 100% - ~15px. This way the thumb never overlaps the sides of the slider. Note that the ~15px width for a slider thumb is taken from chrome, in other browsers this could be different.
To take this into account when positioning the value display we need to offset it by a percentage of half of the width of the thumb, ranging from +7.5px to -7.5px. Here is an updated fiddle https://jsfiddle/crf96bw7/1/ that shows it working. I have added an extra bit of markup to help show the centre position of the value container.
var myRange = document.querySelector('#myRange');
var myValue = document.querySelector('#myValue');
var myValueInner = document.querySelector('#myValueInner');
var buttonWidth = 15;
var range = parseInt(myRange.max) - parseInt(myRange.min)
var centerDotOffset = (myRange.valueAsNumber / range - 0.5) * buttonWidth;
var off = myRange.clientWidth / range;
var px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2) - centerDotOffset;
myValue.style.left = px + 'px';
myValue.style.top = -myRange.offsetHeight - 5 + 'px';
myValueInner.innerHTML = myRange.value;
myRange.oninput = function() {
var centerDotOffset = (myRange.valueAsNumber / range - 0.5) * buttonWidth
let px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2) - centerDotOffset;
myValueInner.innerHTML = myRange.value;
myValue.style.left = px + 'px';
};
#container {
position: relative;
box-sizing: border-box;
margin: 0 auto;
width: 90%;
margin-top: 80px;
}
#myRange {
width: 100%;
margin: 0;
}
#myValue {
background: black;
}
#myValue #myValueInner {
width: 50%;
background: gold;
}
<div id="container">
<output style="position:absolute; min-width:100px; text-align: center;" id="myValue"><div id="myValueInner">
</div></output>
<input type="range" id="myRange" min="0" max="200" step="5" value="150">
</div>
Actually you can style the track and the thumb of the range with CSS. Then you have a known widths of the track and the thumb (otherwise they are platform dependent) and then you can incorporate this into the calculation.
So I added simplified CSS to the example and put the width of 30px
into the thumbWidth
JS variable and changed the formula:
var myRange = document.querySelector('#myRange');
var myValue = document.querySelector('#myValue');
var thumbWidth = 30;
var off = (myRange.clientWidth - thumbWidth) / (parseInt(myRange.max) - parseInt(myRange.min));
var px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2) + (thumbWidth / 2);
myValue.style.left = px + 'px';
myValue.style.top = -myRange.offsetHeight - 5 + 'px';
myValue.innerHTML = myRange.value;
myRange.oninput = function() {
let px = ((myRange.valueAsNumber - parseInt(myRange.min)) * off) - (myValue.clientWidth / 2) + (thumbWidth / 2);
myValue.innerHTML = myRange.value;
myValue.style.left = px + 'px';
};
input[type=range] {
-webkit-appearance: none;
margin: 10px 0;
}
input[type=range]::-webkit-slider-runnable-track {
height: 10px;
background: gray;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 30px;
width: 30px;
background: crimson;
margin-top: -10px;
}
<div style="position:relative; margin:auto; width:90%; margin-top:80px;">
<output style="position:absolute; min-width:100px; text-align: center;" id="myValue"></output>
<input type="range" id="myRange" min="0" max="200" step="5" style="width:80%" value="80">
</div>
PS: A cleaner approach would be to read the width of the thumb via JS out of the <input type="range">
so that no custom CSS would be necessary. However it is not possible to reach inside the user agent shadow dom.