我不知道具体的专业术语叫什么,所以标题只能这样写。其实从标题也可以看出来到底是什么效果。现在很多设计软件、设计类网站都具备这样的功能,就是鼠标按下,指针变成左右拖动的模样,按住鼠标不松开然后左右拖动就可以修改数值,如果是按下直接松开,就是平常的准备输入。
通过以上的描述可以大概知道如何实现,无非就是监听 input 的 mouedown/pointerdown 事件,然后监听 document 的 mousemove/pointermove 和 mouseup/pointerup 事件,move 事件里根据移动距离修改数值,up 事件里取消绑定。
const isDrag = ref(false) const events = computed(() => { if (typeIsNumber(props.type)) { return { touchmove: (e: TouchEvent) => { if (isDrag.value) { e.preventDefault() } }, pointerdown: (e: PointerEvent) => { e.stopPropagation() isDrag.value = true document.addEventListener('pointermove', dragChange) const oldCursor = document.body.style.cursor document.body.style.cursor = 'e-resize' const remove = () => { document.removeEventListener('pointermove', dragChange) document.removeEventListener('pointerup', remove) isDrag.value = false document.body.style.cursor = oldCursor } document.addEventListener('pointerup', remove) }, } } return {} }) // 拖动改变数值 let moveDelta = 0 const dragChange = (e: PointerEvent) => { e.preventDefault() if (!typeIsNumber(props.type)) return if (!e.movementX) return window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty() moveDelta += e.movementX const delta = moveDelta / 10 if (Math.abs(delta) >= 1) { value.value += delta moveDelta = 0 } }
这里是 vue3 版本的实现代码,我这里监听的是 pointer 事件,因为这个事件可以兼顾鼠标和触摸事件。在 pointerdown 事件里设置一下鼠标样式,绑定 move 和 up 事件。move 事件的回调 dragChange 里根据移动的距离修改数值。
但有一些细节需要注意。
input 框的指针样式需要修改:
<q-input v-model="value" v-bind="config.field" :type="type" :min="min" :max="max" v-on="events" :disable="disable" :input-style=" isDrag ? { 'user-select': 'none', cursor: 'e-resize', } : {} " />
input 框的文本需要禁止选中。如上代码使用了 user-select,但这对 input 是无效的。网上有一种解决方案是调用 input 的 blur 方法,我试了,反正我这里无效。
之后我又尝试了 readonly。但 readonly 不阻止文本选择。
然后我又试了 disable,这个有效,但是指针样式会变成 not-allowed。
于是我又想到了另一种方案:实时取消文本选择。
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty()
这就是这段代码的作用。这种方法有效,但体验不好,因为文本会一直闪烁。但在有更好的方法之前,我也只能如此。
这段代码上面还有一句 if (!e.movementX) return。这里主要是为了可以双击选择文本。因为点击/双击也会触发 pointermove 事件。
再解释一下为什么我一定要取消文本选择,从功能上来说,选择文本不影响数值的改变,但是选择文本之后,再次按下鼠标准备拖动改变数值的时候,edge 浏览器会认为你是想搜索文本,从而直接打开新窗口。并且 e.preventDefault 无法阻止这种行为。
说到 e.preventDefault,代码里还有一个需要解释的地方:touchmove 事件。为什么我还要额外监听 touchmove 呢?因为在移动端左右拖动的时候,浏览器也是有默认行为的,并且这个默认行为仅仅通过 pointermove 事件里的 preventDefault 阻止是不够的。
细节上花的时间比主要功能多多了~~~
最后,我这里的功能比较简单,很多细节和市面上的没法比,比如 figma 貌似可以一直拖,拖出窗口后自动从另一侧继续拖。不过无所谓了,我也不追求极致体验,能用就行。毕竟精力有限,还有很多其他重要功能需要实现。