import { document } from "./browser"; import { isFn, noop, options } from "./util"; var globalEvents = {}; export var eventPropHooks = {}; //用于在事件回调里对事件对象进行 export var eventHooks = {}; //用于在元素上绑定特定的事件 //根据onXXX得到其全小写的事件名, onClick --> click, onClickCapture --> click, // onMouseMove --> mousemove export var eventLowerCache = { onClick: "click", onChange: "change", onWheel: "wheel" }; /** * 判定否为与事件相关 * * @param {any} name * @returns */ export function isEventName(name) { return /^on[A-Z]/.test(name); } export var isTouch = "ontouchstart" in document; export function dispatchEvent(e, type, end) { //__type__ 在injectTapEventPlugin里用到 e = new SyntheticEvent(e); if (type) { e.type = type; } var bubble = e.type; var hook = eventPropHooks[bubble]; if (hook && false === hook(e)) { return; } var paths = collectPaths(e.target, end || document); var captured = bubble + "capture"; options.async = true; triggerEventFlow(paths, captured, e); if (!e._stopPropagation) { triggerEventFlow(paths.reverse(), bubble, e); } options.async = false; options.flushBatchedUpdates(); } function collectPaths(from, end) { var paths = []; do { if (from === end) { break; } var events = from.__events; if (events) { paths.push({ dom: from, events: events }); } } while ((from = from.parentNode) && from.nodeType === 1); // target --> parentNode --> body --> html return paths; } function triggerEventFlow(paths, prop, e) { for (var i = paths.length; i--;) { var path = paths[i]; var fn = path.events[prop]; if (isFn(fn)) { e.currentTarget = path.dom; fn.call(path.dom, e); if (e._stopPropagation) { break; } } } } export function addGlobalEvent(name) { if (!globalEvents[name]) { globalEvents[name] = true; addEvent(document, name, dispatchEvent); } } export function addEvent(el, type, fn, bool) { if (el.addEventListener) { // Unable to preventDefault inside passive event listener due to target being // treated as passive el.addEventListener( type, fn, bool || false ); } else if (el.attachEvent) { el.attachEvent("on" + type, fn); } } var rcapture = /Capture$/; export function getBrowserName(onStr) { var lower = eventLowerCache[onStr]; if (lower) { return lower; } var camel = onStr.slice(2).replace(rcapture, ""); lower = camel.toLowerCase(); eventLowerCache[onStr] = lower; return lower; } eventPropHooks.click = function (e) { return !e.target.disabled; }; /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120 firefox DOMMouseScroll detail 下3 上-3 firefox wheel detlaY 下3 上-3 IE9-11 wheel deltaY 下40 上-40 chrome wheel deltaY 下100 上-100 */ /* istanbul ignore next */ const fixWheelType = "onmousewheel" in document ? "mousewheel" : document.onwheel !== void 666 ? "wheel" : "DOMMouseScroll"; const fixWheelDelta = fixWheelType === "mousewheel" ? "wheelDetla" : fixWheelType === "wheel" ? "deltaY" : "detail"; eventHooks.wheel = function (dom) { addEvent(dom, fixWheelType, function (e) { var delta = e[fixWheelDelta] > 0 ? -120 : 120; var deltaY = ~~dom.__wheel + delta; dom.__wheel = deltaY; e = new SyntheticEvent(e); e.type = "wheel"; e.deltaY = deltaY; dispatchEvent(e); }); }; var fixFocus = {}; "blur,focus".replace(/w+/g, function (type) { eventHooks[type] = function () { if (!fixFocus[type]) { fixFocus[type] = true; addEvent( document, type, dispatchEvent, true ); } }; }); /** * DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值; 对于其他事件,这个属性的值是null。IE不支持realtedTarget属性,但提供了保存着同样信息的不同属性。 在mouseover事件触发时,IE的fromElement属性中保存了相关元素; 在mouseout事件出发时,IE的toElement属性中保存着相关元素。 但fromElement与toElement可能同时都有值 */ function getRelatedTarget(e) { if (!e.timeStamp) { e.relatedTarget = e.type === "mouseover" ? e.fromElement : e.toElement; } return e.relatedTarget; } function contains(a, b) { if (b) { while ((b = b.parentNode)) { if (b === a) { return true; } } } return false; } String("mouseenter,mouseleave").replace(/w+/g, function (type) { eventHooks[type] = function (dom, name) { var mark = "__" + name; if (!dom[mark]) { dom[mark] = true; var mask = name === "mouseenter" ? "mouseover" : "mouseout"; addEvent(dom, mask, function (e) { let t = getRelatedTarget(e); if (!t || (t !== dom && !contains(dom, t))) { var common = getLowestCommonAncestor(dom, t); //由于不冒泡,因此paths长度为1 dispatchEvent(e, name, common); } }); } }; }); function getLowestCommonAncestor(instA, instB) { var depthA = 0; for (var tempA = instA; tempA; tempA = tempA.parentNode) { depthA++; } var depthB = 0; for (var tempB = instB; tempB; tempB = tempB.parentNode) { depthB++; } // If A is deeper, crawl up. while (depthA - depthB > 0) { instA = instA.parentNode; depthA--; } // If B is deeper, crawl up. while (depthB - depthA > 0) { instB = instB.parentNode; depthB--; } // Walk in lockstep until we find a match. var depth = depthA; while (depth--) { if (instA === instB) { return instA; } instA = instA.parentNode; instB = instB.parentNode; } return null; } if (isTouch) { eventHooks.click = noop; eventHooks.clickcapture = noop; } export function createHandle(name, fn) { return function (e) { if (fn && fn(e) === false) { return; } dispatchEvent(e, name); }; } var changeHandle = createHandle("change"); var doubleClickHandle = createHandle("doubleclick"); //react将text,textarea,password元素中的onChange事件当成onInput事件 eventHooks.changecapture = eventHooks.change = function (dom) { var mask = /text|password/.test(dom.type) ? "input" : "change"; addEvent(document, mask, changeHandle); }; eventHooks.doubleclick = eventHooks.doubleclickcapture = function () { addEvent(document, "dblclick", doubleClickHandle); }; export function SyntheticEvent(event) { if (event.nativeEvent) { return event; } for (var i in event) { if (!eventProto[i]) { this[i] = event[i]; } } if (!this.target) { this.target = event.srcElement; } this.fixEvent(); this.timeStamp = new Date() - 0; this.nativeEvent = event; } var eventProto = (SyntheticEvent.prototype = { fixEvent: function () { }, //留给以后扩展用 preventDefault: function () { var e = this.nativeEvent || {}; e.returnValue = this.returnValue = false; if (e.preventDefault) { e.preventDefault(); } }, fixHooks: function () { }, stopPropagation: function () { var e = this.nativeEvent || {}; e.cancelBubble = this._stopPropagation = true; if (e.stopPropagation) { e.stopPropagation(); } }, persist: noop, stopImmediatePropagation: function () { this.stopPropagation(); this.stopImmediate = true; }, toString: function () { return "[object Event]"; } }); /* istanbul ignore next */ //freeze_start Object.freeze || (Object.freeze = function (a) { return a; }); //freeze_end