- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
最近在研究Figma的一些功能设计, 对其中的数值输入框可以直接鼠标拖拽调整的这个设计印象非常深刻. 这里用了其他网友的一张动态截图演示一下效果. 。
实际这个拖拽的功能不止看到的这么简单, 在深度研究使用之后, 发现这个拖拽可以无限的拖动, 当鼠标超出网页后会自动回到另一端然后继续拖动, 而且按住shift键, 可以调整单次数值变化的间隔值为10, 细节非常的丰富. 。
这篇文章, 我们就来尝试实现一下这个支持拖拽调整数值的输入框组件. 实现基于: typescript + react + tailwindcss + shadcn-ui 实现的功能有
下面拆解一下组件的实现逻辑 。
通常的拖动更新数值, 实现是现在元素上监听 mousedown, 然后在document上监听 mousemove 和 mouseup. 在 mousemove 中处理位置的计算更新逻辑, 参考draggable-input-v1. 首先, 构建state记录的输入框值和鼠标的位置.
const [snapshot, setSnapshot] = useState(value);
const [mousePos, setMousePos] = useState<number[] | null>(null);
当鼠标在左侧标签按下的时候, 记录鼠标的初始位置 。
const onDragStart = useCallback(
(position: number[]) => {
setMousePos(position);
},
[]
);
给label的jsx绑定mousedown事件 。
return (
<div
onMouseDown={(e) => {
onDragStart([e.clientX, e.clientY]);
}}
className="cursor-ew-resize absolute top-0 left-0 h-full flex items-center"
>
{label}
</div>
);
然后在document上监听, mousemove和mouseup事件, 用于拖拽更新数值 。
useEffect(() => {
// Only change the value if the drag was actually started.
const onUpdate = (event: MouseEvent) => {
const { clientX } = event;
if (mousePos) {
const newSnapshot = snapshot + clientX - mousePos[0];
onChange(newSnapshot);
}
};
// Stop the drag operation now.
const onEnd = (event: MouseEvent) => {
const { clientX } = event;
if (mousePos) {
const newSnapshot = snapshot + clientX - mousePos[0];
setSnapshot(newSnapshot);
setMousePos(null);
onChange(newSnapshot);
}
};
document.addEventListener('mousemove', onUpdate);
document.addEventListener('mouseup', onEnd);
return () => {
document.removeEventListener('mousemove', onUpdate);
document.removeEventListener('mouseup', onEnd);
};
}, [mousePos, onChange, snapshot]);
这个时候, 我们已经可以通过鼠标拖拽来调整数值了. 。
但是, 和Figma的不太一样
基于这些问题, 可以猜测, Figma的这种无限拖拽, 其实是隐藏了鼠标后, 用虚拟的光标模拟鼠标操作的. 我们仔细留意一下Figma的输入框拖拽按下的瞬间, 发现鼠标样式是有变化的, 进一步证明我们的猜想. 。
那么下面就是考虑, 如果用JS隐藏鼠标, 并且保证鼠标不会移出页面可见区域窗口. 这让我想到了, 在浏览一下Web3D效果的页面时, 鼠标点击后进入场景交互, 此时鼠标用于控制第一人称视角相机, 不论怎么拖动都不会跑到别的屏幕上. 。
于是问了一下Claude, 确实有这样的一个API, Element.requestPointerLock() 可以让我们锁定鼠标在某个元素内, 默认使用esc可以推出锁定, 也可以用 document.exitPointerLock() 手动退出锁定. 那么我们要做的就是在鼠标按下(mousedown)的时候, 进入锁定, 鼠标抬起(mouseup)的时候退出锁定. 参考案例里面, v2版本中draggable-label.tsx文件中的的部分代码 。
const onDragStart = useCallback(
(position: number[]) => {
document.body.requestPointerLock();
setMousePos(position);
},
[]
);
const onEnd = () => {
setMousePos(null);
setCursorPosition(null);
document.exitPointerLock();
};
解决了锁定鼠标的问题, 下一步就是虚拟光标模拟鼠标移动的实现. 这一步不算复杂, 我们找一个水平resize的光标对应的svg, 在需要的时候, 控制他显示然后调整位置即可. 这里我直接封装了一个hooks, 参考 use-ew-resize-cursor.tsx 的实现 。
import { useEffect, useState } from 'react';
const EWResizeCursorID = 'ZMeta_ew_resize_cursor';
export const useEWResizeCursor = () => {
const [position, setPosition] = useState<number[] | null>(null);
useEffect(() => {
let ewCursorEle = document.querySelector(
`#${EWResizeCursorID}`
) as HTMLDivElement;
if (position == null) {
ewCursorEle && document.body.removeChild(ewCursorEle);
return;
}
if (ewCursorEle == null) {
ewCursorEle = document.createElement('div');
ewCursorEle.id = EWResizeCursorID;
ewCursorEle.style.cssText =
'position: fixed; top: 0; left: 0;transform: translate3d(-50%, -50%, 0);';
ewCursorEle.innerHTML = `<svg t="1721283130691" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4450" width="32" height="32"><path d="M955.976575 533.675016l-166.175122 166.644747a28.148621 28.148621 0 0 1-39.845904 0c-10.945883-11.018133-10.945883-28.900021 0-39.954279l119.465463-119.826713H160.575743l119.465462 119.826713c11.018133 11.018133 11.018133 28.900021 0 39.954279a28.148621 28.148621 0 0 1-39.845904 0l-166.102872-166.644747c-5.888379-5.852254-8.381006-13.691385-8.019756-21.422141-0.36125-7.658506 2.131377-15.461511 8.019756-21.34989l166.102872-166.608622a28.148621 28.148621 0 0 1 39.845904 0c11.018133 11.018133 11.018133 28.900021 0 39.954279L160.575743 484.075355h708.845269l-119.465463-119.826713c-10.945883-11.018133-10.945883-28.900021 0-39.954279a28.148621 28.148621 0 0 1 39.845904 0l166.175122 166.608622c5.888379 5.888379 8.381006 13.691385 7.911381 21.34989 0.4335 7.730756-2.059127 15.569886-7.911381 21.422141z" fill="#bfbfbf" p-id="4451"></path></svg>`;
document.body.appendChild(ewCursorEle);
}
const [x, y] = position;
ewCursorEle.style.top = y + 'px';
ewCursorEle.style.left = x + 'px';
}, [position]);
return { setPosition };
};
实现的内容很简单, 当传入位置时, 手动构建一个svg的光标在body下, 然后动态设置top和left的值. 那么在mousemove 的时候, 获取鼠标的位移,然后更新给 useEWResizeCursor 即可. 参考我的实现是, mousedown的时候记录初始位置, 直接展示虚拟光标并更新位置. 。
const { setPosition: setCursorPosition } = useEWResizeCursor();
const [mousePos, setMousePos] = useState<number[] | null>(null);
// Start the drag to change operation when the mouse button is down.
const onDragStart = useCallback(
(position: number[]) => {
document.body.requestPointerLock();
setMousePos(position);
setSnapshot(value);
setCursorPosition(position);
},
[setCursorPosition, value]
);
同时记录 mousePos 即鼠标的实际位置, 在mousemove的时候继续使用. 这么做是因为, 当我们使用 requestPointerLock()之后, 鼠标的位置已经被锁定了, 我们在拖动鼠标时, mouseEvent.clientX等属性不会更新, 但是 mouseEvent.movementX 和 mosueEvent.movementY还是可以正常使用的. 因此我们需要自己记录并计算鼠标拖动的大概位置 。
const onUpdate = (event: MouseEvent) => {
const { movementX, movementY } = event;
if (mousePos) {
const newSnapshot = snapshot + movementX;
const [x, y] = mousePos;
const newMousePos = [x + movementX, y + movementY];
setMousePos(newMousePos);
setCursorPosition(newMousePos);
setSnapshot(newSnapshot);
onChange(newSnapshot);
}
};
这样, 鼠标拖动, 虚拟的鼠标位置会更新, 然后输入框的值也会更新. 但是还不能做到无限拖动, Figma的效果是, 当鼠标水平超出网页边界后, 会从另一端出来, 这样就可以周而复始的拖动. 。
那我们要做的就是限制虚拟的光标位置在一个范围内, 这一步其实也很简单, 我们只需要保证 mousePosX 在 (0, bodyWidth)之间即可. 我们可以封装一个小的方法, 来实现这个限制的功能 。
function calcAbsoluteRemainder(value: number, max: number) {
value = value % max;
return value < 0 ? value + max : value;
}
这样, 当鼠标的x位置超出屏幕右侧, 则会自动跳到左侧, 反之亦然. 那么我们的代码就可以简单的改动一下 。
const onUpdate = (event: MouseEvent) => {
const { movementX, movementY } = event;
if (mousePos) {
const newSnapshot = snapshot + movementX;
const [x, y] = mousePos;
const bodyWidth = document.documentElement.clientWidth;
const bodyHeight = document.documentElement.clientHeight;
const newX = calcAbsoluteRemainder(x + movementX, bodyWidth);
const newY = calcAbsoluteRemainder(y + movementY, bodyHeight);
const newMousePos = [newX, newY];
setMousePos(newMousePos);
setCursorPosition(newMousePos);
setSnapshot(newSnapshot);
onChange(newSnapshot);
}
};
这样就可以做到无限拖拽修改数值了. 现在的逻辑是, 每移动一个像素, 数值就加减1, 如果觉得这个更新的频率太快, 希望做到 每10个像素加减1, 我们可以再 补充一个 scale的参数, 在计算snapshot的时候, 用movementX * scale, 然后onchange的时候取整即可. 。
const newSnapshot = snapshot + movementX * scale;
onChange(Math.round(snapshot));
同样, 我想每次调整的间隔是10而不是1 (Figma安装shift拖动) 那么可以再定义一个step参数, 在onchange的时候, 对其做整数倍计算 。
const newValue = Math.round(newSnapshot);
onChange(step === 1 ? newValue : Math.floor(newValue / step) * step);
这里step为1的时候, 就直接返回取整后的值即可. 。
至此, 我们仿照Figma的拖拽功能已经全部实现, 看下最终效果, 简直一模一样. 。
这里我们注意到一个小细节, 即拖拽松开的时候, 鼠标是会回到起始按下的位置, 这点和figma一样. 而且, 第一次按下的时候, 因为锁定鼠标, 浏览器默认会提示 按住esc显示鼠标. 看了一下Figma的web端, 也是一样的. 。
最后此篇关于Figma数值输入框支持拖拽调整功能实现的文章就讲到这里了,如果你想了解更多关于Figma数值输入框支持拖拽调整功能实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在开发一个 Java 脚本,为此我需要正则表达式来检查文本框中输入的文本是否应该是字母和数值的组合。 我尝试了 Java 脚本的 NaN 函数,但字符串的最小长度和最大长度应为 4,并以字母作为第
我给出了两个长方体,其中只有一个轴对齐(另外两个不需要对齐)和顶点坐标(在全局坐标系中),我知道它们相交。我正在寻找一种可以计算路口体积的算法。 为了检查交点,我使用了分离轴定理。 最佳答案 可以通过
我有一个类似这样的对象的 json 列表 [{ "something": "bla", "id": 2 }, { "something": "yes", "id": 1
这是一篇很长的文章,但请留在我身边... 我有一个字典,它将“PO”保存为Key,将“SO”保存为项目(在某些情况下,某个“PO”可能有多个“SO”) . 工作表中的我的 Excel 数据,字典在其中
我的问题是是否有办法使用 terms include在 numeric field在 elasticsearch aggregation . 我在 Elasticsearch 中对多个字段使用通用查询
我有一个 perl 代码片段 use JSON::XS; $a = {"john" => "123", "mary" => "456"}; print encode_json($a),"\n"; 输出
我想对 python 进行一个条件测试,以检查给定输入数字的值是否等于或小于 9,并且大于或等于 0。 number =input( "Please enter a number! :" ) Plea
我有一个这样的对象: var rock = { 5: 0.5, 0: 0.8, 10: 0.3, 2: 1.0, } 我有一个像 4.3 这样的数字,我需要前后数字的索引和值。在这个例子中我会
对于 iOS 中的 Objective-C: 如果我有一个字符串,如何读取单个字符的 unicode 数值? 例如,如果我的字符串是:“Δ”,unicode 字符是 U+0394,那么我如何读取该字符
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,
我有这样的数组 var arrayVal_Int = ["21", "53", "92", "79"]; var arrayVal_Alpha = ["John", "Christine", "L
就像标题暗示我需要做这样的事情...... $i++;//we all know this. $value = 'a'; increment($value);// i need this functi
我有一个文件,其中包含一些不同值的概率,例如: 1 0.1 2 0.05 3 0.05 4 0.2 5 0.4 6 0.2 我想使用此分布生成随机数。是否存在处理此问题的现有模块?自己编写代码相当简单
因此,我在从使用 RCPP 创建的函数返回值时遇到了一些问题。它只返回 NumericVector 的第一个值。问题是当我在自身内部调用函数并将 NumericVector 传递回 out 变量时。任
我有下面的数字 vector 模板类(用于数值计算的 vector )。我正在尝试使编写 D=A+B+C 成为可能,其中所有变量都是 Vector 对象。 A、B 和 C 不应修改。我的想法是使用 V
本文实例讲述了mysql常用函数。分享给大家供大家参考,具体如下: 本文内容: mysql函数的介绍 聚集函数 avg count max
我正在尝试使用 python(无关)为我的公司自动化一些事情,这就是我的问题。首先,我正在从邮箱中的特定文件夹创建数据框。(到这里没问题)” RangeIndex: 36 entries, 0 to
我在让 Angular ng-if 工作时遇到了一些麻烦。我希望我的 DOM 元素之一在 $scope.week = 1 时消失。 在我的 Controller 中我设置了 $scope.week =
我正在阅读 Ingersoll、Morton 和 Farris 撰写的 Taming Text,但我不明白 solr 的数字 trie 实现如何帮助搜索文本?我对 solr.TrieField fie
这个问题已经有答案了: What is the difference between client-side and server-side programming? (3 个回答) 已关闭 9 年前
我是一名优秀的程序员,十分优秀!