用 :focus-within 实现纯 CSS 下拉框组件
:focus-within 伪类:当本节点或其子节点获得焦点时被激活。
借用 MDN 的例子
https://jsfiddle.net/d4w8h2ge/
当点击表单中的文本框时需高亮整个表单元素。但是表单内部的文本框获得焦点并不代表表单元素本身有焦点,所以使用 form:focus
并不能生效,这时就可以使用 :focus-within
当然这也是 :focus-within
的其中一个典型用法。
使用普通的绑定 click
事件的方式实现下拉框组件
回到正题,这是一个使用 JS 实现的下拉框:https://jsfiddle.net/omo6cov2/1/
为了实现这个下拉框,你通常(*)需要绑定两个 click
事件:一个在 a
标签上用于打开下拉框;一个在 body
上用于关闭下拉框。在 body
的 click
事件处理函数里,你得判断事件不能是来自下拉框内部的点击。你还得记得在移除下拉框(例如单页程序切换路由)时移除这个事件。
还有两个问题:
- 由于在
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
,调样式时不要忘了选
完
(*):当然如果你非要把 a 元素上的 click 事件放入 document 的 click 事件里处理我也没意见。