Я пытаюсь реализовать выбор перетаскивания в реакции с нуля, но застрял в прокрутке

Вот код, который я сделал для реализации выбора перетаскивания.

Код генерации элемента


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

Это фактический код, отвечающий за выбор

    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.info("START");
        console.info({ clientX: start.x, clientY: start.y, screenX: start.screenX, screenY: start.screenY });
        console.info("END");
        console.info({ 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.info("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.info(ref);
                }}
            >
                1a
            </button>
        </>
    );

Все работает корректно,

Но дело в том, что когда элементы значимы и показывают прокрутку, он не будет работать как выбор, так и наложение выбора.

Спасибо за ваше время!

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
0
138
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

фактически вы сравниваете положение документа с положением на экране. Ваши clientX и screenX будут в основном одинаковыми для мыши, если ваш элемент занимает весь экран.

Когда элемент прокручивается за пределы верхней части экрана, он будет иметь отрицательную клиентскую координату. Вы добавляете позицию прокрутки, которая указывает позицию элемента в документе.

Однако координаты вашей мыши не смещаются таким же образом.

Вы также, кажется, не используете правильное значение прокрутки. У вас есть элемент прокрутки, но вы добавляете позицию прокрутки окна, которая не прокручивается. Используйте scrollX и scrollY элемента, а не окна.

Вы должны иметь возможность сохранить координаты мыши как смещения документа, добавив к ним также смещения прокрутки, тогда вы будете сравнивать все как координаты документа.

Используйте смещение прокрутки во время сохранения координат, поэтому

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

Вы также, вероятно, должны использовать состояние для конечной позиции. На самом деле он не будет обновлять координаты до следующего рендеринга, поэтому ваш выбор будет на 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`,

Другие вопросы по теме