1
头图

浅谈 :active 伪类选择器和点击后样式变化无效的交互问题

:active 伪类选择器用于定义元素被点击时的样式,它在 CSS 中出现的频率非常高,大概仅次于 :hover:focus,但是这个选择器实际上并不是那么"好用",这主要是表现在以下的两个方面,第一是这个伪类选择器在 Safari(IOS) 中时常会不生效,第二是这个伪类选择器在配合 transition 过渡动画一起使用时,经常会出现变化迟缓,导致点击反馈不够明显,这篇文章主要探讨这两个问题。


01. :active 在 Safari(IOS) 中的问题

如果你有用过 Safari(IOS),那么你很可能已经发现了,在这个浏览器中 :active 伪类选择器设置后经常会不生效,除非为 <body> 或被点击的目标元素绑定了 touchstart 事件,下面是两个简单的 Demo,第一个 Demo 展示了 Safari(IOS) 未绑定 touchstart 事件时 :active 伪类选择器不生效的情况,第二个 Demo 则是 Safari(IOS) 绑定了 touchstart 事件后 :active 生效了的情况:

<!-- Demo01 : 点击却不展示 :active 的红色背景 -->
<style>.test:active{background-color:red;}</style>

<span class="test" tabIndex="0">span</span> / 
<button class="test" type="button">btn</button> / 
<a class="test" href="javascript:void(0)">anchor</a>

↓ View & Code ↑

<!-- Demo02 : 绑定 touchstart 事件设置就正常了 -->
<style>.test:active{background-color:red;}</style>

<span class="test" tabIndex="0">span</span> / 
<button class="test" type="button">btn</button> / 
<a class="test" href="javascript:void(0)">anchor</a>

<script>
// 为 body 绑定空的 touchstart 事件
document.body.addEventListener('touchstart', function(){}, true);
</script>

↓ View & Code ↑

上面的 Demo,点击出现的黑色遮罩,是 Safari(IOS) 独有的 -webkit-tap-highlight-color 样式导致的,一般大家都会把这个样式取消掉,因为它实现的是覆盖色,难看且作用有限,Demo 没取消掉它,只是为了提示点击操作,<span> 设置 tabIndex 属性是因为不设置则点击不会有覆盖色的交互,<button> 点击后变形也是 Safari(IOS) 的默认样式在作怪,总而言之这浏览器就是一堆问题啦。

第二个 Demo 通过在 <body> 绑定 touchstart 事件的方法来解决 :active 伪类不生效的问题,但这个 hack 也不是完美的,这样做遗留的问题是,当我们按住节点进行页面滚动时,节点会响应 :active 伪类的设置,导致页面滚动时一直处于伪类生效的状态,虽然影响不大但毕竟是不完美,更多细节可参考 StackOverflow 的这个页面 :active pseudo-class doesn't work in mobile safari


02. :active 配合 transition 动画的问题

:active 伪类遭遇 transition 过渡动画的样式时,点击交互就可能会变得不明显,因为 transition 过渡动画的存在会导致交互变化存在一个渐进的过程,而点击往往是一瞬间的事情,二者混在一起最后就是动画还未执行完毕就又得逆向恢复成原状,听着很绕?来直接看 Demo 吧,下面第一个 Demo 是没 transition 过渡动画的情况,第二个 Demo 则是有transition 过渡动画的情况:

<!-- Demo01 : 没 transition 动画时交互就很明显 -->
<style>
.button{display:inline-block;padding:0 8px;height:24px;border:1px solid #ccc;line-height:22px;
cursor:pointer;vertical-align:middle;background-color:#fff;}
.button:hover{background-color:#f0f0f0;}
.button:active{background-color:#fff;}
.button:focus{outline:none;}
</style>

<span class="button" tabIndex="0">span</span> / 
<button class="button" type="button">btn</button> / 
<a class="button" href="javascript:void(0)">anchor</a>

↓ View & Code ↑

<!-- Demo02 : 有 transition 动画时交互就不明显 -->
<style>
.button{transition:background-color 500ms;}
.button{display:inline-block;padding:0 8px;height:24px;border:1px solid #ccc;line-height:22px;
cursor:pointer;vertical-align:middle;background-color:#fff;}
.button:hover{background-color:#f0f0f0;}
.button:active{background-color:#fff;}
.button:focus{outline:none;}
</style>

<span class="button" tabIndex="0">span</span> / 
<button class="button" type="button">btn</button> / 
<a class="button" href="javascript:void(0)">anchor</a>

↓ View & Code ↑

上面的截图都是代码在 Chrome 中运行的结果,其实这个问题在所有浏览器中都存在,我们通过改变背景色来展示点击交互,Demo02 由于背景色被设置了过渡动画,所以在点击的时候背景色变化小到几乎看不出来,那是因为点击往往在 100 ~ 200 毫秒间完成,动画时长是 500 毫秒,此时动画尚未完全结束,所以颜色变化太小,最终就导致了变化不明显,除非是按住鼠标不放才能看到动画结束。

那这问题要怎么解决?取消掉背景色的过渡动画吗?这样你猜产品他会不会吐槽说这个前端就是逊啦?连悬停时改变背景色的动画都做不出来…那悬停要动画,点击又不要动画,就很难处理了,也许我们需要改变思路,点击的交互变化不一定得变化背景色,变点其他东西也不是不行啊,例如说改变文本颜色或是增加外阴影?但这些做法都有点非主流,最终效果也不一定好看,那么点击时来点波纹会怎样呢?


03. 使用 xj.ripple 波纹插件来实现点击交互

如果你有用过 Chrome 浏览器或 Android 系统,你就会发现它们面对点击操作,可能会泛起一个波纹,实际上这是 Metrial Design 的设计理念,这样会使得点击交互更加明显,虽然波纹效果并非 Google 原创(大概?),但 Google 确实使得这种交互模式广为人知,XJ 对这种效果也很感兴趣,所以写了一个开源插件 xj.ripple,用于替代常规的点击交互效果,下面是 xj.ripple 插件的两个简单实例:

<!-- 使用 CDN 引入 xj.ripple 的 JS 和 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/xjZone/xj.ripple@0.5.0/dist/xj.ripple.min.css" />
<script src="https://cdn.jsdelivr.net/gh/xjZone/xj.ripple@0.5.0/dist/xj.ripple.min.js"></script>

<style>
/* 不用设置 :active,也不用再理会 Safari 的 BUG */
.button{display:inline-block;padding:0 8px;height:24px;border:1px solid #ccc;line-height:22px;
cursor:pointer;vertical-align:middle;background-color:#fff;}
.button:hover{background-color:#f0f0f0;}
.button:focus{outline:none;}
</style>

<!-- 为元素添加 xj-ripple 类名即可实现波纹效果 -->
<span class="button xj-ripple" tabIndex="0">span</span> / 
<button class="button xj-ripple" type="button">btn</button> / 
<a class="button xj-ripple" href="javascript:void(0)">anchor</a>

↓ View & Code ↑

<!-- 在引入插件前进行全局配置还能省去类名的设置 -->
<script>
if(window.xj === undefined){ xj = {} };
if(xj.rippleConfig === undefined){ xj.rippleConfig = {} };
xj.rippleConfig['0.5.0'] = { defaultSelector : '.xj-ripple, .xj-ripple-out, .button', };
</script>

<!-- 使用 CDN 引入 xj.ripple 的 JS 和 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/xjZone/xj.ripple@0.5.0/dist/xj.ripple.min.css" />
<script src="https://cdn.jsdelivr.net/gh/xjZone/xj.ripple@0.5.0/dist/xj.ripple.min.js"></script>

<style>
/* 设置 position 以便波纹定位,并避免波纹的溢出 */
.button{position:relative;overflow:hidden;}
.button{display:inline-block;padding:0 8px;height:24px;border:1px solid #ccc;line-height:22px;
cursor:pointer;vertical-align:middle;background-color:#fff;}
.button:hover{background-color:#f0f0f0;}
.button:focus{outline:none;}
</style>

<!-- 自动响应 .button 了,省去 .xj-ripple 类名 -->
<span class="button" tabIndex="0">span</span> / 
<button class="button" type="button">btn</button> / 
<a class="button" href="javascript:void(0)">anchor</a>

↓ View & Code ↑

第一个 Demo 展示了 xj.ripple 插件的效果,只需要为目标节点添加 xj-ripple 类名,即可实现点击的时候响应波纹,这样既不用去理会 Safari(IOS) 的不响应问题,也不用担心 transition 过渡动画导致的交互响应迟缓的问题,实际上你连 :active 伪类选择器都不需要设置了,第二个 Demo 则是展示了插件的自动响应,连类名都不需要添加了,HTML 代码不需要改动,使用起来就更加方便了。

与业界中其他类似的 ripple 方案相比,xj.ripple 提供了更多的参数配置,无论是颜色尺寸定位动画,皆可自由控制,并且支持内联属性对象的继承,使得参数的设置变得更加简单,最重要的是它还可以通过全局配置省去添加类名的麻烦,更多的细节可以参考 xj.ripple 的文档,总而言之,使用这个插件来展示点击的交互,既可以解决问题节约代码,又可以让交互效果变得更加生动有趣,何乐而不为呢。


参考内容

MDUI - ripple
Muse UI - ripple
Materialize - ripple

StackOverflow - Safari 不支持 :active

StackOverflow - 将元素添加到 SVG 标签
MDN - document.createElementNS() 方法

知乎 - JavaScript 为什么不推荐用 eval
Veda - JavaScript 探秘:eval() 是魔鬼

chromium - 修改 box-shadow 所引发的问题
W3C - box-shadow 圆角溢出算法实现的讨论

XJ.Chen - xj.ripple


xjArea
12 声望0 粉丝