I have this slider with two thumbs. The thumbs can be moved all along the slider (line), what is made possible by increasing or decreasing their margin-left, but in order for them to move the state move must be true, it happens when each one of the thumbs triggers the event onClickDown. However, If the event onClickedUp is triggered, the cursor leaves the area of the thumb or the slider, move is set to false, what makes the thumb stop moving. And it's ok, that's the idea.
The problem is that the cursor may be faster than the thumbs movement, as can be seen on the gif below, what makes the cursor leaves the area of the thumb and set move to false, even if that's not what the user wants.
So, in order to make the slider works properly the user would have to be extremelly careful when moving the thumbs, what makes it a very annoying UX.
In short, what I need to do is ensure that the cursor doesn't move faster than the thumb, it doesn't matter if I'll have to slow the cursor or increase the speed of the thumbs.
How could I do that?
Here is my code with some notes:
import React, { Fragment } from 'react'
import './Filter.css'
const Filter = props => {
const sliderRef = React.useRef() // => main parent div
const initial_position = 0
const end_position = 200
const initial_min_value = 5 // => Initial price
const initial_max_value = 1290 // => Final price
let [thumb1_position, setValueThumb1] = React.useState(0)
let [thumb2_position, setValueThumb2] = React.useState(0)
let [min_value, setMinValue] = React.useState(initial_min_value)
let [max_value, setMaxValue] = React.useState(initial_max_value)
let [move, setMove] = React.useState(false) // => Enable thumbs to move
// Ensure that the thumb_2 will be in the end of the slider at first
React.useEffect(() => {
setValueThumb2(sliderRef.current.offsetWidth - 5)
}, [])
// Here I get the position of the cursor within the element (slider) and move the thumbs based on it.
const handleChange = e => {
let thumb_class = e.target.className
var rect = sliderRef.current.getBoundingClientRect();
const current_position = e.clientX - rect.left; // X position within the element.
// Only moves if 'move' is true
if (move === true) {
// Get the className to ensure that only the clicked thumb is moved
if (thumb_class.includes('left-thumb')) {
// Ensure that the thumb_1 will always be on the left and the thumb_2 on the right
// Ensure that neither of the thumbs exceed the limits of the slider
if (current_position >= initial_position && current_position < thumb2_position - 25) {
setValueThumb1(current_position)
} else if (current_position >= initial_position && current_position >= thumb2_position - 25) {
setValueThumb1(thumb2_position - 25)
setMove(false)
} else {
setValueThumb1(initial_position)
setMove(false)
}
if (thumb1_position - initial_position < 1) {
setMinValue(initial_min_value)
} else {
setMinValue((thumb1_position - initial_position) * 6.46)
}
} else if (thumb_class.includes('right-thumb')) {
if (current_position >= thumb1_position + 25 && current_position <= end_position) {
setValueThumb2(current_position)
} else if (current_position >= thumb1_position + 25 && current_position >= end_position) {
setValueThumb2(end_position)
setMove(false)
} else {
setValueThumb2(thumb1_position + 25)
setMove(false)
}
if (thumb2_position > end_position - 1) {
setMaxValue(initial_max_value)
} else {
setMaxValue((thumb2_position - initial_position) * 6.48)
}
}
}
}
const moveOn = e => {
setMove(true)
}
const moveOff = () => {
setMove(false)
}
return (
<Fragment>
<div>
<h6 style={{marginBottom: '35px'}}>PRICE FILTER</h6>
<div className="range-container"
onMouseMove={(e) => handleChange(e)}
onMouseDown={(e) => moveOn(e)}
onMouseUp={() => moveOff()}
onMouseLeave={() => moveOff()}
ref={sliderRef}
>
<div className="range">
<span className="rounded-circle left-thumb"
style={{
width:'15px',
height: '15px',
backgroundColor: 'red',
marginTop: '-6px',
marginLeft: thumb1_position - 7 + 'px'
}}
></span>
<span className="rounded-circle right-thumb"
style={{
width:'15px',
height: '15px',
backgroundColor: 'black',
marginTop: '-6px',
marginLeft: thumb2_position - 7 + 'px'
}}
></span>
<p style={{
marginLeft: thumb1_position - 15 + 'px',
position: 'absolute',
marginTop: '15px'}}
> {Math.floor(min_value)}
</p>
<p style={{
marginLeft: thumb2_position - 15 + 'px',
position: 'absolute',
marginTop: '15px'}}
> {Math.floor(max_value)}
</p>
</div>
</div>
</div>
</Fragment>
)
}
export default Filter
