也说说HTML元素的焦点事件以及事件代理

—— 关于focus/blur、focusin/focusout、tabindex和addEventListener事件代理

momo314相同方式共享非商业用途署名转载

作为一个业余前端呢,一只以来,只要用到获取焦点/失去焦点的功能,就是直接 focus / blur, 从来没出过问题。

直到有一天...

xxx.addEventListener('focus', function (event) {
    console.log('focus!')
}, false)

没报错,可是也不好使,这就有些尴尬了...

没办法,把focus换成click试一下,嗯,好使,没问题,证明写法是对的,但是咋就不好使呢?

经过各种百度和谷歌,终于发现,获取焦点/失去焦点这个操作,除了 focus / blur,竟然还有另一对: focusin / focusout。而且,把 focus 换成 focusin 之后竟然好使了!难道focus会不支持addEventListener的事件代理吗?感觉不太科学...


focusin / focusoutfocus / blur 有什么区别呢?

查文档:

  1. focus / blur: 当focusable元素获得/失去焦点时触发,不支持冒泡
  2. focusin / focusout: 当focusable元素获得/失去焦点时触发,支持冒泡
  3. 执行顺序: focusin -> focus -> focusout -> blur

除了冒泡,好像也并没有什么大的差别,那么为什么上面的代码用focus时不好使呢?

这个说起来就是我的锅了。

关于这个锅呢,得先来谈谈 addEventListener 事件代理:

大家知道,addEventListener事件代理其实就是将子元素的事件绑定在父元素上,之所以能够这样做,得益于标准事件模型的捕获和冒泡。在标准事件模型中,一个事件的触发会经历三个阶段:捕获阶段+目标阶段+冒泡阶段,有了捕获和冒泡才能实现事件代理。而focus / blur虽然不支持冒泡,但是是有捕获阶段的(非IE),所以依然能够进行事件代理。

但是为啥上面的的代码不行呢,是因为将addEventListener的最后一个参数useCapture指定为了 false(虽然默认值就是false)。这个参数什么意思呢,根据文档的描述:

useCapture 可选 / Boolean

是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件。沿着DOM树向上冒泡的事件不会触发被指定为use capture(也就是设为true)的listener。当一个元素嵌套了另一个元素,两个元素都对同一个事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。

所以,当被设置为默认值false的时候,事件的传播方式会是由下向上,通过冒泡的方式来传播的,而focus / blur并不支持冒泡,所以自然无法进行事件代理。

同理,如果将最后一个参数useCapture设置为true,则,focus也可以正常进行事件代理了。


既然说到了焦点事件,那就顺便再提一下 tabindex

说实话,在我们非专业前端这个圈子里,这个属性已经快要被人遗忘了,但还是挺重要了,提升用户体验的利器啊。

不多废话,总结一下用法:

tabindex属性可以用来指定一个元素获取焦点的顺序,同时也可以切换一个元素的 focusable 和 !focusable 状态。

tabindex的值分为三类:

  1. 正整数:将元素设置为 focusable 状态,同时强制指定元素获取焦点的顺序
  2. 0:将元素设置为 focusable 状态,设置设置元素获取焦点的顺序为文档顺序,即按照HTML文档顺序自动依次获得焦点
  3. 负整数:将元素设置为 !focusable 状态

focusable 状态的元素可以通过按tab键在元素间切换选中状态;而 !focusable 状态的元素使用tab键无法选中,只能通过鼠标点击来获取焦点。


没了。

✎﹏ 本文来自于 momo314和他们家的猫,文章原创,转载请注明作者并保留原文链接。