Sou*_*ara 5 javascript select drag typescript reactjs
这是我为实现拖动选择而编写的代码。
项目生成代码
const items = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104,
].map((i) => ({ item: i, selected: i === 1 }));
Run Code Online (Sandbox Code Playgroud)
这是负责选择的实际代码
const [data, setData] = useState(items);
const [isSelecting, setIsSelecting] = useState(false);
const [start, setStart] = useState<Coords>({ x: 0, y: 0, screenX: 0, screenY: 0 });
const [end, setEnd] = useState<Coords>({ x: 0, y: 0, screenX: 0, screenY: 0 });
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleMouseDown(e: any) {
if (e.target.closest(".selectable")) return;
setIsSelecting(true);
setStart({ x: e.clientX, y: e.clientY, screenX: e.screenX, screenY: e.screenY });
setEnd({ x: e.clientX, y: e.clientY, screenX: e.screenX, screenY: e.screenY });
setData((data) => [...data.map((item) => ({ ...item, selected: false }))]);
}
ref.current?.addEventListener("mousedown", handleMouseDown);
return () => {
ref.current?.removeEventListener("mousedown", handleMouseDown);
};
}, [ref]);
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
if (!isSelecting) return;
console.log("START");
console.log({ clientX: start.x, clientY: start.y, screenX: start.screenX, screenY: start.screenY });
console.log("END");
console.log({ clientX: e.clientX, clientY: e.clientY, screenX: e.screenX, screenY: e.screenY });
setEnd({ x: e.clientX, y: e.clientY, screenX: e.screenX, screenY: e.screenY });
const selected = [...data];
const elements = document.getElementsByClassName("selectable");
for (let i = 0; i < elements.length; i++) {
const rect = elements[i].getBoundingClientRect();
const elementRect = {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
right: rect.right + window.pageXOffset,
bottom: rect.bottom + window.pageYOffset,
};
if (
((elementRect.left >= Math.min(start.x, end.x) && elementRect.left <= Math.max(start.x, end.x)) ||
(elementRect.right >= Math.min(start.x, end.x) && elementRect.right <= Math.max(start.x, end.x))) &&
((elementRect.top >= Math.min(start.y, end.y) && elementRect.top <= Math.max(start.y, end.y)) ||
(elementRect.bottom >= Math.min(start.y, end.y) && elementRect.bottom <= Math.max(start.y, end.y)))
) {
selected[i].selected = true;
} else {
selected[i].selected = false;
}
}
setData(selected);
}
function handleMouseUp() {
setIsSelecting(false);
reset();
}
const reset = () => {
setStart({ x: 0, y: 0, screenX: 0, screenY: 0 });
setEnd({ x: 0, y: 0, screenX: 0, screenY: 0 });
};
Run Code Online (Sandbox Code Playgroud)
生成叠加层
const overlayStyle: any = {
position: "absolute",
backgroundColor: colors.slate[800],
opacity: 0.5,
border: "1px dotted",
borderColor: colors.slate[300],
left: `${Math.min(start.x, end.x) - (ref.current?.offsetLeft || 0)}px`,
top: `${Math.min(start.y, end.y) - (ref.current?.offsetTop || 0) + (ref.current?.scrollTop || 0)}px`,
width: `${Math.abs(end.x - start.x)}px`,
height: `${Math.abs(end.y - start.y) + (ref.current?.scrollTop || 0)}px`,
display: isSelecting ? "block" : "none",
pointerEvents: "none",
};
Run Code Online (Sandbox Code Playgroud)
JSX 模板
return (
<>
<div onMouseUp={handleMouseUp} onMouseMove={handleMouseMove} className="relative p-4 overflow-auto bg-slate-600 h-96" ref={ref}>
<ul className="flex flex-wrap gap-2">
{data.map((item) => (
<li
onClick={() => {
console.log("a");
}}
className={cx(
"flex items-center justify-center w-24 text-white hover:border-4 hover:border-slate-500 rounded-lg select-none aspect-square bg-slate-700 selectable cursor-pointer",
{ "border-sky-500 border-4 hover:border-sky-500": item.selected }
)}
key={item.item}
>
Item {item.item}
</li>
))}
</ul>
<div style={overlayStyle}></div>
</div>
<button
onClick={() => {
console.log(ref);
}}
>
1a
</button>
</>
);
Run Code Online (Sandbox Code Playgroud)
一切都工作正常,
但问题是,当元素很重要并显示滚动时,选择和选择叠加将不起作用。
感谢您的时间!
实际上,您正在将文档位置与屏幕位置进行比较。如果您的元素占据整个屏幕,您的 clientX 和 screenX 对于鼠标来说基本上是相同的。
当元素滚动离开屏幕顶部时,它将具有负的客户端坐标。您正在添加滚动位置,它给出了元素在文档中的位置。
但是,您的鼠标坐标不会以相同的方式偏移。
您似乎也没有使用正确的滚动值。您有一个滚动元素,但正在添加不滚动的窗口滚动位置。使用元素的scrollX和scrollY,而不是窗口。
您应该能够通过将滚动偏移添加到鼠标坐标来将鼠标坐标保存为文档偏移,然后您将比较所有内容作为文档坐标。
在保存坐标时使用滚动偏移,因此
setStart({ x: e.clientX + ref.current.scrollLeft, y: e.clientY + ref.current.scrollTop });
Run Code Online (Sandbox Code Playgroud)
您可能还应该使用状态作为结束位置。在接下来的渲染之前,它实际上不会更新坐标,因此您的选择将是 1 渲染并且鼠标移到后面。您是否已经注意到并想知道为什么?
做:
const endX = e.clientX + ref.current.scrollLeft;
const endY = e.clientY + ref.current.scrollTop;
setEnd({x: endX, y: endY});
Run Code Online (Sandbox Code Playgroud)
并在下面的计算中使用这些值。它们将是最新的(您仍然需要setEnd对覆盖进行操作,但是您的覆盖计算也应该改变。如果它是绝对定位的,它应该随窗口滚动,所以只需使用文档位置,不用担心将其剪辑到屏幕上)
const elementRect = {
left: rect.left + ref.current.scrollLeft,
top: rect.top + ref.current.scrollTop,
right: rect.right + ref.current.scrollLeft,
bottom: rect.bottom + ref.current.scrollTop,
};
selected[i].selected = ((elementRect.left >= Math.min(start.x, endX) && elementRect.left <= Math.max(start.x, endX)) ||
(elementRect.right >= Math.min(start.x, endX) && elementRect.right <= Math.max(start.x, endX))) &&
((elementRect.top >= Math.min(start.y, endY) && elementRect.top <= Math.max(start.y, endY)) ||
(elementRect.bottom >= Math.min(start.y, endY) && elementRect.bottom <= Math.max(start.y, endY)))
);
// for your overlay, if you make it a child of your main
// element, it will work better (set zIndex). Otherwise,
// you need to do a more complex rectangle intersection than this.
left: `${Math.min(start.x, end.x)}px`,
top: `${Math.min(start.y, end.y)}px`,
width: `${Math.abs(end.x - start.x)}px`,
height: `${Math.abs(end.y - start.y)}px`,
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
522 次 |
| 最近记录: |