React-dnd: Подключение monitor.getClientOffset к DropTarget не обновляет реквизиты

Созданный на 3 июн. 2015  ·  22Комментарии  ·  Источник: react-dnd/react-dnd

Я создал DropTarget и подключил getClientOffset следующим образом:

targetCollect = function (connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    clientOffset: monitor.getClientOffset()
  }
}

Однако при перемещении перетаскивания внутри компонента цель не получает обновленные свойства при каждом движении мыши (только при входе и выходе). Однако указатель мыши для целевой спецификации вызывается неоднократно. Есть ли причина, по которой свойство clientOffset не вводится каждый раз при его изменении?

bug wontfix

Самый полезный комментарий

+1. Я столкнулся с этим и потратил много времени, пытаясь понять, почему getClientOffset () отлично работает в canDrop () - который вызывается неоднократно ... - но нет способа передать моему контролю «где» над элементом, который мы находимся. Мне нужно иметь возможность вставлять выше и ниже treenode, а не только «на» него. У canDrop () нет способа, который я могу найти, который позволил бы мне фактически сообщить моему компоненту, какой из многих «типов» отбрасывания имеет узел, чтобы мой элемент управления мог соответственно повторно отображаться. Изменение стойки в canDrop привело к ошибке. Подписка на onMouseMove не работает во время операций перетаскивания. До сих пор это была огромная трата времени ... любая помощь будет признательна.

@ibash , не могли бы вы опубликовать суть того, что вы сделали для решения своей проблемы?

Все 22 Комментарий

О, хороший момент. Правильно, я не ожидал такого использования. В настоящее время единственный способ включить отслеживание смещения клиента - использовать DragLayer . В противном случае, из соображений производительности, он обновляет свойства только в том случае, если что-то, касающееся самой _ цели_ drop, изменилось.

Я бы сказал, что это проблема API, и может быть несколько решений:

  • Запретите доступ к getClientOffset() и подобным методам изнутри функции collect и скажите вместо этого использовать DragLayer (легко, но глупо);
  • Автоматически определить, что пользователь хочет отслеживать смещение, и подписаться на изменения смещения, если это так (сложнее, но более согласованно).

Я бы согласился с пиаром по второму варианту. Вы можете проверить реализацию DragLayer чтобы увидеть, как она подписывается на изменения смещения.

Я рассмотрел второй вариант, но это сложно, поскольку сбор - это функция, поэтому невозможно / легко узнать, какие функции монитора могут быть вызваны.

Мой вариант использования - это перетаскивание виджетов, которые привязаны к определенному месту, возможно, смещение других виджетов относительно сложным способом (подумайте о перетаскивании значков программ на Android). Это действительно цель, которая должна перестраиваться в зависимости от точного местоположения наведения, в то время как само перетаскивание совершенно нормально в качестве снимка HTML5. Я могу сделать это с помощью наведения указателя на целевую спецификацию, обновляя состояние целевого компонента. Так что я оставлю это пока и работаю над этим. Спасибо за прекрасную библиотеку; У меня есть что-то приличное, что работает за несколько часов работы.

Давайте пока оставим это открытым. Это то же самое, что и # 172: я не ожидал, что люди будут использовать дельты внутри canDrop или функции сбора. Я не собираюсь исправлять это сразу, но, возможно, через месяц или около того я освобожусь.

Я тоже столкнулся с этой проблемой. Что вы думаете о том, чтобы разрешить клиентам передавать в спецификацию DropTarget флаг для подписки на изменения смещения? Это еще один "подводный камень" для пользователя, но это проще, чем вручную подписываться / отказываться от подписки на смещения.

Не могли бы вы, возможно, использовать RxJS-DOM для наблюдения за событием dragover на элементе и обновления состояния с помощью координат мыши? Возможно хакерское решение.

  componentDidMount() {
    this._events.dragOver = DOM.fromEvent(findDOMNode(this.refs.grid), 'dragover')
                            .throttle(200)
                            .subscribe(this.getMouseCoords)
  }

В итоге я взял монитор и подписался на изменения смещения, такие как DragLayer. Я также закончил тем, что местами подключился к внутреннему реестру. Я не думаю, что то, что я хочу сделать, _ слишком необычно, поэтому, IMO, это можно решить с помощью простых изменений api.

Что-нибудь из этого вышло? Я хотел бы отслеживать смещение клиента в моем DropTarget, а не в настраиваемом слое перетаскивания, потому что мне кажется, что легче изменить структуру DropTarget, когда я могу получить доступ к месту наведения из DropTarget вместо настраиваемого слоя перетаскивания.

+1 Я бы хотел сделать именно то, что пытается сделать @jchristman .

+1. Я столкнулся с этим и потратил много времени, пытаясь понять, почему getClientOffset () отлично работает в canDrop () - который вызывается неоднократно ... - но нет способа передать моему контролю «где» над элементом, который мы находимся. Мне нужно иметь возможность вставлять выше и ниже treenode, а не только «на» него. У canDrop () нет способа, который я могу найти, который позволил бы мне фактически сообщить моему компоненту, какой из многих «типов» отбрасывания имеет узел, чтобы мой элемент управления мог соответственно повторно отображаться. Изменение стойки в canDrop привело к ошибке. Подписка на onMouseMove не работает во время операций перетаскивания. До сих пор это была огромная трата времени ... любая помощь будет признательна.

@ibash , не могли бы вы опубликовать суть того, что вы сделали для решения своей проблемы?

Наконец, переключение на использование зависания сделало это для меня.

`hover: (props: MyProps, monitor: DropTargetMonitor, component: ReportItemRow) => {
if (component! = null) {
const clientOffset = monitor.getClientOffset ();

        // Is the dragging node dragged above/below us as opposed to "on" us?

        const elementRect = document.elementFromPoint(clientOffset.x, clientOffset.y).getBoundingClientRect();

        const percentageY = (clientOffset.y - elementRect.top) / elementRect.height;

        // Divide the box up into .25 .5 .25
        const insertDragAndDropMagnetPercent = .25;

        if (insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "above" });
        }
        else if (1 - insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "inside" });
        }
        else {
            component.setState({ dropTarget: "below" });
        }
    }
},`

@ noah79 , пока вы ждете, что из этого что-то выйдет, вы можете решить свою проблему, создав несколько целей для перетаскивания и поместив их под свои тройные узлы. Что-то вроде этого:

         _____
         _____| <-- drop div1 (above treenode1/below top of list)
treenode1_____| <-- drop div2 (on treenode1)
         _____| <-- drop div3 (below treenode1/above treenode2)
treenode2_____| <-- drop div4 (on treenode 2)
         _____| <-- drop div5 (below treenode2/above bottom of list)

В итоге получается 2n + 1 целей перетаскивания, где n - количество элементов в вашем списке. Затем, в зависимости от того, над каким из div наведен курсор, вы можете изменить внешний вид древовидного списка. Я сделал что-то очень похожее на это, чтобы обойтись без доступа к getClientOffset () на данный момент, и я не заметил снижения производительности. Высота каждого drop-div должна быть 1/2 высоты строки treenode1, а первый drop-div должен располагаться на 1/4 высоты строки treenode1 выше, чем первая строка. То есть ваш CSS должен выглядеть так:

.dropdiv-container {
    position: absolute;
    top: -0.25em; /* If the top of this container is aligned with the top of the treenode list initially */
}
.dropdiv {
    height: 0.5em; /* If the height of treenode1 line is 1em with no padding */
}
<div className='dropdiv-container'>
    { renderDropTargets(2 * listLength + 1) }
</div>

Имеет ли это смысл?

Вот суть того, как я это решил. Хитрость в том, чтобы просто проникнуть в
react-dnd internals и напрямую использовать глобальный монитор. Взгляните на
Перетащите исходный код DragDropMonitor.js, чтобы получить представление о доступных методах.
ссылка: https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

Придется извинить за кофейный скрипт :)

  # in the component being dragged, get access to the dragDropManager by adding
  # it to the contextTypes
  #
  # dragDropManager is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropManager.js

  <strong i="11">@contextTypes</strong>: {
    dragDropManager: React.PropTypes.object
  }

  # because we can receive events after the component is unmounted, we need to
  # keep track of whether the component is mounted manually.
  #
  # <strong i="12">@_monitor</strong> is what lets us access all the nice internal state - it is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

  componentWillMount: () =>
    <strong i="13">@_isMounted</strong> = true
    <strong i="14">@_monitor</strong> = @context.dragDropManager.getMonitor()

    <strong i="15">@_unsubscribeToStateChange</strong> = @_monitor.subscribeToStateChange(@_onStateChange)
    <strong i="16">@_unsubscribeToOffsetChange</strong> = @_monitor.subscribeToOffsetChange(@_onOffsetChange)

  componentWillUnmount: () =>
    <strong i="17">@_isMounted</strong> = false
    <strong i="18">@_monitor</strong> = null

    @_unsubscribeToStateChange()
    @_unsubscribeToOffsetChange()

  # we can access dragging / dropping state by accessing the monitor 

  _onStateChange: () =>
    return unless <strong i="19">@_isMounted</strong>

    # When we stop dragging reset the counter state and hide all cursors.
    if <strong i="20">@_previousIsDragging</strong> && !@_monitor.isDragging()
      console.log('no longer dragging')
    <strong i="21">@_previousIsDragging</strong> = @_monitor.isDragging()

  _onOffsetChange: () =>
    return unless <strong i="22">@_isMounted</strong>

    # mouse is the x/y coordinates
    mouse = @_monitor.getClientOffset()

    # item is the drag item
    item = @_monitor.getItem()

    # if you want to check if a dragged item is over a target, you need the
    # targetId -- in the DropTarget wrapper you can pass it in like:
    #
    #   (connect, monitor) ->
    #     {
    #       targetId: monitor.targetId,
    #       connectDropTarget: connect.dropTarget()
    #     }
    #
    # and then you can use it like below

    @_monitor.isOverTarget(@props.targetId))

Имеет ли это смысл? Если нет, я могу добавить больше деталей

Я тоже столкнулся с этим сегодня и потратил впустую несколько часов. Как насчет того, чтобы оставить примечание об этом в документации в разделе DropTarget -> Функция сбора? Это, по крайней мере, избавило бы некоторых других от разочарований.

Кстати. Еще один действительно уродливый способ решения этой проблемы - отправить координаты _as state_ из dropTarget.hover() :

const dropTarget = {
    hover(props, monitor, component) {
        // HACK! Since there is an open bug in react-dnd, making it impossible
        // to get the current client offset through the collect function as the
        // user moves the mouse, we do this awful hack and set the state (!!)
        // on the component from here outside the component.
        component.setState({
            currentDragOffset: monitor.getClientOffset(),
        });
    },
    drop() { /* ... */ },
};

Конечно, есть масса более правильных способов сделать это, чтобы избежать такого злоупотребления setState. Но, по крайней мере, этот небольшой прием очень сжат и может работать до тех пор, пока эта проблема не будет в конечном итоге исправлена. Это позволяет вам обойти ошибку, не изменяя другие компоненты и / или файлы и не полагаясь на внутренние компоненты библиотеки.

Изменить: это в основном то же самое, что и noah79, я просто не читал его код раньше.

Неужели нет способа получить позицию мыши при падении? Моему целевому компоненту не нужно об этом знать, поэтому setState для меня не вариант. Мне просто нужны координаты, чтобы вызвать действие redux.

Пожалуйста, не обращайте внимания на мое последнее сообщение. Теперь я вижу, что drop и endDrag - это две разные части функциональности, и получение позиционирования курсора в drop работает должным образом. :)

Согласно моим тестам, response-dnd использует события перемещения мыши, как только элемент перетаскивается над целью. Если бы для html-backend было возможно не использовать это событие, то целевой компонент мог бы просто условно разместить обычный слушатель mousemove на своем dom, когда для свойства isOver установлено значение true. После этого этот слушатель может уловить позицию мыши (обычным способом), когда что-то перетаскивают по нему. Я заставил это работать временно, используя setState из метода drag (). Реакция дает предупреждения об установке состояния в середине перехода рендеринга.

Это меня тоже сбило с толку. +1 за решение API или хотя бы что-то в FAQ.

Есть ли план поддержать это?
У меня есть идея обходного пути без взлома: создать оболочку для DropTarget которая также обертывает hover чтобы вызвать другой вызов функции сбора и повторный рендеринг, если функция сбора возвращает новые значения .

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

Я наткнулся на эту проблему, исследуя, почему смещения не обновляются, похоже, что все еще нужно обходное решение.
Я подхватил идею DropTarget , но принимает в своих опциях collectRapidly , который представляет собой список реквизитов для запроса через функцию сбора + передачу при каждом событии наведения. Передача всех свойств без разбора из функции сбора вызвала некоторую странность, и мы вообще не можем передать connect сборщику, поэтому есть меры против запроса connectDropTarget "быстро".

Поэтому вместо DropTarget(types, target, collect) вы должны использовать RapidFireDropTarget(types, target, collect, {collectRapidly: ['offset']}) , где offset предположительно является чем-то, получающим свое значение из семейства функций monitor.getOffset .

import React from 'react';
import {DropTarget} from 'react-dnd';
import pick from 'lodash/pick';

function RapidFireDropTarget(types, spec, collect, options={}) {
  let collectProps = {};
  const prevHover = spec.hover;
  const {collectRapidly = []} = options;

  const dummyConnect = {
    dropTarget: () => () => {
      throw new Error('Rapidly collected props cannot include results from `connect`');
    }
  };

  const WrappedComponent = Component => {
    return class extends React.Component {
      render() {
        return <Component {...this.props} {...collectProps} />;
      }
    };
  };

  spec.hover = (props, monitor, component) => {
    const allCollectProps = collect(dummyConnect, monitor);

    collectProps = pick(allCollectProps, collectRapidly);
    component.forceUpdate();

    if (prevHover instanceof Function) {
      prevHover(props, monitor, component);
    }
  }

  return (Component) => {
    return DropTarget(types, spec, collect, options)(WrappedComponent(Component));
  };
}

export default RapidFireDropTarget;

Эта проблема была автоматически помечена как устаревшая, поскольку в последнее время не было активности. Он будет закрыт, если больше не будет активности. Спасибо за ваш вклад.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги