Description
最近再做一个类似滑块控件的东西,按道理来说滑块控件应该是挺好做的,没什么难度。但是具体做的时候却遇到了一点点小波折,就是类似下面这种滑块的实现:
主要难点就在于这种滑块的滑动点不是连续的,而是离散的。连续的还比较好做,只要获取鼠标的clientX
然后转换成控件圆点的left
就行。
那么,离散的要怎么实现?
先不急,我们先仔细观察一下这个控件,看看有什么规律可循。其实正常人都能看出来,控件小圆点在哪个离散点取决于鼠标的相对离散点的最近距离。
假如控件有A,B,C三个离散断点,离散点之间的距离我们取个中点,以这些中点为分界点,鼠标在中点左侧,则小圆点在左侧离鼠标最近那个离散点,若鼠标在中点右侧,则小圆点在右侧离鼠标最近那个离散点。画个图好理解一点:
假如ABC之间距离都为100,那么鼠标在< 50
的位置时小圆点在A上,>= 50 && < 150
小圆点在B上,>= 150
小圆点在C上,以此类推。
实现
现在知道了原理,但是怎么用代码实现呢?
当时想了挺久的,一开始打算用简单粗暴if else
判断鼠标位置:
/**
* x为鼠标横坐标
*/
if(x < 50) {
//....
}
else if(x >= 50 && x < 150) {
//...
}
else {
//...
}
但假如离散点很多怎么办?所以if else
不太现实。后来想到用取模%
:
/**
* x为鼠标横坐标
* interval为离散点间间隔
*/
n = x%interval //???????
n = x%(interval/2) //???????
但是认真推算一下根本不是那个道理,好像越想越歪,走不通。
难道就没有办法了吗,看起来并不是很复杂的功能啊。其实再结合上面的图仔细联想一下,假如我想要小圆点在B上,我只要鼠标大于1/2AB并且小于1/2BC,也就是说,假如以B为基准,只要大于一点点1/2AB就取到基准,小于一点点1/2BC就舍去不要,这不就是小学学的四舍五入嘛!
大体思路是有了,但是怎么四舍五入是个问题。其实思路很简单,离散点间的距离是已知的,以控件最左边为原点,我们用鼠标的横坐标/控件距离,再四舍五入,就可以算出现在圆点应该在哪个离散点上。伪代码如下:
/**
* x为鼠标横坐标
* interval为离散点间间隔
*
* Math.round是javascript提供的四舍五入的方法
*/
n = Math.round(x/interval);
那么知道了应该要到哪个点上,那么只要将n
乘上离散点间间隔,就能算出小圆点的left
应该设置多少了:
left = interval*Math.round(x/interval);
就是这么简单直接,没有一点点多余的东西,一行代码搞定。
下面是我做的一个成品效果:
核心代码:
/**
* clickFlag是一个标志,用作判断用户是否按下鼠标,按下鼠标才能拖拽圆点
* 在mousedown的时候将标志设置为true,表示用户已经按下鼠标
*/
mouseDown() {
this.setState({
clickFlag: true
});
}
/**
* mousemove的时候先判断用户是否在按下鼠标的情况下进行拖拽
* transformXY是用作将鼠标相对于页面的横纵坐标转换成相对于控件的横纵坐标
* @param {Event} ev 事件对象
*/
mouseMove(ev) {
if(this.state.clickFlag) {
let {x, y} = this.transformXY(ev.clientX, ev.clientY);
/**
* 首先做一个判断,防止鼠标越界(超出控件)
*/
if(x < 400 - 7 && x > 0) {
this.setState({
/**
* 关键代码
* 100就是离散点间的间隔
*/
left: Math.round(x/100)*100
});
}
}
}
/**
* mouseup鼠标抬起,将标志位设置为false
*/
mouseUp() {
this.setState({
clickFlag: false
});
}