event.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import {
  2. mayQueue
  3. } from './may-dom/scheduler';
  4. var globalEvent = {
  5. }
  6. export var eventHooks = {}; //用于在元素上绑定特定的事件
  7. var document = window.document;
  8. export var isTouch = "ontouchstart" in document;
  9. export function dispatchEvent(e, type, end) {
  10. e = new SyntheticEvent(e);
  11. if (type) {
  12. e.type = type;
  13. }
  14. var _type = e.type;
  15. //全局的一个标识 在事件中setState应当合并
  16. mayQueue.isInEvent = true;
  17. //onClickCapture 在捕获阶段触发
  18. var captured = _type + 'capture';
  19. var eventCollect = bubbleEvent(e.target, end || document);
  20. //先触发捕获
  21. triggerEventFlow(e, eventCollect, captured);
  22. if (!e._stopPropagation) {
  23. //触发冒泡
  24. triggerEventFlow(e, eventCollect.reverse(), _type);
  25. }
  26. mayQueue.isInEvent = false;
  27. //在事件中合并state之后 触发reRender
  28. mayQueue.clearQueue();
  29. }
  30. /**
  31. * 自己冒泡,收集冒泡过程中的所有事件
  32. * @param {*event target} from
  33. * @param {*end || document} end
  34. */
  35. function bubbleEvent(from, end) {
  36. var collect = [];
  37. do {
  38. if (from === end) {
  39. break;
  40. }
  41. var event = from._listener;
  42. if (event) {
  43. collect.push({
  44. dom: from,
  45. events: event
  46. });
  47. }
  48. } while ((from = from.parentNode) && from.nodeType === 1);
  49. // target --> parentNode --> body --> html
  50. return collect;
  51. }
  52. function triggerEventFlow(e, collect, prop) {
  53. for (var i = collect.length; i--;) {
  54. var eObj = collect[i];
  55. var fn = eObj.events[prop];
  56. if (isFn(fn)) {
  57. e.currentTarget = eObj.dom;
  58. fn.call(eObj.dom, e);
  59. if (e._stopPropagation) {
  60. break;
  61. }
  62. }
  63. }
  64. }
  65. export var eventLowerCache = {
  66. onClick: "click",
  67. onChange: "change",
  68. onWheel: "wheel"
  69. };
  70. var rcapture = /Capture$/;
  71. export function getBrowserName(onStr) {
  72. var lower = eventLowerCache[onStr];
  73. if (lower) {
  74. return lower;
  75. }
  76. var camel = onStr.slice(2).replace(rcapture, "");
  77. lower = camel.toLowerCase();
  78. eventLowerCache[onStr] = lower;
  79. return lower;
  80. }
  81. export function addEvent(name) {
  82. if (!globalEvent[name]) {
  83. globalEvent[name] = true;
  84. addDocumentEvent(document, name, dispatchEvent);
  85. }
  86. }
  87. function addDocumentEvent(el, name, fn, bool) {
  88. if (el.addEventListener) {
  89. el.addEventListener(name, fn, bool || false);
  90. } else if (el.attachEvent) {
  91. el.attachEvent('on' + name, fn);
  92. }
  93. }
  94. export function SyntheticEvent(event) {
  95. if (event.nativeEvent) {
  96. return event;
  97. }
  98. if (!this.target) {
  99. this.target = event.srcElement;
  100. }
  101. for (var i in event) {
  102. if (!eventProto[i]) {
  103. this[i] = event[i];
  104. }
  105. }
  106. this.timeStamp = new Date() - 0;
  107. this.nativeEvent = event;
  108. }
  109. export function createHandle(name, fn) {
  110. return function (e) {
  111. if (fn && fn(e) === false) {
  112. return;
  113. }
  114. dispatchEvent(e, name);
  115. };
  116. }
  117. if (isTouch) {
  118. eventHooks.click = noop;
  119. eventHooks.clickcapture = noop;
  120. }
  121. var changeHandle = createHandle("change");
  122. var doubleClickHandle = createHandle("doubleclick");
  123. //react将text,textarea,password元素中的onChange事件当成onInput事件
  124. eventHooks.changecapture = eventHooks.change = function (dom) {
  125. if (/text|password/.test(dom.type)) {
  126. addDocumentEvent(document, "input", changeHandle);
  127. }
  128. };
  129. eventHooks.doubleclick = eventHooks.doubleclickcapture = function () {
  130. addDocumentEvent(document, "dblclick", doubleClickHandle);
  131. };
  132. var eventProto = (SyntheticEvent.prototype = {
  133. preventDefault: function () {
  134. var e = this.nativeEvent || {};
  135. e.returnValue = this.returnValue = false;
  136. if (e.preventDefault) {
  137. e.preventDefault();
  138. }
  139. },
  140. fixHooks: function () { },
  141. stopPropagation: function () {
  142. var e = this.nativeEvent || {};
  143. e.cancleBubble = this._stopPropagation = true;
  144. if (e.stopPropagation) {
  145. e.stopPropagation();
  146. }
  147. },
  148. persist: noop,
  149. stopImmediatePropagation: function () {
  150. this.stopPropagation();
  151. this.stopImmediate = true;
  152. },
  153. toString: function () {
  154. return "[object Event]";
  155. }
  156. })
  157. Object.freeze ||
  158. (Object.freeze = function (a) {
  159. return a;
  160. });
  161. function isFn(obj) {
  162. return Object.prototype.toString.call(obj) === "[object Function]";
  163. }
  164. function noop() { }
  165. /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
  166. firefox DOMMouseScroll detail 下3 上-3
  167. firefox wheel detlaY 下3 上-3
  168. IE9-11 wheel deltaY 下40 上-40
  169. chrome wheel deltaY 下100 上-100 */
  170. /* istanbul ignore next */
  171. const fixWheelType = "onmousewheel" in document ? "mousewheel" : document.onwheel !== void 666 ? "wheel" : "DOMMouseScroll";
  172. const fixWheelDelta = fixWheelType === "mousewheel" ? "wheelDetla" : fixWheelType === "wheel" ? "deltaY" : "detail";
  173. eventHooks.wheel = function (dom) {
  174. addDocumentEvent(dom, fixWheelType, function (e) {
  175. var delta = e[fixWheelDelta] > 0 ? -120 : 120;
  176. var deltaY = ~~dom.__wheel + delta;
  177. dom.__wheel = deltaY;
  178. e = new SyntheticEvent(e);
  179. e.type = "wheel";
  180. e.deltaY = deltaY;
  181. dispatchEvent(e);
  182. });
  183. };
  184. var fixFocus = {};
  185. "blur,focus".replace(/\w+/g, function (type) {
  186. eventHooks[type] = function () {
  187. if (!fixFocus[type]) {
  188. fixFocus[type] = true;
  189. addDocumentEvent(document, type, dispatchEvent, true);
  190. }
  191. };
  192. });
  193. /**
  194. *
  195. DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值;
  196. 对于其他事件,这个属性的值是null。IE不支持realtedTarget属性,但提供了保存着同样信息的不同属性。
  197. 在mouseover事件触发时,IE的fromElement属性中保存了相关元素;
  198. 在mouseout事件出发时,IE的toElement属性中保存着相关元素。
  199. 但fromElement与toElement可能同时都有值
  200. */
  201. function getRelatedTarget(e) {
  202. if (!e.timeStamp) {
  203. e.relatedTarget = e.type === "mouseover" ? e.fromElement : e.toElement;
  204. }
  205. return e.relatedTarget;
  206. }
  207. function contains(a, b) {
  208. if (b) {
  209. while ((b = b.parentNode)) {
  210. if (b === a) {
  211. return true;
  212. }
  213. }
  214. }
  215. return false;
  216. }
  217. String("mouseenter,mouseleave").replace(/\w+/g, function (type) {
  218. eventHooks[type] = function (dom, name) {
  219. var mark = "__" + name;
  220. if (!dom[mark]) {
  221. dom[mark] = true;
  222. var mask = name === "mouseenter" ? "mouseover" : "mouseout";
  223. addDocumentEvent(dom, mask, function (e) {
  224. let t = getRelatedTarget(e);
  225. if (!t || (t !== dom && !contains(dom, t))) {
  226. var common = getLowestCommonAncestor(dom, t);
  227. //由于不冒泡,因此paths长度为1
  228. dispatchEvent(e, name, common);
  229. }
  230. });
  231. }
  232. };
  233. });
  234. function getLowestCommonAncestor(instA, instB) {
  235. var depthA = 0;
  236. for (var tempA = instA; tempA; tempA = tempA.parentNode) {
  237. depthA++;
  238. }
  239. var depthB = 0;
  240. for (var tempB = instB; tempB; tempB = tempB.parentNode) {
  241. depthB++;
  242. }
  243. // If A is deeper, crawl up.
  244. while (depthA - depthB > 0) {
  245. instA = instA.parentNode;
  246. depthA--;
  247. }
  248. // If B is deeper, crawl up.
  249. while (depthB - depthA > 0) {
  250. instB = instB.parentNode;
  251. depthB--;
  252. }
  253. // Walk in lockstep until we find a match.
  254. var depth = depthA;
  255. while (depth--) {
  256. if (instA === instB) {
  257. return instA;
  258. }
  259. instA = instA.parentNode;
  260. instB = instB.parentNode;
  261. }
  262. return null;
  263. }