下面是我用来实现拖动 Select 的代码.

项生成代码


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 }));

这是负责 Select 的实际代码

    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 });
    };

生成覆盖

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",
    };

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>
        </>
    );

一切都运行正常,

但问题是,当元素是重要的,并显示滚动它将不会工作 Select 以及 Select 覆盖.

Preview

谢谢您抽时间见我!

推荐答案

实际上,您是在比较文档位置和屏幕位置.如果你的元素占据了整个屏幕,你的客户端X和屏幕X对于鼠标来说基本上是一样的.

当元素滚动出屏幕顶部时,它将具有负的工作端坐标.您添加的是滚动位置,它提供了元素在文档中的位置.

但是,您的鼠标坐标的偏移方式并不相同.

您似乎也没有使用正确的滚动值.您有一个滚动元素,但添加的是不滚动的窗口滚动位置.使用元素的scrollXscrollY,而不是窗口.

您应该能够通过将滚动偏移量添加到文档偏移量来将鼠标坐标保存为文档偏移量,然后您就可以将所有内容作为文档坐标进行比较.

在保存坐标时使用滚动偏移量,因此

setStart({ x: e.clientX + ref.current.scrollLeft, y: e.clientY + ref.current.scrollTop });

您可能还应该使用STATE作为结束位置.直到下一次渲染之前,它不会实际更新坐标,所以您的 Select 将是1次渲染,鼠标向后移动.你是否已经注意到并想知道为什么?

const endX = e.clientX + ref.current.scrollLeft;
const endY = e.clientY + ref.current.scrollTop;
setEnd({x: endX, y: endY});

并在下面的计算中使用这些值.它们将更新(您仍然需要为您的覆盖做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`,

Javascript相关问答推荐

CSS背景过滤器忽略转换持续时间(ReactJS)

当我使用jQuery时,我的图标显示为[对象对象]

单击树元素时阻止焦点事件触发?

如何将特定的字符串类型转换为TypScript中的字符串?

为什么(1 + Number.Min_UTE)等于1?

错误:找不到react-redux上下文值;请确保该组件包装在React原生Expo应用程序中的提供者中

如何提取Cypress中文本

Plotly热图:在矩形上zoom 后将zoom 区域居中

Phaser 3 console. log()特定游戏角色的瓷砖属性

Angular material 拖放堆叠的牌副,悬停时自动展开&

如何禁用附加图标点击的v—自动完成事件

S文本内容和值不必要的不同

从包含数百行的表中获取更改后的值(以表单形式发送到后端)的正确方法是什么?

我在Django中的视图中遇到多值键错误

如何使用JavaScript拆分带空格的单词

对路由DOM嵌套路由作出react

处理app.param()中的多个参数

在JS/TS中将复杂图形转换为数组或其他数据 struct

如何通过Axios在GraphQL查询中发送数组

JavaScript将字符串数字转换为整数