index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import * as React from "react";
  2. import { int, innerHeight, innerWidth, outerHeight, outerWidth, parseBounds } from '../utils';
  3. /// <reference path="react.d.ts" />
  4. const doc = document;
  5. export class Dragger extends React.Component {
  6. constructor(props) {
  7. super(props);
  8. this.state = {
  9. /** x轴位移,单位是px */
  10. x: 0,
  11. /** y轴位移,单位是px */
  12. y: 0,
  13. /**鼠标点击元素的原始位置,单位是px */
  14. originX: 0,
  15. originY: 0,
  16. isUserMove: true,
  17. /**已经移动的位移,单位是px */
  18. lastX: 0,
  19. lastY: 0,
  20. /**堆叠的层级 */
  21. zIndex: 1
  22. };
  23. this.move = this.move.bind(this);
  24. this.onDragEnd = this.onDragEnd.bind(this);
  25. this.parent = null;
  26. this.self = null;
  27. }
  28. move(event) {
  29. let { lastX, lastY } = this.state;
  30. /* event.client - this.state.origin 表示的是移动的距离,
  31. * elX表示的是原来已经有的位移
  32. */
  33. let deltaX, deltaY;
  34. if (event.type.indexOf('mouse') >= 0) {
  35. deltaX = event.clientX - this.state.originX + lastX;
  36. deltaY = event.clientY - this.state.originY + lastY;
  37. }
  38. else {
  39. deltaX = event.touches[0].clientX - this.state.originX + lastX;
  40. deltaY = event.touches[0].clientY - this.state.originY + lastY;
  41. }
  42. const { bounds } = this.props;
  43. if (bounds) {
  44. /**
  45. * 如果用户指定一个边界,那么在这里处理
  46. */
  47. let NewBounds = typeof bounds !== 'string' ? parseBounds(bounds) : bounds;
  48. /**
  49. * 网格式移动范围设定,永远移动 n 的倍数
  50. * 注意:设定移动范围的时候,一定要在判断bounds之前,否则会造成bounds不对齐
  51. */
  52. const { grid } = this.props;
  53. if (Array.isArray(grid) && grid.length === 2) {
  54. deltaX = Math.round(deltaX / grid[0]) * grid[0];
  55. deltaY = Math.round(deltaY / grid[1]) * grid[1];
  56. }
  57. if (this.props.bounds === 'parent') {
  58. NewBounds = {
  59. left: int(this.parent.style.paddingLeft) + int(this.self.style.marginLeft) - this.self.offsetLeft,
  60. top: int(this.parent.style.paddingTop) + int(this.self.style.marginTop) - this.self.offsetTop,
  61. right: innerWidth(this.parent) - outerWidth(this.self) - this.self.offsetLeft +
  62. int(this.parent.style.paddingRight) - int(this.self.style.marginRight),
  63. bottom: innerHeight(this.parent) - outerHeight(this.self) - this.self.offsetTop +
  64. int(this.parent.style.paddingBottom) - int(this.self.style.marginBottom)
  65. };
  66. }
  67. /**
  68. * 保证不超出右边界和底部
  69. * keep element right and bot can not cross the bounds
  70. */
  71. if (NewBounds !== 'parent')
  72. deltaX = Math.min(deltaX, NewBounds.right);
  73. if (NewBounds !== 'parent')
  74. deltaY = Math.min(deltaY, NewBounds.bottom);
  75. /**
  76. * 保证不超出左边和上边
  77. * keep element left and top can not cross the bounds
  78. */
  79. if (NewBounds !== 'parent')
  80. deltaX = Math.max(deltaX, NewBounds.left);
  81. if (NewBounds !== 'parent')
  82. deltaY = Math.max(deltaY, NewBounds.top);
  83. }
  84. /**如果设置了x,y限制 */
  85. deltaX = this.props.allowX ? deltaX : 0;
  86. deltaY = this.props.allowY ? deltaY : 0;
  87. /**移动时回调,用于外部控制 */
  88. if (this.props.onMove)
  89. this.props.onMove(event, deltaX, deltaY);
  90. this.setState({
  91. x: deltaX,
  92. y: deltaY
  93. });
  94. }
  95. onDragStart(event) {
  96. /** 保证用户在移动元素的时候不会选择到元素内部的东西 */
  97. doc.body.style.userSelect = 'none';
  98. // if (this.props.hasDraggerHandle) {
  99. // if (event.target.className !== 'handle') return
  100. // }
  101. /**
  102. * 把监听事件的回掉函数,绑定在document上
  103. * 当设置边界的时候,用户鼠标会离开元素的范围
  104. * 绑定在document上可以使得其依旧能够监听
  105. * 如果绑定在元素上,则鼠标离开元素,就不会再被监听了
  106. */
  107. if (event.type.indexOf('mouse') >= 0) {
  108. doc.addEventListener('mousemove', this.move);
  109. doc.addEventListener('mouseup', this.onDragEnd);
  110. }
  111. else {
  112. doc.addEventListener('touchmove', this.move);
  113. doc.addEventListener('touchend', this.onDragEnd);
  114. }
  115. if (this.props.bounds === 'parent' &&
  116. /**为了让 这段代码不会重复执行 */
  117. (typeof this.parent === 'undefined' || this.parent === null)) {
  118. /**
  119. * 在这里我们将父节点缓存下来,保证当用户鼠标离开拖拽区域时,我们仍然能获取到父节点
  120. * what we do here is
  121. * making sure that we still can retrieve our parent when user's mouse left this node.
  122. */
  123. this.parent = event.currentTarget.offsetParent; //todo
  124. /**
  125. * 我们自己
  126. * ourself
  127. */
  128. this.self = event.currentTarget;
  129. }
  130. this.props.onDragStart && this.props.onDragStart(this.state.x, this.state.y);
  131. let originX, originY;
  132. if (event.type.indexOf('mouse') >= 0) {
  133. originX = event.clientX;
  134. originY = event.clientY;
  135. }
  136. else {
  137. originX = event.touches[0].clientX;
  138. originY = event.touches[0].clientY;
  139. }
  140. this.setState({
  141. originX: originX,
  142. originY: originY,
  143. lastX: this.state.x,
  144. lastY: this.state.y,
  145. zIndex: 10
  146. });
  147. }
  148. onDragEnd(event) {
  149. /** 取消用户选择限制,用户可以重新选择 */
  150. doc.body.style.userSelect = '';
  151. this.parent = null;
  152. this.self = null;
  153. if (event.type.indexOf('mouse') >= 0) {
  154. doc.removeEventListener('mousemove', this.move);
  155. doc.removeEventListener('mouseup', this.onDragEnd);
  156. }
  157. else {
  158. doc.removeEventListener('touchmove', this.move);
  159. doc.removeEventListener('touchend', this.onDragEnd);
  160. }
  161. this.setState({
  162. zIndex: 1
  163. });
  164. this.props.onDragEnd && this.props.onDragEnd(event);
  165. }
  166. componentDidMount() {
  167. /**
  168. * 这个函数只会调用一次
  169. * 这个只是一个临时的解决方案,因为这样会使得元素进行两次刷新
  170. */
  171. if (typeof this.props.x === 'number' &&
  172. typeof this.props.y === 'number') {
  173. this.setState({
  174. x: this.props.x,
  175. y: this.props.y
  176. });
  177. }
  178. }
  179. componentWillReceiveProps(nextProps) {
  180. /**
  181. * 外部props 改变的时候更新元素的内部位置
  182. * 这个api设计其实很不好
  183. * 以后可能会修改掉
  184. */
  185. const { isUserMove } = nextProps;
  186. if (!isUserMove) {
  187. if (typeof nextProps.x === 'number' &&
  188. typeof nextProps.y === 'number') {
  189. this.setState({
  190. x: nextProps.x,
  191. y: nextProps.y,
  192. lastX: nextProps.x,
  193. lastY: nextProps.y
  194. });
  195. }
  196. }
  197. }
  198. render() {
  199. let { x, y, zIndex } = this.state;
  200. const { style, className } = this.props;
  201. if (!this.props.isUserMove) {
  202. /**当外部设置其props的x,y初始属性的时候,我们在这里设置元素的初始位移 */
  203. x = this.props.x ? this.props.x : 0;
  204. y = this.props.y ? this.props.y : 0;
  205. }
  206. /**主要是为了让用户定义自己的className去修改css */
  207. const fixedClassName = typeof className === 'undefined' ? '' : className + ' ';
  208. return (React.createElement("div", { className: `${fixedClassName}WrapDragger`, style: Object.assign({}, style, { zIndex: zIndex, touchAction: 'none!important', transform: `translate(${x}px,${y}px)` }), onMouseDown: this.onDragStart.bind(this), onTouchStart: this.onDragStart.bind(this), onTouchEnd: this.onDragEnd.bind(this), onMouseUp: this.onDragEnd.bind(this) }, React.Children.only(this.props.children)));
  209. }
  210. }
  211. /**
  212. * 初始变量设置
  213. */
  214. Dragger.defaultProps = {
  215. allowX: true,
  216. allowY: true,
  217. isUserMove: true
  218. };