用 :focus-within 实现纯 CSS 下拉框组件

jianqi 2019-06-27

:focus-within 伪类:当本节点或其子节点获得焦点时被激活。

借用 MDN 的例子

https://jsfiddle.net/d4w8h2ge/

当点击表单中的文本框时需高亮整个表单元素。但是表单内部的文本框获得焦点并不代表表单元素本身有焦点,所以使用 form:focus 并不能生效,这时就可以使用 :focus-within

当然这也是 :focus-within 的其中一个典型用法。

使用普通的绑定 click 事件的方式实现下拉框组件

回到正题,这是一个使用 JS 实现的下拉框:https://jsfiddle.net/omo6cov2/1/

为了实现这个下拉框,你通常(*)需要绑定两个 click 事件:一个在 a 标签上用于打开下拉框;一个在 body 上用于关闭下拉框。在 bodyclick 事件处理函数里,你得判断事件不能是来自下拉框内部的点击。你还得记得在移除下拉框(例如单页程序切换路由)时移除这个事件。

还有两个问题:

  • 由于在 body 上绑定了事件,在下拉框关闭时也会触发,造成不必要的开销。
  • 如果页面被嵌在 iframe 内,点击 iframe 外部元素不会触发 iframe 内部的 click 事件,所以无法通过点击 iframe 外部关闭下拉框。

使用 :focus-within 实现下拉框组件

这是一个使用 :focus-within 实现下拉框组件:https://jsfiddle.net/2vnn7fa4/1/

原理:点击链接时,a 元素获得焦点,激活了父元素的 :focus-within

除了把 .dropdown:not(.open) 改为 .dropdown:not(:focus-within) 外,还有一个要点:ul 标签本身(及其父元素们都)不能获得焦点,所以点击下拉框内部时默认会把整个文档的焦点清除。解决方法很简单:给 ul 标签添加 tabindex 属性即可。

另外:Chrome 的 devtool 里有一项 Force state -> :focus-within,调样式时不要忘了选

浏览器支持率,edge 需要加把劲了

(*):当然如果你非要把 a 元素上的 click 事件放入 document 的 click 事件里处理我也没意见。

相关推荐