Reputation: 148
Most video players allow you to click on the progress bar to jump to a particular point in the video by using the position of the mouse on the progress bar to roughly work out what point in the video the user will skip to if the progress bar is clicked at that point.
If the progress bar max value is small the calculation is correct.
This is an example:
$("#range").on("click", function(event){
var tooltip = Math.round((event.offsetX / event.target.clientWidth) * parseInt(event.target.getAttribute('max')));
if (tooltip < 0) {
tooltip = 0
}else if (tooltip > 10) {
tooltip = 10
}
$(".calculated").text("Calculated Value: " + tooltip)
$(".real").text("Real Value: " + $(this).val())
});
.main{
position: relative;
width: 100%;
margin-top: 50px
}
/* Tooltip text */
.main .tooltip {
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
padding: 5px;
border-radius: 6px;
}
input{
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main">
<span class="tooltip calculated">5</span>
<span class="tooltip real">20</span>
<input type="range" min="0" max="10" value="20" id="range">
</div>
But if the progress bar max value is big the calculation will not be exact.
This is an example:
$("#range").on("click", function(event){
var tooltip = Math.round((event.offsetX / event.target.clientWidth) * parseInt(event.target.getAttribute('max')));
if (tooltip < 0) {
tooltip = 0
}else if (tooltip > 1000) {
tooltip = 1000
}
$(".calculated").text("Calculated Value: " + tooltip)
$(".real").text("Real Value: " + $(this).val())
});
.main{
position: relative;
width: 100%;
margin-top: 50px
}
/* Tooltip text */
.main .tooltip {
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
padding: 5px;
border-radius: 6px;
}
input{
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main">
<span class="tooltip calculated">20</span>
<span class="tooltip real">20</span>
<input type="range" min="0" max="1000" value="20" id="range">
</div>
Is there any other solution to get more accurate calculation ? I'm happy to hear any suggestions.
Upvotes: 1
Views: 1125
Reputation: 33933
The reason why there is so much error in the calculation you attempted is everything but obvious.
You correctly identified an imprecision with a big maxValue
on a short slider width.
The calculation error comes from the fact that the range slider knotches are not distributed in a linear way from left to right. So a simple division of the offsetX
by the slider's width is... incomplete.
The "thumb" of the slider has a width. So in order to be able to get the minimum and maximum values while keeping the thumb inside the slider, the knotches are distributed in a way to gradually compensate that width from the extremes (+/-50% of thumb width) to the middle (0% of thumb width). In other words, the middle really is the middle value... But the exteme knotches are "offsetted" with a percentage of the thumb width.
To the span
you used to show the calculated value versus the "real" one, I added a span for the difference in calculation and one for the error percentage. So as you can see below, I achieved a maximal error percentage of around 0.10% or less... I really doubt it is possible to get better than this! ;)
So to make sure that my calculation is tight here, I used a super huge range of ten millions (lol!)... And tried to come with the most accurate calculation possible. There always will be an error... But the idea is to keep it minimal AND "consistant" all over the slider width.
Since the key is about the thumb width, I added some CSS took from css-tricks just to make sure about that width. Removing that styling does not change anything (at least on Chrome). I did NOT found a way to retreive the slider's thumb width programmatically. So your simple solution is to define it.
I hope the calculations below are clear.
$("#range").on("click", function(event) {
// Get the slider's params
let maxValue = parseInt(event.target.getAttribute('max'))
let sliderWidth = event.target.clientWidth
// That is the key value causing offsets in calculations
let thumbWidth = 16
// I made a named function just to have the calculation logic apart from the rest.
let thumbCorrection = getOffsetXcorrection(event.offsetX, sliderWidth, thumbWidth)
// Using that "thumb correction" in the basic intuitive calculation anyone would expect
let clickedKnotch = (event.offsetX-thumbCorrection) / sliderWidth
// The demo outputs
var calculatedValue = Math.round(clickedKnotch * maxValue)
let realValue = $(this).val()
let difference = calculatedValue - realValue
let errorPercentage = Math.abs((difference / maxValue) *100)
$(".calculated").text("Calculated: " + calculatedValue)
$(".real").text("Real: " + realValue)
$(".difference").text("Diff: " + difference)
$(".percentage").text("Error: " + errorPercentage.toFixed(2) + "%")
});
function getOffsetXcorrection(clickedPosition, fullWidth, thumbWidth) {
// The middle is the 0% correction point
let middle = fullWidth/2
// The "error" always is about half the thumb width
let halfThumbWidth = thumbWidth/2
// So where occured the click in that context?
let percentageFromMiddle = (middle - clickedPosition) / middle
// Return the correction about the click position to use in a "linear" calculation
let correction = percentageFromMiddle * halfThumbWidth
return Math.round(correction)
}
.main {
position: relative;
width: 100%;
margin-top: 50px
}
/* Tooltip text */
.main .tooltip {
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
padding: 5px;
border-radius: 6px;
}
input {
width: 100%;
margin-top: 1em;
}
/* From CSS-tricks */
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 3px;
/*background: #ffffff;
cursor: pointer;
margin-top: -14px;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; */
}
.main .percentage{
background-color: tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main">
<span class="tooltip calculated">20</span>
<span class="tooltip real">20</span>
<span class="tooltip difference">0</span>
<span class="tooltip percentage">0</span>
<input type="range" min="0" max="10000000" value="20" id="range">
</div>
Upvotes: 1
Reputation: 695
Because of the width of the point.
In the picture above, the actual length of the input is (100% - point's width)
Although it is possible to calculate simply by excluding the length of the dot, it is not recommended because errors may occur depending on the screen width or browser type.
My solution is to use percentages.
$("#range").on("click", function(event) {
var inputVal = parseInt(event.target.value);
var inputMaxVal = parseInt(event.target.getAttribute("max"));
var inputPercentage = inputVal / inputMaxVal * 100;
var tmpVideo = document.getElementById("yourVideo");
var videoDur = tmpVideo.duration;
var videoCur = tmpVideo.currentTime;
var videoPercentage = videoCur / videoDur * 100;
$(".calculated").text("Calculated Value: " + inputPercentage);
$(".real").text("Real Value: " + videoPercentage);
});
Now compare the inputPercentage and videoPercentage.
p.s. I may have misunderstood the problem because I am using a translator.
Upvotes: 1