2
0

simulate.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. (function($) {
  2. $.simulateByPoint = function(type, options) {
  3. var docEl = $(document);
  4. var point = options.point;
  5. var clientX, clientY;
  6. var node;
  7. if (point) {
  8. clientX = point.left - docEl.scrollLeft();
  9. clientY = point.top - docEl.scrollTop();
  10. node = document.elementFromPoint(clientX, clientY);
  11. $(node).simulate(type, options);
  12. }
  13. };
  14. var DEBUG_DELAY = 500;
  15. var DEBUG_MIN_DURATION = 2000;
  16. var DEBUG_MIN_MOVES = 100;
  17. var DRAG_DEFAULTS = {
  18. point: null, // the start point
  19. localPoint: { left: '50%', top: '50%' },
  20. end: null, // can be a point or an el
  21. localEndPoint: { left: '50%', top: '50%' },
  22. dx: 0,
  23. dy: 0,
  24. moves: 5,
  25. duration: 100 // ms
  26. };
  27. var dragStackCnt = 0;
  28. $.simulate.prototype.simulateDrag = function() {
  29. var targetNode = this.target;
  30. var targetEl = $(targetNode);
  31. var options = $.extend({}, DRAG_DEFAULTS, this.options);
  32. var dx = options.dx;
  33. var dy = options.dy;
  34. var duration = options.duration;
  35. var moves = options.moves;
  36. var startPoint;
  37. var endEl;
  38. var endPoint;
  39. var localPoint;
  40. var offset;
  41. // compute start point
  42. if (options.point) {
  43. startPoint = options.point;
  44. }
  45. else {
  46. localPoint = normalizeElPoint(options.localPoint, targetEl);
  47. offset = targetEl.offset();
  48. startPoint = {
  49. left: offset.left + localPoint.left,
  50. top: offset.top + localPoint.top
  51. };
  52. }
  53. // compute end point
  54. if (options.end) {
  55. if (isPoint(options.end)) {
  56. endPoint = options.end;
  57. }
  58. else { // assume options.end is an element
  59. endEl = $(options.end);
  60. localPoint = normalizeElPoint(options.localEndPoint, endEl);
  61. offset = endEl.offset();
  62. endPoint = {
  63. left: offset.left + localPoint.left,
  64. top: offset.top + localPoint.top
  65. };
  66. }
  67. }
  68. if (endPoint) {
  69. dx = endPoint.left - startPoint.left;
  70. dy = endPoint.top - startPoint.top;
  71. }
  72. moves = Math.max(moves, options.debug ? DEBUG_MIN_MOVES : 1);
  73. duration = Math.max(duration, options.debug ? DEBUG_MIN_DURATION : 10);
  74. simulateDrag(
  75. this,
  76. targetNode,
  77. startPoint,
  78. dx,
  79. dy,
  80. moves,
  81. duration,
  82. options
  83. );
  84. };
  85. function simulateDrag(self, targetNode, startPoint, dx, dy, moveCnt, duration, options) {
  86. var debug = options.debug;
  87. var docNode = targetNode.ownerDocument;
  88. var docEl = $(docNode);
  89. var waitTime = duration / moveCnt;
  90. var moveIndex = 0;
  91. var clientCoords;
  92. var intervalId;
  93. var dotEl;
  94. var dragId;
  95. if (debug) {
  96. dotEl = $('<div>')
  97. .css({
  98. position: 'absolute',
  99. zIndex: 99999,
  100. border: '5px solid red',
  101. borderRadius: '5px',
  102. margin: '-5px 0 0 -5px'
  103. })
  104. .appendTo('body');
  105. }
  106. function updateCoords() {
  107. var progress = moveIndex / moveCnt;
  108. var left = startPoint.left + dx * progress;
  109. var top = startPoint.top + dy * progress;
  110. clientCoords = {
  111. clientX: left - docEl.scrollLeft(),
  112. clientY: top - docEl.scrollTop()
  113. };
  114. if (debug) {
  115. dotEl.css({ left: left, top: top });
  116. }
  117. }
  118. function startDrag() {
  119. updateCoords();
  120. dragId = ++dragStackCnt;
  121. // simulate a drag-start only if another drag isn't already happening
  122. if (dragStackCnt === 1) {
  123. self.simulateEvent(targetNode, 'mousedown', clientCoords);
  124. }
  125. if (debug) {
  126. setTimeout(function() {
  127. startMoving();
  128. }, DEBUG_DELAY);
  129. }
  130. else {
  131. startMoving();
  132. }
  133. }
  134. function startMoving() {
  135. intervalId = setInterval(tick, waitTime);
  136. }
  137. function tick() { // called one interval after start
  138. moveIndex++;
  139. updateCoords(); // update clientCoords before mousemove
  140. self.simulateEvent(docNode, 'mousemove', clientCoords);
  141. if (moveIndex >= moveCnt) {
  142. stopMoving();
  143. }
  144. }
  145. function stopMoving() {
  146. clearInterval(intervalId);
  147. if (debug) {
  148. setTimeout(function() {
  149. dotEl.remove(); // do this before calling stopDrag/callback. don't want dot picked up by elementFromPoint
  150. stopDrag();
  151. }, DEBUG_DELAY);
  152. }
  153. else {
  154. stopDrag();
  155. }
  156. }
  157. function stopDrag() { // progress at 1, coords already up to date at this point
  158. (options.onBeforeRelease || function() {})();
  159. // only simulate a drop if the current drag is still the active one.
  160. // otherwise, this means another drag has begun via onBeforeRelease.
  161. if (dragId === dragStackCnt) {
  162. if ($.contains(docNode, targetNode)) {
  163. self.simulateEvent(targetNode, 'mouseup', clientCoords);
  164. self.simulateEvent(targetNode, 'click', clientCoords);
  165. }
  166. else {
  167. self.simulateEvent(docNode, 'mouseup', clientCoords);
  168. }
  169. }
  170. dragStackCnt--;
  171. (options.onRelease || options.callback || function() {})(); // TODO: deprecate "callback" ?
  172. }
  173. startDrag();
  174. }
  175. function normalizeElPoint(point, el) {
  176. var left = point.left;
  177. var top = point.top;
  178. if (/%$/.test(left)) {
  179. left = parseInt(left) / 100 * el.outerWidth();
  180. }
  181. if (/%$/.test(top)) {
  182. top = parseInt(top) / 100 * el.outerHeight();
  183. }
  184. return { left: left, top: top };
  185. }
  186. function isPoint(input) {
  187. return typeof input === 'object' && // `in` operator only works on objects
  188. 'left' in input && 'top' in input;
  189. }
  190. })(jQuery);