转载

实现"输入框"获得焦点时外边框变蓝

背景

之前做了一个网页,网页中的所有输入框都被设计为“获得鼠标焦点时外边框不变蓝”。突然某一天,产品觉得这样用户体验不好,不能很明显地告诉用户当前鼠标停在哪里,于是要求改为“当输入框获得鼠标焦点时,外边框自动变蓝;失去焦点时外边框恢复原样”。

浏览器自动会给输入框 inputtextareaoutline 属性设置一个默认值,效果就是当输入框获得鼠标焦点时外边框会自动带上颜色,失去焦点时外边框颜色消失。由于不同浏览器 outline 属性的默认值不同,导致外边框的颜色也不同。如果不想要浏览器的 outline 默认设置,只需要将 outline 属性设置为 none 即可。

所以一开始听到这个需求,感觉特别简单,不就是纯 CSS 的问题嘛!我只要将所有输入框的 outline 属性设置为浏览器默认的值就好了,但我想的太天真了。。。。

1、如何复制浏览器默认的 outline 样式

由于网页中已有的输入框的样式遍布在各个文件中,有一种非常机械的办法,那就是到每个文件中去删除掉输入框的 outline:none 样式,这样输入框就会使用浏览器默认的 outline 样式了。但是这样做要改的地方太多了,会疯掉的!再想想有没有更省事的方法,有了!在最基本的 base.css 样式文件中给输入框添加一个 focus 时候的 outline 样式不就解决了么。那么问题来了,该将 outline 设置何值,才能自动使用浏览器默认的 outline 样式呢?

我第一个想到的方法是将 outline 设置为 auto ,但发现只有webkit 内核的浏览器才支持这个属性值,firefox 不支持,因为 auto 并不是标准的 outline 值。所以要想直接复制浏览器默认的 outline 样式是做不到的。难道我要针对每个浏览器设置不同的 outline 值,为了这么一点破需求费那么大工夫?!算了,省事点的方式还是直接摒弃浏览器的默认 outline 样式,统一将 outline 设置为一种样式好了。

2、避开 outline,选择 border

但我发现了新问题,通过设置 outline 样式为输入框添加外边框,效果并不好。因为 outline 没有类似 border-radius 的属性来改变边角的弧度。这样就导致了一个问题:当输入框设置了 border 颜色,同时 border-radius3px 以上时,能明显的看到 outline 外边框并没有和 border 重合。看来设置 outline 来达到边框变蓝效果并不是最好的选择,为什么不选用设置 border 来达到同样的效果呢?只要将 border 再设置下 border-shadow ,那么看起来也是和 outline 效果一样的,而且 border 还能设置 border-radius ,显示效果更满足需求。

3、如何让任何一个元素都能 focusable

通过设置 border 解决了大多数输入框,但发现又有了新问题。

网页中有些输入框其实并不是单一使用 input / textarea 来实现的,这类“输入框”看起来和一般输入框并无两样,但其实 HTML 结构是一个 div 里面包含着一个 input / textarea 来实现的。而且输入框的边框设置在了父元素 div 上,所有当输入框获得焦点时,看到的应该是父元素 div 上的边框变蓝,而不是里面的 input / textarea 的边框变蓝。

要想获得焦点时父元素 div 的边框变蓝,肯定可行的方法是通过 JS 来实现:给 input / textarea 绑定一个 focus 和 一个 blur 事件,在 focus 事件处理函数中将父元素 div 的边框置蓝,在 blur 事件处理函数中将父元素 div 的边框恢复原样。这种方法时绝对可行的,但是我总觉得应该有更简单的,纯粹使用 css 实现的方法。

如果想单纯通过css 实现,首先想到的是用选择器 div:focus 。但是发现 div 上无法使用 :focus 伪类。于是我就猜想:可能并不是所有的元素都是 focusable的,那么得找一份说明哪些元素 focusable的规范。

哪些元素是 focusable的?

搜索结果:

根据 DOM Level 2 HTML规范,focusable 的DOM元素都会有一个原生的 focus() 方法,只有 focusable 的DOM 元素才有 focus 事件,才能使用 :focus 伪类。拥有原生的 focus() 方法的DOM元素包括几种:HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement。很明显,规范中遗漏了HTMLButtonElement and HTMLAreaElement。

虽然规范这么定义,但浏览器在实现时却是另外一套。浏览器给任何一个 HTMLElement 都定义 focus() 方法,但并不是任何一个HTMLElement 都能获得焦点(获得焦点术语叫 active, 具体请参考: http://help.dottoro.com/ljqmdirr.php )。一般来说,任何一个时刻HTML 文档中只会有一个active元素,但并不是任何一个元素都能成为active元素。能成为active 的元素包括:

  1. 表单元素(form controllers): input/option/textarea/button

  2. 链接元素(links): a 标签、 area 标签(必须要带 href 属性,包括 href 属性为空)

  3. 可以被编辑的元素(包括通过添加 contenteditable = "true" 属性变成可编辑的情况)

  4. 设置了 tabindex 属性( tabindex 值非-1)的元素

  5. window :当页面窗口从隐藏变成前置可见时, focus 事件就会触发

根据搜索结果,要想将父元素 div 变成focusable,只需要在元素上设置 tabindex 属性,然后通过 :focus 伪类设置父元素 div 获得焦点时的 border 样式。但我发现当鼠标点击里面的 input 元素时,父元素 div 并没有获得焦点,获得焦点的是子元素 input

所以最后还是没能通过纯 css 的方法来解决这个问题,无奈之下我还是通过js去解决了。。。。。。

正文到此结束
Loading...