Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 使React组件/分区可拖动的推荐方法_Javascript_Reactjs - Fatal编程技术网

Javascript 使React组件/分区可拖动的推荐方法

Javascript 使React组件/分区可拖动的推荐方法,javascript,reactjs,Javascript,Reactjs,我想制作一个可拖动(即,可通过鼠标重新定位)的React组件,它似乎必然涉及全局状态和分散的事件处理程序。我可以用肮脏的方式,在我的JS文件中使用一个全局变量,甚至可以将它包装在一个漂亮的闭包界面中,但我想知道是否有一种方法可以更好地与网格交互 此外,由于我以前从未在原始JavaScript中这样做过,我想看看专家是如何做到这一点的,以确保我已经处理了所有的关键案例,尤其是与React相关的案例 谢谢。我可能应该把这篇文章变成一篇博客文章,但这里有一个很好的例子 这些评论应该能很好地解释事情,但

我想制作一个可拖动(即,可通过鼠标重新定位)的React组件,它似乎必然涉及全局状态和分散的事件处理程序。我可以用肮脏的方式,在我的JS文件中使用一个全局变量,甚至可以将它包装在一个漂亮的闭包界面中,但我想知道是否有一种方法可以更好地与网格交互

此外,由于我以前从未在原始JavaScript中这样做过,我想看看专家是如何做到这一点的,以确保我已经处理了所有的关键案例,尤其是与React相关的案例


谢谢。

我可能应该把这篇文章变成一篇博客文章,但这里有一个很好的例子

这些评论应该能很好地解释事情,但是如果你有问题,请告诉我

这是一把小提琴:

关于国家所有制等问题的思考。 “谁应该拥有什么样的国家”从一开始就是一个需要回答的重要问题。在“可拖动”组件的情况下,我可以看到一些不同的场景

情景1 父对象应拥有可拖动对象的当前位置。在这种情况下,draggable仍将拥有“我正在拖动”状态,但每当mousemove事件发生时,它将调用
this.props.onChange(x,y)

情景2 父对象只需要拥有“非移动位置”,因此可拖动对象将拥有其“拖动位置”,但在MouseUp上,它将调用
this.props.onChange(x,y)
,并将最终决定推迟给父对象。如果父级不喜欢拖动表的结束位置,它将不更新其状态,并且拖动表将在拖动前“快速恢复”到其初始位置

混合物还是成分?
@ssorallen指出,由于“draggable”与其说是事物本身,不如说是一个属性,因此它可能更适合作为一个混音。我对mixin的经验是有限的,所以我还没有看到它们在复杂的情况下如何帮助或阻碍。这可能是最好的选择。

我想添加一个第三种方案

移动位置不会以任何方式保存。可以将其视为鼠标移动-您的光标不是React组件,对吗

您所要做的就是向组件添加一个类似于“draggable”的道具,以及一个将操纵dom的拖动事件流

setXandY: function(event) {
    // DOM Manipulation of x and y on your node
},

componentDidMount: function() {
    if(this.props.draggable) {
        var node = this.getDOMNode();
        dragStream(node).onValue(this.setXandY);  //baconjs stream
    };
},
在这种情况下,DOM操作是一件优雅的事情(我从来没有想过我会这么说)

我实现了,一种灵活的HTML5拖放混合,用于使用完整的DOM控件进行反应

现有的拖放库不适合我的用例,所以我写了自己的。它类似于我们在Stampsy.com上运行了大约一年的代码,但被重写以利用React和Flux

我的主要要求是:

  • 发出自己的零DOM或CSS,将其留给消费组件
  • 对消费组件施加尽可能少的结构
  • 使用HTML5拖放作为主要后端,但可以在将来添加不同的后端
  • 像最初的HTML5API一样,强调拖动数据,而不仅仅是“可拖动的视图”
  • 从消费代码中隐藏HTML5API的怪癖
  • 对于不同类型的数据,不同的组件可能是“拖放源”或“拖放目标”
  • 允许一个组件包含多个拖放源,并在需要时拖放目标
  • 使拖放目标在拖动或悬停兼容数据时易于更改其外观
  • 可以轻松地使用图像来拖动缩略图,而不是元素屏幕截图,从而避免浏览器的怪癖
如果这些听起来很熟悉,请继续阅读

用法 简单阻力源 首先,声明可以拖动的数据类型

这些用于检查拖放源和拖放目标的“兼容性”:

// ItemTypes.js
module.exports = {
  BLOCK: 'block',
  IMAGE: 'image'
};
(如果您没有多种数据类型,则此库可能不适合您。)

然后,让我们制作一个非常简单的可拖动组件,当拖动该组件时,它表示
图像

var { DragDropMixin } = require('react-dnd'),
    ItemTypes = require('./ItemTypes');

var Image = React.createClass({
  mixins: [DragDropMixin],

  configureDragDrop(registerType) {

    // Specify all supported types by calling registerType(type, { dragSource?, dropTarget? })
    registerType(ItemTypes.IMAGE, {

      // dragSource, when specified, is { beginDrag(), canDrag()?, endDrag(didDrop)? }
      dragSource: {

        // beginDrag should return { item, dragOrigin?, dragPreview?, dragEffect? }
        beginDrag() {
          return {
            item: this.props.image
          };
        }
      }
    });
  },

  render() {

    // {...this.dragSourceFor(ItemTypes.IMAGE)} will expand into
    // { draggable: true, onDragStart: (handled by mixin), onDragEnd: (handled by mixin) }.

    return (
      <img src={this.props.image.url}
           {...this.dragSourceFor(ItemTypes.IMAGE)} />
    );
  }
);
在一个组件中拖动源+拖放目标 假设我们现在希望用户能够从
ImageBlock
中拖出图像。我们只需要向其添加适当的
dragSource
,并添加一些处理程序:

var { DragDropMixin } = require('react-dnd'),
    ItemTypes = require('./ItemTypes');

var ImageBlock = React.createClass({
  mixins: [DragDropMixin],

  configureDragDrop(registerType) {

    registerType(ItemTypes.IMAGE, {

      // Add a drag source that only works when ImageBlock has an image:
      dragSource: {
        canDrag() {
          return !!this.props.image;
        },

        beginDrag() {
          return {
            item: this.props.image
          };
        }
      }

      dropTarget: {
        acceptDrop(image) {
          DocumentActionCreators.setImage(this.props.blockId, image);
        }
      }
    });
  },

  render() {

    return (
      <div {...this.dropTargetFor(ItemTypes.IMAGE)}>

        {/* Add {...this.dragSourceFor} handlers to a nested node */}
        {this.props.image &&
          <img src={this.props.image.url}
               {...this.dragSourceFor(ItemTypes.IMAGE)} />
        }
      </div>
    );
  }
);
var{DragDropMixin}=require('react-dnd'),
ItemTypes=require('./ItemTypes');
var ImageBlock=React.createClass({
混合物:[DragDropMixin],
配置DragDrop(注册表类型){
registerType(ItemTypes.IMAGE{
//添加仅在ImageBlock具有图像时可用的拖动源:
dragSource:{
坎德拉格(){
return!!this.props.image;
},
开始{
返回{
项目:this.props.image
};
}
}
dropTarget:{
acceptDrop(图像){
DocumentActionCreators.setImage(this.props.blockId,image);
}
}
});
},
render(){
返回(
{/*将{…this.dragSourceFor}处理程序添加到嵌套节点*/}
{this.props.image&&
}
);
}
);
还有什么可能? 我并没有涵盖所有内容,但可以通过以下几种方式使用此API:

  • 使用
    getDragState(type)
    getDropState(type)
    了解拖动是否处于活动状态,并使用它切换CSS类或属性
  • 指定
    dragPreview
    Image
    以使用图像作为拖动占位符(使用
    imagepredermixin
    加载图像)
  • 比如说,我们想使
    ImageBlocks
    可重新排序。我们只需要它们为
    项目类型.BLOCK
    实现
    dropTarget
    dragSource
  • 假设我们添加其他类型的块,我们可以通过将其放置在mixin中重用它们的重新排序逻辑
  • dropTargetFor(…类型)
    允许一次指定多个类型,因此一个放置区域可以捕获多个不同的类型
  • 当您需要更细粒度的控制时
    var { DragDropMixin } = require('react-dnd'),
        ItemTypes = require('./ItemTypes');
    
    var ImageBlock = React.createClass({
      mixins: [DragDropMixin],
    
      configureDragDrop(registerType) {
    
        registerType(ItemTypes.IMAGE, {
    
          // dropTarget, when specified, is { acceptDrop(item)?, enter(item)?, over(item)?, leave(item)? }
          dropTarget: {
            acceptDrop(image) {
              // Do something with image! for example,
              DocumentActionCreators.setImage(this.props.blockId, image);
            }
          }
        });
      },
    
      render() {
    
        // {...this.dropTargetFor(ItemTypes.IMAGE)} will expand into
        // { onDragEnter: (handled by mixin), onDragOver: (handled by mixin), onDragLeave: (handled by mixin), onDrop: (handled by mixin) }.
    
        return (
          <div {...this.dropTargetFor(ItemTypes.IMAGE)}>
            {this.props.image &&
              <img src={this.props.image.url} />
            }
          </div>
        );
      }
    );
    
    var { DragDropMixin } = require('react-dnd'),
        ItemTypes = require('./ItemTypes');
    
    var ImageBlock = React.createClass({
      mixins: [DragDropMixin],
    
      configureDragDrop(registerType) {
    
        registerType(ItemTypes.IMAGE, {
    
          // Add a drag source that only works when ImageBlock has an image:
          dragSource: {
            canDrag() {
              return !!this.props.image;
            },
    
            beginDrag() {
              return {
                item: this.props.image
              };
            }
          }
    
          dropTarget: {
            acceptDrop(image) {
              DocumentActionCreators.setImage(this.props.blockId, image);
            }
          }
        });
      },
    
      render() {
    
        return (
          <div {...this.dropTargetFor(ItemTypes.IMAGE)}>
    
            {/* Add {...this.dragSourceFor} handlers to a nested node */}
            {this.props.image &&
              <img src={this.props.image.url}
                   {...this.dragSourceFor(ItemTypes.IMAGE)} />
            }
          </div>
        );
      }
    );
    
    import React, {Component} from 'react';
    import ReactDOM from 'react-dom';
    import Draggable from 'react-draggable';
    
    var App = React.createClass({
        render() {
            return (
                <div>
                    <h1>Testing Draggable Windows!</h1>
                    <Draggable handle="strong">
                        <div className="box no-cursor">
                            <strong className="cursor">Drag Here</strong>
                            <div>You must click my handle to drag me</div>
                        </div>
                    </Draggable>
                </div>
            );
        }
    });
    
    ReactDOM.render(
        <App />, document.getElementById('content')
    );
    
    <html>
        <head>
            <title>Testing Draggable Windows</title>
            <link rel="stylesheet" type="text/css" href="style.css" />
        </head>
        <body>
            <div id="content"></div>
            <script type="text/javascript" src="bundle.js" charset="utf-8"></script>    
        <script src="http://localhost:8080/webpack-dev-server.js"></script>
        </body>
    </html>
    
    const throttle = (f) => {
        let token = null, lastArgs = null;
        const invoke = () => {
            f(...lastArgs);
            token = null;
        };
        const result = (...args) => {
            lastArgs = args;
            if (!token) {
                token = requestAnimationFrame(invoke);
            }
        };
        result.cancel = () => token && cancelAnimationFrame(token);
        return result;
    };
    
    class Draggable extends React.PureComponent {
        _relX = 0;
        _relY = 0;
        _ref = React.createRef();
    
        _onMouseDown = (event) => {
            if (event.button !== 0) {
                return;
            }
            const {scrollLeft, scrollTop, clientLeft, clientTop} = document.body;
            // Try to avoid calling `getBoundingClientRect` if you know the size
            // of the moving element from the beginning. It forces reflow and is
            // the laggiest part of the code right now. Luckily it's called only
            // once per click.
            const {left, top} = this._ref.current.getBoundingClientRect();
            this._relX = event.pageX - (left + scrollLeft - clientLeft);
            this._relY = event.pageY - (top + scrollTop - clientTop);
            document.addEventListener('mousemove', this._onMouseMove);
            document.addEventListener('mouseup', this._onMouseUp);
            event.preventDefault();
        };
    
        _onMouseUp = (event) => {
            document.removeEventListener('mousemove', this._onMouseMove);
            document.removeEventListener('mouseup', this._onMouseUp);
            event.preventDefault();
        };
    
        _onMouseMove = (event) => {
            this.props.onMove(
                event.pageX - this._relX,
                event.pageY - this._relY,
            );
            event.preventDefault();
        };
    
        _update = throttle(() => {
            const {x, y} = this.props;
            this._ref.current.style.transform = `translate(${x}px, ${y}px)`;
        });
    
        componentDidMount() {
            this._ref.current.addEventListener('mousedown', this._onMouseDown);
            this._update();
        }
    
        componentDidUpdate() {
            this._update();
        }
    
        componentWillUnmount() {
            this._ref.current.removeEventListener('mousedown', this._onMouseDown);
            this._update.cancel();
        }
    
        render() {
            return (
                <div className="draggable" ref={this._ref}>
                    {this.props.children}
                </div>
            );
        }
    }
    
    class Test extends React.PureComponent {
        state = {
            x: 100,
            y: 200,
        };
    
        _move = (x, y) => this.setState({x, y});
    
        // you can implement grid snapping logic or whatever here
        /*
        _move = (x, y) => this.setState({
            x: ~~((x - 5) / 10) * 10 + 5,
            y: ~~((y - 5) / 10) * 10 + 5,
        });
        */
    
        render() {
            const {x, y} = this.state;
            return (
                <Draggable x={x} y={y} onMove={this._move}>
                    Drag me
                </Draggable>
            );
        }
    }
    
    ReactDOM.render(
        <Test />,
        document.getElementById('container'),
    );
    
    .draggable {
        /* just to size it to content */
        display: inline-block;
        /* opaque background is important for performance */
        background: white;
        /* avoid selecting text while dragging */
        user-select: none;
    }
    
    import React from 'react';
    import ReactDOM from 'react-dom';
    import PropTypes from 'prop-types';
    
    class Draggable extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                relX: 0,
                relY: 0,
                x: props.x,
                y: props.y
            };
            this.gridX = props.gridX || 1;
            this.gridY = props.gridY || 1;
            this.onMouseDown = this.onMouseDown.bind(this);
            this.onMouseMove = this.onMouseMove.bind(this);
            this.onMouseUp = this.onMouseUp.bind(this);
            this.onTouchStart = this.onTouchStart.bind(this);
            this.onTouchMove = this.onTouchMove.bind(this);
            this.onTouchEnd = this.onTouchEnd.bind(this);
        }
    
        static propTypes = {
            onMove: PropTypes.func,
            onStop: PropTypes.func,
            x: PropTypes.number.isRequired,
            y: PropTypes.number.isRequired,
            gridX: PropTypes.number,
            gridY: PropTypes.number
        }; 
    
        onStart(e) {
            const ref = ReactDOM.findDOMNode(this.handle);
            const body = document.body;
            const box = ref.getBoundingClientRect();
            this.setState({
                relX: e.pageX - (box.left + body.scrollLeft - body.clientLeft),
                relY: e.pageY - (box.top + body.scrollTop - body.clientTop)
            });
        }
    
        onMove(e) {
            const x = Math.trunc((e.pageX - this.state.relX) / this.gridX) * this.gridX;
            const y = Math.trunc((e.pageY - this.state.relY) / this.gridY) * this.gridY;
            if (x !== this.state.x || y !== this.state.y) {
                this.setState({
                    x,
                    y
                });
                this.props.onMove && this.props.onMove(this.state.x, this.state.y);
            }        
        }
    
        onMouseDown(e) {
            if (e.button !== 0) return;
            this.onStart(e);
            document.addEventListener('mousemove', this.onMouseMove);
            document.addEventListener('mouseup', this.onMouseUp);
            e.preventDefault();
        }
    
        onMouseUp(e) {
            document.removeEventListener('mousemove', this.onMouseMove);
            document.removeEventListener('mouseup', this.onMouseUp);
            this.props.onStop && this.props.onStop(this.state.x, this.state.y);
            e.preventDefault();
        }
    
        onMouseMove(e) {
            this.onMove(e);
            e.preventDefault();
        }
    
        onTouchStart(e) {
            this.onStart(e.touches[0]);
            document.addEventListener('touchmove', this.onTouchMove, {passive: false});
            document.addEventListener('touchend', this.onTouchEnd, {passive: false});
            e.preventDefault();
        }
    
        onTouchMove(e) {
            this.onMove(e.touches[0]);
            e.preventDefault();
        }
    
        onTouchEnd(e) {
            document.removeEventListener('touchmove', this.onTouchMove);
            document.removeEventListener('touchend', this.onTouchEnd);
            this.props.onStop && this.props.onStop(this.state.x, this.state.y);
            e.preventDefault();
        }
    
        render() {
            return <div
                onMouseDown={this.onMouseDown}
                onTouchStart={this.onTouchStart}
                style={{
                    position: 'absolute',
                    left: this.state.x,
                    top: this.state.y,
                    touchAction: 'none'
                }}
                ref={(div) => { this.handle = div; }}
            >
                {this.props.children}
            </div>;
        }
    }
    
    export default Draggable;
    
    
    function Draggable() {
      const [ref, x, y, isDragging] = useDragging();
    
      return (
        <div
          ref={ref}
          style={{
            position: "absolute",
            width: 50,
            height: 50,
            background: isDragging ? "blue" : "gray",
            left: x,
            top: y,
          }}
        ></div>
      );
    }