(function(global) { "use strict"; if (!global.Windows) global.Windows = {}; if (!global.Windows.UI) global.Windows.UI = {}; if (!global.Windows.UI.Event) global.Windows.UI.Event = {}; var Monitor = (function() { var _sIdAttr = "data-monitor-id"; var _idCounter = 1; var _aRegistry = {}; var _typeRegistry = {}; // 按事件类型分类缓存 var _polling = false; var _loopHandle = null; var _cleanupThreshold = 30000; // 30秒清理一次 var _lastCleanup = Date.now(); var _checkInterval = 200; // 节流时间 var _eventTypes = [ "resize", "position", "attribute", "child" ]; // 缓存 DOM 元素引用 var _elementCache = {}; function _ensureId(el) { if (!el.getAttribute(_sIdAttr)) { el.setAttribute(_sIdAttr, "monitor_" + (_idCounter++)); } return el.getAttribute(_sIdAttr); } function _getElementById(id) { if (_elementCache[id] && _elementCache[id].parentNode) { return _elementCache[id]; } var el = document.querySelector("[" + _sIdAttr + "=\"" + id + "\"]"); if (el) _elementCache[id] = el; return el; } function _getAttrSnapshot(el) { var attrs = {}; for (var i = 0; i < el.attributes.length; i++) { var attr = el.attributes[i]; attrs[attr.name] = attr.value; } attrs["_rect"] = el.getBoundingClientRect(); return attrs; } function _hasChanged(snapshotA, snapshotB) { for (var key in snapshotA) { if (snapshotA.hasOwnProperty(key)) { if (key === "_rect") { var a = snapshotA[key], b = snapshotB[key]; if (!b || a.top !== b.top || a.left !== b.left || a.width !== b.width || a.height !== b.height) { return true; } } else { if (snapshotA[key] !== snapshotB[key]) { return true; } } } } return false; } function _pollOnce() { var now = Date.now(); // 按事件类型遍历,减少不必要检查 for (var type in _typeRegistry) { if (!_typeRegistry.hasOwnProperty(type)) continue; var list = _typeRegistry[type]; for (var i = 0; i < list.length; i++) { var item = list[i]; var el = _getElementById(item.id); if (!el) { list.splice(i--, 1); delete _elementCache[item.id]; continue; } var newSnapshot = _getAttrSnapshot(el); if (_hasChanged(item.snapshot, newSnapshot)) { item.snapshot = newSnapshot; try { item.callback.call(el, { type: type }); } catch (ex) { console.error("Monitor callback error:", ex); } } } } // 清理过期节点 if (now - _lastCleanup > _cleanupThreshold) { _cleanup(); _lastCleanup = now; } } function _startLoop() { if (_polling) return; _polling = true; function loop() { _pollOnce(); _loopHandle = global.requestAnimationFrame ? requestAnimationFrame(loop) : setTimeout(loop, _checkInterval); } loop(); } function _stopLoop() { _polling = false; if (_loopHandle) { if (global.cancelAnimationFrame) cancelAnimationFrame(_loopHandle); else clearTimeout(_loopHandle); _loopHandle = null; } } function _cleanup() { for (var type in _typeRegistry) { if (!_typeRegistry.hasOwnProperty(type)) continue; var list = _typeRegistry[type]; for (var i = 0; i < list.length; i++) { if (!_getElementById(list[i].id)) { list.splice(i--, 1); delete _elementCache[list[i].id]; } } } } function observe(el, type, callback) { if (_eventTypes.indexOf(type) < 0) throw new Error("Unsupported event type: " + type); var id = _ensureId(el); if (!_typeRegistry[type]) _typeRegistry[type] = []; _typeRegistry[type].push({ id: id, callback: callback, snapshot: _getAttrSnapshot(el) }); _startLoop(); } function detach(el, type, callback) { if (!_typeRegistry[type]) return; var id = el.getAttribute(_sIdAttr); if (!id) return; var list = _typeRegistry[type]; for (var i = 0; i < list.length; i++) { if (list[i].id === id && (!callback || list[i].callback === callback)) { list.splice(i--, 1); delete _elementCache[id]; } } } function clearAll() { _typeRegistry = {}; _elementCache = {}; _stopLoop(); } return { /** * 监听元素变化,并触发回调函数。 * @param {Element} el 目标元素 * @param {string} type 事件类型,如 "resize", "position", "attribute", "child" * @param {function} callback 回调函数,参数为事件对象 */ observe: observe, /** * 取消监听元素变化。 * @param {Element} el 目标元素 * @param {string} type 事件类型,如 "resize", "position", "attribute", "child" * @param {function} [callback] 回调函数,如果指定,则只移除指定的回调函数,否则移除所有回调函数。 */ detach: detach, /** * 清除所有监听。 */ clearAll: clearAll, /** * 事件类型枚举。 */ EventType: { /** 元素尺寸变化 */ resize: "resize", /** 元素位置变化 */ position: "position", /** 元素属性变化 */ attribute: "attribute", /** 子元素变化 */ child: "child" } }; })(); global.Windows.UI.Event.Monitor = Monitor; })(window); /* // 1) 监听元素尺寸变化 var el = document.getElementById("box"); Windows.UI.Event.Monitor.observe(el, "resize", function (e) { console.log("resized", e.oldValue, e.newValue, e.rect); }); // 2) 监听属性变化 Windows.UI.Event.Monitor.observe(el, "attributeChange", function (e) { console.log("attrs changed", e.detail); // detail.added / removed / changed }); // 3) 监听附着/分离 Windows.UI.Event.Monitor.observe(el, "attach", function (e) { console.log("attached to doc"); }); Windows.UI.Event.Monitor.observe(el, "detach", function (e) { console.log("detached from doc"); }); // 4) 取消监听 Windows.UI.Event.Monitor.unobserve(el, "resize", handler); */ (function(global) { "use strict"; var EventUtil = {}; /** * 添加事件,兼容 IE10/IE11 * @param {Element|Window|Document} el 目标元素 * @param {string} sType 事件类型,如 "click", "resize", "scroll" * @param {function} pfHandler 回调函数 * @param {boolean} [bUseCapture] 是否捕获阶段,默认 false */ EventUtil.addEvent = function(el, sType, pfHandler, bUseCapture) { if (!el || typeof sType !== "string" || typeof pfHandler !== "function") return; bUseCapture = !!bUseCapture; if (el.addEventListener) { // 标准方式 el.addEventListener(sType, pfHandler, bUseCapture); } else if (el.attachEvent) { // IE8-9 fallback el.attachEvent("on" + sType, pfHandler); } else { // 最原始方式 var oldHandler = el["on" + sType]; el["on" + sType] = function(e) { if (oldHandler) oldHandler(e || window.event); pfHandler(e || window.event); }; } }; /** * 移除事件,兼容 IE10/IE11 * @param {Element|Window|Document} el 目标元素 * @param {string} sType 事件类型,如 "click", "resize", "scroll" * @param {function} pfHandler 回调函数 * @param {boolean} [bUseCapture] 是否捕获阶段,默认 false */ EventUtil.removeEvent = function(el, sType, pfHandler, bUseCapture) { if (!el || typeof sType !== "string" || typeof pfHandler !== "function") return; bUseCapture = !!bUseCapture; if (el.removeEventListener) { el.removeEventListener(sType, pfHandler, bUseCapture); } else if (el.detachEvent) { el.detachEvent("on" + sType, pfHandler); } else { var oldHandler = el["on" + sType]; if (oldHandler === pfHandler) { el["on" + sType] = null; } } }; // 暴露到全局命名空间 if (typeof module !== "undefined" && module.exports) { module.exports = { Windows: { UI: { Event: { Util: EventUtil } } } }; } else { global.Windows = global.Windows || {}; global.Windows.UI = global.Windows.UI || {}; global.Windows.UI.Event = global.Windows.UI.Event || {}; global.Windows.UI.Event.Util = EventUtil; } })(this); /* 使用示例: var handler = function (e) { console.log("事件触发", e.type); }; // 添加事件 Windows.UI.Event.Util.addEvent(window, "resize", handler); // 删除事件 Windows.UI.Event.Util.removeEvent(window, "resize", handler); */ (function(global) { "use strict"; /** * * @param {function} fn * @param {number} delay * @param {boolean} immediate 是否在第一次立即执行(可选,默认 false) * @returns {function} 返回一个新的函数,该函数在 delay 时间后执行 fn 函数,如果在 delay 时间内再次调用该函数,则会重新计时。 */ function debounce(fn, delay, immediate) { var timer = null; var lastCall = 0; return function() { var context = this; var args = arguments; var now = +new Date(); var callNow = immediate && !timer; if (now - lastCall >= delay) { lastCall = now; if (callNow) { fn.apply(context, args); } } clearTimeout(timer); timer = setTimeout(function() { lastCall = +new Date(); if (!immediate) { fn.apply(context, args); } }, delay); }; } module.exports = { debounce: debounce }; })(this); (function(global) { "use strict"; var eToEvent = {}; // 存储元素和回调 var lastContent = {}; // 存储上一次的 textContent // 注册文本变化事件 global.setTextChangeEvent = function(el, fn) { if (!el || typeof fn !== "function") return; var id = el.__textChangeId; if (!id) { id = Math.random().toString(36).substr(2, 9); el.__textChangeId = id; lastContent[id] = el.textContent; } eToEvent[id] = { el: el, callback: fn }; }; // 定时轮询 setInterval(function() { var keys = Object.keys(eToEvent); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var obj = eToEvent[key]; var el = obj.el; var currentText = el.textContent; if (currentText !== lastContent[key]) { lastContent[key] = currentText; try { obj.callback.call(el, currentText); } catch (e) { // 忽略回调错误 if (typeof console !== "undefined") console.error(e); } } } }, 20); })(this);