Recommended way of making React component/div draggable

后端 未结 9 695
耶瑟儿~
耶瑟儿~ 2020-11-28 18:13

I want to make a draggable (that is, repositionable by mouse) React component, which seems to necessarily involve global state and scattered event handlers. I can do it the

9条回答
  •  情歌与酒
    2020-11-28 18:40

    I should probably turn this into a blog post, but here's pretty solid example.

    The comments should explain things pretty well, but let me know if you have questions.

    And here's the fiddle to play with: http://jsfiddle.net/Af9Jt/2/

    var Draggable = React.createClass({
      getDefaultProps: function () {
        return {
          // allow the initial position to be passed in as a prop
          initialPos: {x: 0, y: 0}
        }
      },
      getInitialState: function () {
        return {
          pos: this.props.initialPos,
          dragging: false,
          rel: null // position relative to the cursor
        }
      },
      // we could get away with not having this (and just having the listeners on
      // our div), but then the experience would be possibly be janky. If there's
      // anything w/ a higher z-index that gets in the way, then you're toast,
      // etc.
      componentDidUpdate: function (props, state) {
        if (this.state.dragging && !state.dragging) {
          document.addEventListener('mousemove', this.onMouseMove)
          document.addEventListener('mouseup', this.onMouseUp)
        } else if (!this.state.dragging && state.dragging) {
          document.removeEventListener('mousemove', this.onMouseMove)
          document.removeEventListener('mouseup', this.onMouseUp)
        }
      },
    
      // calculate relative position to the mouse and set dragging=true
      onMouseDown: function (e) {
        // only left mouse button
        if (e.button !== 0) return
        var pos = $(this.getDOMNode()).offset()
        this.setState({
          dragging: true,
          rel: {
            x: e.pageX - pos.left,
            y: e.pageY - pos.top
          }
        })
        e.stopPropagation()
        e.preventDefault()
      },
      onMouseUp: function (e) {
        this.setState({dragging: false})
        e.stopPropagation()
        e.preventDefault()
      },
      onMouseMove: function (e) {
        if (!this.state.dragging) return
        this.setState({
          pos: {
            x: e.pageX - this.state.rel.x,
            y: e.pageY - this.state.rel.y
          }
        })
        e.stopPropagation()
        e.preventDefault()
      },
      render: function () {
        // transferPropsTo will merge style & other props passed into our
        // component to also be on the child DIV.
        return this.transferPropsTo(React.DOM.div({
          onMouseDown: this.onMouseDown,
          style: {
            left: this.state.pos.x + 'px',
            top: this.state.pos.y + 'px'
          }
        }, this.props.children))
      }
    })
    

    Thoughts on state ownership, etc.

    "Who should own what state" is an important question to answer, right from the start. In the case of a "draggable" component, I could see a few different scenarios.

    Scenario 1

    the parent should own the current position of the draggable. In this case, the draggable would still own the "am I dragging" state, but would call this.props.onChange(x, y) whenever a mousemove event occurs.

    Scenario 2

    the parent only needs to own the "non-moving position", and so the draggable would own it's "dragging position" but onmouseup it would call this.props.onChange(x, y) and defer the final decision to the parent. If the parent doesn't like where the draggable ended up, it would just not update it's state, and the draggable would "snap back" to it's initial position before dragging.

    Mixin or component?

    @ssorallen pointed out that, because "draggable" is more an attribute than a thing in itself, it might serve better as a mixin. My experience with mixins is limited, so I haven't seen how they might help or get in the way in complicated situations. This might well be the best option.

提交回复
热议问题