phenomLi / Blog

Comments, Thoughts, Conclusions, Ideas, and the progress.
219 stars 17 forks source link

Math.round()的妙用 #16

Open phenomLi opened 6 years ago

phenomLi commented 6 years ago

最近再做一个类似滑块控件的东西,按道理来说滑块控件应该是挺好做的,没什么难度。但是具体做的时候却遇到了一点点小波折,就是类似下面这种滑块的实现:

主要难点就在于这种滑块的滑动点不是连续的,而是离散的。连续的还比较好做,只要获取鼠标的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
    });
}