react-drag code学习

15 篇文章 0 订阅
6 篇文章 0 订阅

react-drag记录

/*
	bound 为限制位置
*/
'use strict';

var React = require('react');
var findDOMNode = require('react-dom').findDOMNode;
var propTypes = require('prop-types');
var createReactClass = require('create-react-class');

function classNames() {
  var classes = '';
  for (var i = 0; i < arguments.length; i++) {
    var arg = arguments[i];
    if (!arg) continue;
    var argType = typeof arg;
    if ('string' === argType || 'number' === argType) {
      classes += ' ' + arg;
    } else if (Array.isArray(arg)) {
      classes += ' ' + classNames.apply(null, arg);
    } else if ('object' === argType) {
      for (var key in arg) {
        if (arg.hasOwnProperty(key) && arg[key]) {
          classes += ' ' + key;
        }
      }
    }
  }
  return classes.substr(1);
}

var emptyFunction = function () {};
var CX = classNames;

function createUIEvent(draggable) {
  return {
    position: {
      top: draggable.state.pageY,
      left: draggable.state.pageX
    }
  };
}

function canDragY(draggable) {
  return draggable.props.axis === 'both' ||
      draggable.props.axis === 'y';
}

function canDragX(draggable) {
  return draggable.props.axis === 'both' ||
      draggable.props.axis === 'x';
}

function isFunction(func) {
  return typeof func === 'function' ||
    Object.prototype.toString.call(func) === '[object Function]';
}

// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc
function findInArray(array, callback) {
  for (var i = 0, length = array.length, element = null; i < length; i += 1) {
    element = array[i];
    if (callback.apply(callback, [element, i, array])) {
      return element;
    }
  }
}

function matchesSelector(el, selector) {
  var method = findInArray([
    'matches',
    'webkitMatchesSelector',
    'mozMatchesSelector',
    'msMatchesSelector',
    'oMatchesSelector'
  ], function (method) {
    return isFunction(el[method]);
  });

  return el[method].call(el, selector);
}

// @credits:
// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
/* Conditional to fix node server side rendering of component */
var isTouchDevice;
if (typeof window === 'undefined') {
    // Do Node Stuff
  isTouchDevice = false;
} else {
  // Do Browser Stuff
  isTouchDevice = 'ontouchstart' in window // works on most browsers
    || 'onmsgesturechange' in window; // works on ie10 on ms surface
  // Check for IE11
  try {
    document.createEvent('TouchEvent');
  } catch (e) {
    isTouchDevice = false;
  }

}

// look ::handleDragStart
//function isMultiTouch(e) {
//  return e.touches && Array.isArray(e.touches) && e.touches.length > 1
//}

/**
 * simple abstraction for dragging events names
 * */
var dragEventFor = (function () {
  var eventsFor = {
    touch: {
      start: 'touchstart',
      move: 'touchmove',
      end: 'touchend'
    },
    mouse: {
      start: 'mousedown',
      move: 'mousemove',
      end: 'mouseup'
    }
  };
  return eventsFor[isTouchDevice ? 'touch' : 'mouse'];
})();

/**
 * get {pageX, pageY} positions of control
 * */
function getControlPosition(e) {
  var position = (e.touches && e.touches[0]) || e;
  return {
    pageX: position.pageX,
    pageY: position.pageY
  };
}

function getBoundPosition(pageX, pageY, bound, target) {
  if (bound) {
    if ((typeof bound == 'string' && bound.toLowerCase() !== 'parent') &&
        (typeof bound !== 'object')) {
      console.warn('Bound should either "parent" or an object');
    }
    var par = target.parentNode;
    var topLimit = bound.top || 0;
    var leftLimit = bound.left || 0;
    var rightLimit = bound.right || par.offsetWidth;
    var bottomLimit = bound.bottom || par.offsetHeight;
    pageX = Math.min(pageX, rightLimit - target.offsetWidth);
    pageY = Math.min(pageY, bottomLimit - target.offsetHeight);
    pageX = Math.max(leftLimit, pageX);
    pageY = Math.max(topLimit, pageY);
  }
  return {
    pageX: pageX,
    pageY: pageY
  };
}

function addEvent(el, event, handler) {
  if (!el) { return; }
  if (el.attachEvent) {
    el.attachEvent('on' + event, handler);
  } else if (el.addEventListener) {
    el.addEventListener(event, handler, true);
  } else {
    el['on' + event] = handler;
  }
}

function removeEvent(el, event, handler) {
  if (!el) { return; }
  if (el.detachEvent) {
    el.detachEvent('on' + event, handler);
  } else if (el.removeEventListener) {
    el.removeEventListener(event, handler, true);
  } else {
    el['on' + event] = null;
  }
}

var ReactDrag = createReactClass({
  displayName: 'Draggable',

  propTypes: {
    /**
     * `axis` determines which axis the draggable can move.
     *
     * 'both' allows movement horizontally and vertically.
     * 'x' limits movement to horizontal axis.
     * 'y' limits movement to vertical axis.
     *
     * Defaults to 'both'.
     */
    axis: propTypes.oneOf(['both', 'x', 'y']),

    /**
     * `handle` specifies a selector to be used as the handle
     * that initiates drag.
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *         return (
     *            <Draggable handle=".handle">
     *              <div>
     *                  <div className="handle">Click me to drag</div>
     *                  <div>This is some other content</div>
     *              </div>
     *           </Draggable>
     *         );
     *       }
     *   });
     * ```
     */
    handle: propTypes.string,

    /**
     * `cancel` specifies a selector to be used to prevent drag initialization.
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *           return(
     *               <Draggable cancel=".cancel">
     *                   <div>
     *             <div className="cancel">You can't drag from here</div>
     *            <div>Dragging here works fine</div>
     *                   </div>
     *               </Draggable>
     *           );
     *       }
     *   });
     * ```
     */
    cancel: propTypes.string,

    /**
     * `grid` specifies the x and y that dragging should snap to.
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *           return (
     *               <Draggable grid={[25, 25]}>
     *                   <div>I snap to a 25 x 25 grid</div>
     *               </Draggable>
     *           );
     *       }
     *   });
     * ```
     */
    grid: propTypes.arrayOf(propTypes.number),

    /**
     * `start` specifies the x and y that the dragged item should start at
     *
     * Example:
     *
     * ```jsx
     *   var App = React.createClass({
     *       render: function () {
     *           return (
     *               <Draggable start={{x: 25, y: 25}}>
     *                   <div>I start with left: 25px; top: 25px;</div>
     *               </Draggable>
     *           );
     *       }
     *   });
     * ```
     */
    start: propTypes.object,

    /**
     * Called when dragging starts.
     *
     * Example:
     *
     * ```js
     *  function (event, ui) {}
     * ```
     *
     * `event` is the Event that was triggered.
     * `ui` is an object:
     *
     * ```js
     *  {
     *    position: {top: 0, left: 0}
     *  }
     * ```
     */
    onStart: propTypes.func,

    /**
     * Called while dragging.
     *
     * Example:
     *
     * ```js
     *  function (event, ui) {}
     * ```
     *
     * `event` is the Event that was triggered.
     * `ui` is an object:
     *
     * ```js
     *  {
     *    position: {top: 0, left: 0}
     *  }
     * ```
     */
    onDrag: propTypes.func,

    /**
     * Called when dragging stops.
     *
     * Example:
     *
     * ```js
     *  function (event, ui) {}
     * ```
     *
     * `event` is the Event that was triggered.
     * `ui` is an object:
     *
     * ```js
     *  {
     *    position: {top: 0, left: 0}
     *  }
     * ```
     */
    onStop: propTypes.func,

    /**
     * A workaround option which can be passed if
     * onMouseDown needs to be accessed,
     * since it'll always be blocked (due to that
     * there's internal use of onMouseDown)
     *
     */
    onMouseDown: propTypes.func,

    /**
     * Defines the bounderies around the element
     * could be dragged. This property could be
     * object or a string. If used as object
     * the bounderies should be defined as:
     *
     * {
     *   left: LEFT_BOUND,
     *   right: RIGHT_BOUND,
     *   top: TOP_BOUND,
     *   bottom: BOTTOM_BOUND
     * }
     *
     * The only acceptable string
     * property is: "parent".
     */
    bound: propTypes.any
  },

  componentWillUnmount: function () {
    // Remove any leftover event handlers
    removeEvent(window, dragEventFor.move, this.handleDrag);
    removeEvent(window, dragEventFor.end, this.handleDragEnd);
  },

  getDefaultProps: function () {
    return {
      axis: 'both',
      handle: null,
      cancel: null,
      grid: null,
      bound: false,
      start: {
        x: 0,
        y: 0
      },
      onStart: emptyFunction,
      onDrag: emptyFunction,
      onStop: emptyFunction,
      onMouseDown: emptyFunction
    };
  },

  getInitialState: function () {
    return {
      // Whether or not currently dragging
      dragging: false,

      // Start top/left of this.getDOMNode()
      startX: 0,
      startY: 0,

      // Offset between start top/left and mouse top/left
      offsetX: 0,
      offsetY: 0,

      // Current top/left of this.getDOMNode()
      pageX: this.props.start.x,
      pageY: this.props.start.y
    };
  },

  handleDragStart: function (e) {
    // todo: write right implementation to prevent multitouch drag
    // prevent multi-touch events
    // if (isMultiTouch(e)) {
    //     this.handleDragEnd.apply(e, arguments);
    //     return
    // }

    // Make it possible to attach event handlers on top of this one
    this.props.onMouseDown(e);

    var node = findDOMNode(this);

    // Short circuit if handle or cancel prop was provided
    // and selector doesn't match
    if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) ||
      (this.props.cancel && matchesSelector(e.target, this.props.cancel))) {
      return;
    }

    var dragPoint = getControlPosition(e);

    // Initiate dragging
    this.setState({
      dragging: true,
      offsetX: parseInt(dragPoint.pageX, 10),
      offsetY: parseInt(dragPoint.pageY, 10),
      startX: parseInt(node.style.left, 10) || 0,
      startY: parseInt(node.style.top, 10) || 0
    });

    // Call event handler
    this.props.onStart(e, createUIEvent(this));

    // Add event handlers
    addEvent(window, dragEventFor.move, this.handleDrag);
    addEvent(window, dragEventFor.end, this.handleDragEnd);
  },

  handleDragEnd: function (e) {
    // Short circuit if not currently dragging
    if (!this.state.dragging) {
      return;
    }

    // Turn off dragging
    this.setState({
      dragging: false
    });

    // Call event handler
    this.props.onStop(e, createUIEvent(this));

    // Remove event handlers
    removeEvent(window, dragEventFor.move, this.handleDrag);
    removeEvent(window, dragEventFor.end, this.handleDragEnd);
  },

  handleDrag: function (e) {
    var dragPoint = getControlPosition(e);

    // Calculate top and left
    var pageX = (this.state.startX +
        (dragPoint.pageX - this.state.offsetX));
    var pageY = (this.state.startY +
        (dragPoint.pageY - this.state.offsetY));
    var pos =
      getBoundPosition(pageX, pageY, this.props.bound, findDOMNode(this));
    pageX = pos.pageX;
    pageY = pos.pageY;

    // Snap to grid if prop has been provided
    if (Array.isArray(this.props.grid)) {
      var directionX = pageX < parseInt(this.state.pageX, 10) ? -1 : 1;
      var directionY = pageY < parseInt(this.state.pageY, 10) ? -1 : 1;

      pageX = Math.abs(pageX - parseInt(this.state.pageX, 10)) >=
          this.props.grid[0]
          ? (parseInt(this.state.pageX, 10) +
            (this.props.grid[0] * directionX))
          : this.state.pageX;

      pageY = Math.abs(pageY - parseInt(this.state.pageY, 10)) >=
          this.props.grid[1]
          ? (parseInt(this.state.pageY, 10) +
            (this.props.grid[1] * directionY))
          : this.state.pageY;
    }

    // Update top and left
    this.setState({
      pageX: pageX,
      pageY: pageY
    });

    // Call event handler
    this.props.onDrag(e, createUIEvent(this));

    // Prevent the default behavior
    e.preventDefault();
  },

  render: function () {
    var originalStyle = this.props.children.props.style;
    var style = {
      // Set top if vertical drag is enabled
      top: canDragY(this)
        ? this.state.pageY
        : this.state.startY,

        // Set left if horizontal drag is enabled
      left: canDragX(this)
        ? this.state.pageX
        : this.state.startX
    };
    for (var s in originalStyle) {
      style[s] = originalStyle[s];
    }
    var className = CX({
      'react-drag': true,
      'react-drag-dragging': this.state.dragging
    });
    var oldClass = this.props.children.props.className;
    if (oldClass) {
      className = oldClass + ' ' + className;
    }
    // Reuse the child provided
    // This makes it flexible to use whatever element is wanted (div, ul, etc)
    return React.cloneElement(
        React.Children.only(this.props.children), {
      style: style,
      className: className,

      onMouseDown: this.handleDragStart,
      onTouchStart: function (ev) {
        ev.preventDefault(); // prevent for scroll
        return this.handleDragStart.apply(this, arguments);
      }.bind(this),

      onMouseUp: this.handleDragEnd,
      onTouchEnd: this.handleDragEnd
    });
  }
});

module.exports = ReactDrag;

thanks

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的示例,展示了如何使用`react-drag-and-select`组件来实现选择多个项目的功能: ```javascript import React, { useState } from "react"; import DragAndSelect from "react-drag-and-select"; function App() { const [selectedItems, setSelectedItems] = useState([]); const handleSelection = (selectedItems) => { setSelectedItems(selectedItems); }; const items = [ { id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }, { id: 4, name: "Item 4" }, { id: 5, name: "Item 5" }, ]; return ( <div> <DragAndSelect items={items} onSelection={handleSelection} render={(item, index) => ( <div key={item.id} style={{ marginBottom: 10 }}> <input type="checkbox" checked={selectedItems.includes(item.id)} onChange={() => {}} /> <span style={{ marginLeft: 10 }}>{item.name}</span> </div> )} /> </div> ); } export default App; ``` 在上面的代码中,我们首先导入了`react`和`react-drag-and-select`库。然后,我们使用`useState`钩子来定义了一个名为`selectedItems`的状态,以跟踪用户选择的项目。接着,我们定义了一个名为`handleSelection`的回调函数,用于在选择发生变化时更新`selectedItems`状态。在`handleSelection`回调函数中,我们简单地将所选项目传递给`setSelectedItems`函数,以更新状态。 接下来,我们定义了一个名为`items`的数组,其中包含了我们想要供用户选择的项目。在组件的`render`方法中,我们使用`DragAndSelect`组件来包裹我们的项目。我们将`items`数组传递给`DragAndSelect`组件的`items`属性,以告诉它哪些项目可以被选择。我们还将`handleSelection`函数传递给`DragAndSelect`组件的`onSelection`属性,以在选择发生变化时接收通知。 最后,我们定义了一个`render`函数,用于渲染每个项目。在这个函数中,我们使用一个`<div>`元素来包裹每个项目,并添加了一个复选框和项目名称。我们使用`selectedItems.includes(item.id)`来确定当前项目是否被选择。如果是,我们将复选框标记为选中状态。否则,我们将复选框标记为未选中状态。 希望这个示例能够帮助您了解如何使用`react-drag-and-select`组件来实现选择多个项目的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值