是否可以在浏览器中以编程方式捕获页面上的所有事件?

Posted

技术标签:

【中文标题】是否可以在浏览器中以编程方式捕获页面上的所有事件?【英文标题】:Is it possible to programmatically catch all events on the page in the browser? 【发布时间】:2011-07-03 16:41:31 【问题描述】:

首先,这里列出了 W3C 标准定义的事件类型。 (这个列表基于 html5 标准中定义的 onevent 属性。我假设还有几十种其他事件类型,但这个列表已经足够长了。)

中止 后印 印前 卸载前 模糊 可以玩 可以通关 改变 点击 上下文菜单 复制 换球 切 dblclick DOMContentLoaded 拖动 拖累 dragenter 拖动离开 拖拉机 拖动启动 放下 持续时间变化 清空 结束 错误 专注 专注于 聚焦 形式改变 表单输入 哈希变化 输入 无效 按键 按键 键位 加载 加载数据 加载元数据 加载开始 留言 鼠标按下 鼠标进入 鼠标离开 鼠标移动 鼠标移出 鼠标悬停 鼠标移动 鼠标滚轮 离线 在线 页面隐藏 页面显示 粘贴 暂停 播放 正在播放 弹出状态 进展 汇率变化 readystatechange 重做 重置 调整大小 滚动 寻求 寻求 选择 显示 停滞不前 存储 提交 暂停 时间更新 撤消 卸载 音量变化 等待中

现在,是否可以定义一个全局事件处理程序,当 any 事件最初发生在页面上的 any 元素上时调用? (在这种情况下,我不想计算那些发生在元素上的事件,因为它们是从后代元素冒出来的——这就是我写“最初发生”的原因。)

如果这不可能,是否至少可以定义一个事件处理程序,当 any 事件冒泡到 DOM 树的根部(即 document 对象或window 对象 - 两者都应该工作)? (我知道可以以编程方式停止冒泡,但我会在没有在任何其他元素上定义其他处理程序的页面上使用此事件处理程序。)(另外,我相信某些事件不会冒泡,但让我们忽略这些为了这个论点的缘故。)

我知道我可以做到这一点(使用 jQuery):

$(document).bind('abort afterprint beforeprint beforeunload etc.', function() 
    // handle event
);

但这对我来说是一个相当不受欢迎的解决方案。

顺便说一句,我不需要跨浏览器解决方案。如果它只适用于一个浏览器,我很好。

另外,Firebug is able to log events,但我希望能够以编程方式(通过 javascript)捕获事件,而不是让它们简单地登录到控制台。

【问题讨论】:

你已经得到了很好的事件类型列表;为什么不直接使用$('body').delegate('*', allEventTypes.join(' '), function() ... ) @Pointy 那将是 B 计划。但如果至少有一个浏览器公开了捕获所有冒泡到根目录的事件的功能,我想知道该功能。 @Pointy 虽然绑定到窗口对象也很好(不需要委托):$(window).bind('all those event types', function() ... ); 哦,是的,我猜你是对的 - ".delegate('*', ...)" 有点傻 :-) 为什么我没有在您的精彩列表中找到“DOMNodeInserted”事件(这是一个相当重要的事件,因为我现在正在构建的库依赖依赖它)?跨度> 【参考方案1】:
/*

function getAllEventTypes()

  if(location.href !='https://developer.mozilla.org/en-US/docs/Web/Events') return;

  var types = ;
  $('.standard-table:eq(0) tr').find('td:eq(1)').map(function()
    var type = $.trim(this.innerText) || 'OtherEvent';
    types[type] = types[type] || [];     
    var event = $.trim(this.previousElementSibling.innerText);
    if(event) types[type].push(event);
  );
  for(var t in types) types[t] = types[t].join(' ');
  return "var DOMEvents = "+JSON.stringify(types, null, 4).replace(/"(\w+)\":/ig, '$1:');


*/

var DOMEvents = 
UIEvent: "abort DOMActivate error load resize scroll select unload",
ProgressEvent: "abort error load loadend loadstart progress progress timeout",
Event: "abort afterprint beforeprint cached canplay canplaythrough change chargingchange chargingtimechange checking close dischargingtimechange DOMContentLoaded downloading durationchange emptied ended ended error error error error fullscreenchange fullscreenerror input invalid languagechange levelchange loadeddata loadedmetadata noupdate obsolete offline online open open orientationchange pause pointerlockchange pointerlockerror play playing ratechange readystatechange reset seeked seeking stalled submit success suspend timeupdate updateready visibilitychange volumechange waiting",
AnimationEvent: "animationend animationiteration animationstart",
AudioProcessingEvent: "audioprocess",
BeforeUnloadEvent: "beforeunload",
TimeEvent: "beginEvent endEvent repeatEvent",
OtherEvent: "blocked complete upgradeneeded versionchange",
FocusEvent: "blur DOMFocusIn  Unimplemented DOMFocusOut  Unimplemented focus focusin focusout",
MouseEvent: "click contextmenu dblclick mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup show",
SensorEvent: "compassneedscalibration Unimplemented userproximity",
OfflineAudioCompletionEvent: "complete",
CompositionEvent: "compositionend compositionstart compositionupdate",
ClipboardEvent: "copy cut paste",
DeviceLightEvent: "devicelight",
DeviceMotionEvent: "devicemotion",
DeviceOrientationEvent: "deviceorientation",
DeviceProximityEvent: "deviceproximity",
MutationNameEvent: "DOMAttributeNameChanged DOMElementNameChanged",
MutationEvent: "DOMAttrModified DOMCharacterDataModified DOMNodeInserted DOMNodeInsertedIntoDocument DOMNodeRemoved DOMNodeRemovedFromDocument DOMSubtreeModified",
DragEvent: "drag dragend dragenter dragleave dragover dragstart drop",
GamepadEvent: "gamepadconnected gamepaddisconnected",
HashChangeEvent: "hashchange",
KeyboardEvent: "keydown keypress keyup",
MessageEvent: "message message message message",
PageTransitionEvent: "pagehide pageshow",
PopStateEvent: "popstate",
StorageEvent: "storage",
SVGEvent: "SVGAbort SVGError SVGLoad SVGResize SVGScroll SVGUnload",
SVGZoomEvent: "SVGZoom",
TouchEvent: "touchcancel touchend touchenter touchleave touchmove touchstart",
TransitionEvent: "transitionend",
WheelEvent: "wheel"


var RecentlyLoggedDOMEventTypes = ;

for(DOMEvent in DOMEvents)

  var DOMEventTypes = DOMEvents[DOMEvent].split(' ');

  DOMEventTypes.filter(function(DOMEventType)
    var DOMEventCategory = DOMEvent + ' '+DOMEventType;  
    document.addEventListener(DOMEventType, function(e)
      if(RecentlyLoggedDOMEventTypes[DOMEventCategory]) return;
      RecentlyLoggedDOMEventTypes[DOMEventCategory] = true;
      setTimeout(function() RecentlyLoggedDOMEventTypes[DOMEventCategory] = false , 5000);
      var isActive = e.target==document.activeElement;
      if(isActive) 
        console.info(DOMEventCategory, 
          ' target=', e.target, 
          ' active=', document.activeElement, 
          ' isActive=', true );
       else 
        console.log(DOMEventCategory, 
          ' target=', e.target,
          ' active=', document.activeElement, 
          ' isActive=', false );
      

    , true);
  );


【讨论】:

请查看我的回答,了解最新版本的 MDN 网站上运行的 getAllEventTypes 的更新版本。【参考方案2】:

您可以遍历 dom 元素的所有属性并选择匹配 /on(.*)/ 模式的属性(例如 onclick 或 onmousemove):

var events = [];
for (var property in element) 
    var match = property.match(/^on(.*)/)
    if (match)  
        events.push(match[1]);
    

console.log(events.join(' '))

【讨论】:

这个值得一试的家伙。不复杂的解决方案。但我仍然建议console.log(events); 不带join(),因为它使它更具可读性。【参考方案3】:

我非常怀疑 Firefox 有没有办法做到这一点。查看Firebug's source code(尤其是attachAllListeners 方法),结果表明遍历事件名称列表显然是可行的方法,但这并不能解决冒泡问题。

【讨论】:

【参考方案4】:

似乎没有任何“简单的方法”可以做到这一点。

我的想法: 您知道哪些是所有事件,因此您可以处理每个 DOM 元素的所有事件:

var events =
[   
    "onabort",
    "onafterprint",
    "onbeforeprint",
    "onbeforeunload",
    ...

];

var root = document.body;
var elms = root.childNodes;

for(var i = 0; i < elms.length; i++)

    for(var j = 0; j < events.length; j++)
    
        elms[i][events[j]] = globalHandler;
    


function globalHandler()

    alert("Global handler called");

这是“直观的想法”,但似乎效率不高。但是,它应该可以工作。

祝你好运。

【讨论】:

我不认为将每个可能的事件类型的事件处理程序分配给页面上每个可能的元素是一个好主意。我担心我的浏览器会爆炸:) @Sime 只要确保你只有几个元素就可以了!【参考方案5】:

聚会有点晚了,但我确实在这里创造了一些可能对其他人有用的东西。

https://codepen.io/phreaknation/pen/QmJjEa

这是一个 ES6 类,它从该元素已知的元素中捕获所有事件。此演示允许您更改页面中的元素时间,以及通过指向其 MDN 页面的可点击链接读出事件,以及与元素交互并查看事件是如何通过时间戳触发的。

希望对你有帮助

类代码

class EventSystem 
  constructor(element) 
    this._ = 
      element: null
    

    return this;
  

  getAllEventTypes(blacklist = [], whitelist = [] = ) 
    const events = [];
    for (let property in this._.element) 
      const match = property.match(/^on(.*)/);
      if (match) 
        if ((whitelist.length > 0 ? whitelist.indexOf(match) !== -1 : true) &&
            (blacklist.length > 0 ? blacklist.indexOf(match) === -1 : true)) 
          events.push(match[1]);
                  
      
    
    return events;
  

  getElementType() 
    return this._.element.tagName.toLowerCase();
  

  setElement(element) 
    this._.element = element;
    return this;
  

  applyEvents(events, callback) 
    events.forEach((event) => 
      this._.element.addEventListener(event, (ev) => 
        if (typeof callback === 'function') 
          callback(event, ev);
        
      )
    )
  

【讨论】:

【参考方案6】:

我对这个问题的解决方案。我遍历全局上下文中的所有数据类型(在本例中为window),检查类型是否扩展EventTarget,然后通过检查“on”前缀提取它们。

const getEventNames = (root) => 
  let events = [ ];

  const objectHasSubPrototype = (object, comp) => 
    let proto = Object.getPrototypeOf(object);

    while(proto !== null && proto !== EventTarget) 
      proto = Object.getPrototypeOf(proto);
    

    return (proto !== null);
  ;

  const addEventNames = (propNames) => 
    propNames.filter(x => x.match(/^on\w+$/)).forEach((propName) => 
      propName = propName.substr(2);
      if(events.indexOf(propName) === -1) 
        events.push(propName);
      
    );
  ;

  Object.getOwnPropertyNames(root).forEach((name) => 
    let value = root[name];

    if(value) 
      if(objectHasSubPrototype(value, EventTarget)) 
        let propNames = Object.getOwnPropertyNames(Object.getPrototypeOf(value).prototype);
        addEventNames(propNames);

        propNames = Object.getOwnPropertyNames(window);
        addEventNames(propNames);
      
    
  );

  return events;
;

// Attach all events to the window
getEventNames(window).forEach((eventName) => 
  window.addEventListener(eventName, (event) => console.log(eventName, event));
);

【讨论】:

【参考方案7】:

如何监听特定目标上的所有事件 Element ?

对于所有本机事件,我们可以通过遍历 target.onevent 属性并为所有事件安装我们的侦听器来检索支持的事件列表。

for (const key in target) 
    if(/^on/.test(key)) 
        const eventType = key.substr(2);
        target.addEventListener(eventType, listener);
    

据我所知,发出事件的唯一另一种方式是通过EventTarget.dispatchEvent,每个Node 和因此每个Element 都继承。 要监听所有这些手动触发的事件,我们可以全局代理dispatchEvent 方法,并为我们刚刚看到的名称的事件安装我们的监听器✨ ^^

const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) 
    if (!alreadyListenedEventTypes.has(event.type)) 
        target.addEventListener(event.type, listener, ...otherArguments);
        alreadyListenedEventTypes.add(event.type);
    
    dispatchEvent_original.apply(this, arguments);
;

? 函数 sn-p ?

function addEventListenerAll(target, listener, ...otherArguments) 

    // install listeners for all natively triggered events
    for (const key in target) 
        if (/^on/.test(key)) 
            const eventType = key.substr(2);
            target.addEventListener(eventType, listener, ...otherArguments);
        
    

    // dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
    const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
    function dispatchEvent(event) 
        target.addEventListener(event.type, listener, ...otherArguments);  // multiple identical listeners are automatically discarded
        dispatchEvent_original.apply(this, arguments);
    
    EventTarget.prototype.dispatchEvent = dispatchEvent;
    if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);




// usage example
addEventListenerAll(window, (evt) => 
    console.log(evt.type);
);
document.body.click();
document.body.dispatchEvent(new Event('omg!',  bubbles: true ));


// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
    window,
    (evt) =>  console.log(evt.type); ,
    true
);
document.body.dispatchEvent(new Event('omfggg!',  bubbles: false ));

【讨论】:

【参考方案8】:

对于 MDN 网站的最新版本:

(function getAllEventTypes()
  if(location.href !='https://developer.mozilla.org/en-US/docs/Web/Events') return;

  var types = ;
  $('.standard-table').map(function()
    if($(this).find('caption').length > 0)
        var type = $(this).find('caption')[0].innerHTML || 'OtherEvent';
    types[type] = types[type] || [];     
    $(this).find('tbody tr td code a').each(function(el)
        if(this.innerText) types[type].push(this.innerText);
    );
    
  );
  for(var t in types) types[t] = types[t].join(' ');
  return "var DOMEvents = "+JSON.stringify(types, null, 4).replace(/"(\w+)\":/ig, '$1:');
)();

【讨论】:

我觉得这不是一个好的答案。第一行是 url 检查当前页面是否为特定页面。其余代码在当前页面中查找一个表,如果有人在他们的页面上使用该表,则该表将不存在。我尝试在本地运行此代码,但它永远不会执行,并且在我注释掉第一行时失败。这不是一个有效的解决方案。这归结为只能在 MDN 事件页面上工作。 @PhreakNation 该函数将在该 url 上运行以生成代码......不是很好,但它不应该是独立的

以上是关于是否可以在浏览器中以编程方式捕获页面上的所有事件?的主要内容,如果未能解决你的问题,请参考以下文章

JS的事件冒泡和事件捕获

MS-Access 2007 - 如何在点击事件中以编程方式访问子表单列数据

浅谈js的事件冒泡和事件捕获

浅谈js的事件冒泡和事件捕获

在浏览器中以低延迟捕获声音输入

在 c++ 中以可变帧速率使用 FFmpeg 库(不是 libav 分支)以编程方式捕获视频