也说说HTML元素的焦点事件以及事件代理
—— 关于focus/blur、focusin/focusout、tabindex和addEventListener事件代理
作为一个业余前端呢,一只以来,只要用到获取焦点/失去焦点的功能,就是直接 focus / blur
, 从来没出过问题。
直到有一天...
xxx.addEventListener('focus', function (event) {
console.log('focus!')
}, false)
没报错,可是也不好使,这就有些尴尬了...
没办法,把focus
换成click
试一下,嗯,好使,没问题,证明写法是对的,但是咋就不好使呢?
经过各种百度和谷歌,终于发现,获取焦点/失去焦点这个操作,除了 focus / blur
,竟然还有另一对: focusin / focusout
。而且,把 focus
换成 focusin
之后竟然好使了!难道focus会不支持addEventListener的事件代理吗?感觉不太科学...
focusin / focusout
跟 focus / blur
有什么区别呢?
查文档:
- focus / blur: 当focusable元素获得/失去焦点时触发,不支持冒泡
- focusin / focusout: 当focusable元素获得/失去焦点时触发,支持冒泡
- 执行顺序: 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的值分为三类:
- 正整数:将元素设置为 focusable 状态,同时强制指定元素获取焦点的顺序
- 0:将元素设置为 focusable 状态,设置设置元素获取焦点的顺序为文档顺序,即按照HTML文档顺序自动依次获得焦点
- 负整数:将元素设置为 !focusable 状态
focusable 状态的元素可以通过按tab
键在元素间切换选中状态;而 !focusable 状态的元素使用tab
键无法选中,只能通过鼠标点击来获取焦点。
没了。