如何拖动元素并确保光标不会比它快?
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
【讨论】:
以上是关于如何拖动元素并确保光标不会比它快?的主要内容,如果未能解决你的问题,请参考以下文章