Files
2025-12-07 15:18:40 +08:00

403 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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);