Sunday 14 May 2023

I am trying to implement drag select in react from scratch but stuck in the scroll

Here is the code that I made to implement drag selection.

Item generate code


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

This the actual code responsible for the 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 });
    };

Generate the overlay

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 Templete


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

All are working correctly,

But the thing is when the elements are significant and show scroll it will not working select as well as the selection overlay.

Preview

Thank you for your time!



from I am trying to implement drag select in react from scratch but stuck in the scroll

No comments:

Post a Comment