如何拖动元素并确保光标不会比它快?

Posted

技术标签:

【中文标题】如何拖动元素并确保光标不会比它快?【英文标题】:How to drag an element and ensure that the cursor won't faster than it? 【发布时间】:2021-01-24 16:42:15 【问题描述】:

我有这个带有两个拇指的滑块。拇指可以沿着滑块(线)移动,这可以通过增加或减少他们的margin-left来实现,但是为了让他们移动状态move必须是true,它发生在每个拇指触发事件onClickDown。但是,如果触发事件onClickedUp,光标离开拇指或滑块的区域,move 设置为false,是什么使拇指停止移动。没关系,就是这样。

问题是光标可能比拇指移动更快,如下图所示,是什么让光标离开拇指区域并将move设置为false,即使那不是用户想要的。

因此,为了使滑块正常工作,用户在移动拇指时必须非常小心,这是一个非常烦人的用户体验。

简而言之,我需要做的是确保光标的移动速度不会超过拇指,我是否必须减慢光标或增加拇指的速度都没有关系。

我该怎么做?

这是我的代码和一些注释:

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

【问题讨论】:

很难从 gif 中分辨出来,但是当您朝任一方向移动时会发生这种情况吗? @Ricardo Sanchez 是的,方向无关紧要。光标总是比拇指快 我明白了,如果您禁用 if (move === true) 语句,它的行为是否符合预期? 不,因为我需要确保拇指只能在事件onMouseDown被触发时移动,如果onMouseUp被触发则意味着用户不再想要移动滑块,因此,move 设置为 false。如果没有这个条件,无论用户是否想要它都将始终启用。我认为的一种可能性是创建一个条件以确保在触发onMouseDown 时光标不会离开拇指区域。但我只是想了想,我仍然不知道该怎么做。 有道理,我只是想缩小问题的范围 【参考方案1】:

我不断挖掘并偶然发现setPointerCapture() 方法,解决了我的问题。

https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture

它的功能是完全按照我对代码所做的操作,但使用pointer events。它使元素在 pointerdown 事件上移动,并在 pointerup 事件上停止移动。

因为它几乎可以完成我需要做的所有事情,所以我可以摆脱一些我正在使用的功能。事实上,唯一保留的函数是handleChange。除此之外,我还删除了move 状态,但我必须添加一些refs 才能获取每个元素。

这是新代码:

import React,  Fragment  from 'react'

import './Filter.css'


const Filter = props => 

    const sliderRef = React.useRef() // => main parent div

    const sliderRef = React.useRef() // => Div que engloba o slider
    const thumb_1_Ref = React.useRef() // => Div que engloba o slider
    const thumb_2_Ref = React.useRef() // => Div que engloba o slider
    const price_thumb_1_Ref = React.useRef() // => Div que engloba o slider
    const price_thumb_2_Ref = React.useRef() // => Div que engloba o slider
    
    const initial_position = 0 
    
    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 [mobile_thumb1_position, setValueMobileThumb1] =  React.useState(0)
    let [mobile_thumb2_position, setValueMobileThumb2] =  React.useState(0)
    let [min_value, setMinValue] =  React.useState(initial_min_value)
    let [max_value, setMaxValue] =  React.useState(initial_max_value)
    
    // Ensure that the thumb_2 will be in the end of the slider at first
    React.useEffect(() => 
        setValueThumb2(sliderRef.current.offsetWidth - 5)
    , [])



    let slider
    let slider_price

    const beginSliding = e => 
        slider.onpointermove = slide
        slider.setPointerCapture(e.pointerId)
    
    
    const stopSliding = e => 
        slider.onpointermove = null
        slider.releasePointerCapture(e.pointerId)
    
    
    const slide = e => 
        const thumb_class = e.target.className

        let rect = sliderRef.current.getBoundingClientRect()
        let current_position = e.clientX - rect.left 
        
        if (thumb_class.includes('right-thumb')) 

            current_position = current_position - sliderRef.current.offsetWidth

            if (current_position >= initial_position) 
                current_position = initial_position
            

            if (current_position <= mobile_thumb1_position - 175) 
                current_position = mobile_thumb1_position - 175
            
            
            setValueMobileThumb2(current_position)
         
        
        if (thumb_class.includes('left-thumb')) 

            if (current_position <= initial_position) 
                current_position = initial_position
            

            if (current_position >= mobile_thumb2_position + 175) 
                current_position = mobile_thumb2_position + 175
            

            setValueMobileThumb1(current_position)
        
        
        slider.style.transform = `translate($current_positionpx)`
        slider_price.style.transform = `translate($current_positionpx)`
    
        
    
    const handleChange = e => 

        const thumb_class = e.target.className

        if (thumb_class.includes('left-thumb')) 

            slider = thumb_1_Ref.current;
            slider_price = price_thumb_1_Ref.current;

            slider.onpointerdown = beginSliding;
            slider.onpointerup = stopSliding;

            if (mobile_thumb1_position - initial_position < 1) 
                setMinValue(initial_min_value)
             else 
                setMinValue((mobile_thumb1_position - initial_position) * 6.45)
            

         else if (thumb_class.includes('right-thumb')) 
            
            slider = thumb_2_Ref.current;
            slider_price = price_thumb_2_Ref.current;

            slider.onpointerdown = beginSliding;
            slider.onpointerup = stopSliding;

            if (mobile_thumb2_position > -1) 
                setMaxValue(initial_max_value)
             else 
                setMaxValue((mobile_thumb2_position + 200) * 6.45)
            
        

    



    return (
        <Fragment>
            <div>
                <h6 style=marginBottom: '35px'>PRICE FILTER</h6>
                <div className="range-container"
                    onMouseMove=(e) => handleChange(e)
                    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'
                            
                            ref=thumb_1_Ref
                        ></span>
                        <span 
                            className="rounded-circle right-thumb"
                            style=
                                width:'15px',
                                height: '15px',
                                backgroundColor: 'black',
                                marginTop: '-6px',
                                marginLeft: thumb2_position - 7 + 'px'
                            
                            ref=thumb_2_Ref
                        ></span>
                        <p style=
                            marginLeft: thumb1_position - 15 + 'px',
                            position: 'absolute',
                            marginTop: '15px'
                            ref=price_thumb_1_Ref
                        > Math.floor(min_value)
                        </p>
                        <p style=
                            marginLeft: thumb2_position - 15 + 'px',
                            position: 'absolute',
                            marginTop: '15px'
                            ref=price_thumb_2_Ref
                        > Math.floor(max_value)
                        </p>
                    </div>
                </div>
            </div>
        </Fragment>
    )


export default Filter 

【讨论】:

以上是关于如何拖动元素并确保光标不会比它快?的主要内容,如果未能解决你的问题,请参考以下文章

当光标隐藏在其他 HTML 元素后面时如何触发 mouseenter 事件?

HTML 元素的光标在之后设置,但在拖动期间不设置

jQuery可拖动问题

拖动 DOM 元素时有啥方法可以改变光标样式吗?

更改电子中可拖动区域的光标

更改html5拖放功能,使元素看起来附加到光标[重复]