细说iOS Safari下focus的行为

这篇文章发布于 2020年10月26日,星期一,01:05,归类于 Mobile相关, Web综合。 阅读 23529 次, 今日 8 次 6 条评论

 

封面图占位图

iOS Safari浏览器下的focus行为和其他浏览器都一些明显不一致的地方,有时候会给开发带来困扰,这里就说说相关的细节知识,均源自于自己日常开发遇到的问题。

一、button点击无法focus

:focus-within伪类是一个非常实用的CSS伪类,详见我2年前写的这篇文章“详细了解CSS :focus-within伪类及其交互应用”。

这个伪类在Chrome和Firefox浏览器下使用非常OK,但是在Safari浏览器下却有坑,链接和按钮明明点击了,却不能触发祖先元素的:focus-within伪类匹配。

千万不要认为是Safari浏览器不支持:focus-within伪类,Safari浏览器支持很多年了,兼容性如下图所示。

:focus-within伪类最新的兼容性

那为何Safari浏览器下没有效果呢?

原因就在于Safari浏览器下,<a>链接元素和<button>按钮元素在点击的时候,是不会有focus状态的。

我猜测是Safari浏览器不希望按钮或链接点击的时候有focus轮廓才这么设计的,但是,显然这种一刀切的做法就不如Chrome和Firefox浏览器处理的好,在Chrome浏览器下,默认状态下,按钮和链接在点击的时候是不会有outline轮廓的,只有键盘访问的时候才有,兼顾视觉和无障碍访问。

那有没有什么办法让Safari浏览器下的链接和按钮在点击的时候也能响应:focus伪类呢?

<a>链接focus

要想让Safari浏览器下<a>链接元素在点击后保持focus状态,可以是设置tabindex="0",如下所示:

<a href tabindex="0">点击我</a>

tabindex="0"不会影响链接元素原本的索引顺序,千万不要设置tabindex属性为其他值,具体原因详见我写的这篇文章:“HTML tabindex属性与web网页键盘无障碍访问”。

<button>按钮focus

要想让Safari浏览器下<button>按钮元素在点击后保持focus状态,目前是没有什么有效方法的。

MDN文档中对于<button>按钮元素的点击和聚焦的关系有专门的整理,具体参见下表。

是否点击按钮元素可以focus
桌面浏览器 Windows 8.1 OS X 10.X
Firefox Yes – Firefox 30.0 No (即使使用 tabindex) Firefox 63
Chrome Yes – Chrome 35 Yes – Chrome 65
Safari N/A No (即使使用tabindex) Safari 12
Internet Explorer Yes – Internet Explorer 11 N/A
Presto Yes – Opera 12 Yes – Opera 12
是否轻触按钮元素可以被focus
手机浏览器 iOS 7.1.2 Android 4.4.4
Safari Mobile No (即使使用tabindex) N/A
Chrome 35 No (even with a tabindex) Yes

因此,如果大家要使用:focus-within伪类,同时需要兼容Safari浏览器,请使用<a>标签按钮代替<button>标签按钮。

二、focus无法失焦blur事件不触发

例如如下代码所示的<a>元素再点击之后会被focus,但是此时你再点击页面空白区域,该元素是不会失焦的,也就是不会blur。

<a href tabindex="0">点击我</a>

这个现象只会出现在iOS Safari浏览器下,微信和原生Safari浏览器都会遇到这个体验问题。

我专门做了个demo演示这个问题,您可以狠狠地点击这里:iOS Safari blur无法触发demo

此demo需要在iOS Safari浏览器下访问,因为如果是PC电脑,可以拿出手机打开微信扫一扫进行体验:

demo页面二维码

此时,点击demo页面的链接(有时候需要点击2次),就会看到下拉浮层出现了,如下截图所示:

浮层出现

相关CSS代码如下所示:

.drop-panel {
    display: none;
    position: absolute;    
    border: 1px solid #ccc;
    background-color: #fff;
    padding: 12px 20px;
}
:focus + .drop-panel {
    display: block;
}

但是,在iPhone手机下,或者iPad下,浮层出现后,你点击旁边白色的空白区域,浮层死活都不隐藏,导致:focus伪类交互就出现了问题。

这就是这一小节要说明的focus无法失焦blur事件不触发的问题。

补充于翌日: 上面blur不触发我在iOS 12下测试是存在的,然后今天收到反馈,iPhone 12,iOS 14点击空白处可以隐藏,因此,iOS可能在后续的版本优化了这个交互细节。

实际上,blur失焦是可以触发的,就是当我们点击其他控件元素的时候,比方说浮层显示之后,我们点击的不是空白区域,而是按钮元素、或者是其他链接元素,或者输入框元素,则:focus会失焦,blur时间会触发,也就是焦点不能消失,只能转移。

有没有什么办法让iOS Safari浏览器下的可以点击空白区域也失焦呢?

可以有。

就是找到层级足够高的祖先元素(不能是<body>元素,可以是<body>子元素),然后设置tabindex=-1,任意的非控件HTML元素在设置了tabindex=-1属性后,点击的时候是会有focus状态的。

也就是点击空白区域,会把原本附着在链接上的焦点转移到祖先元素上,于是blur失焦就正常触发了。

例如demo页面中的这个按钮点击后,就会给一个祖先元素设置tabindex=-1

点击的按钮示意

此时再去点击上面的链接,就会发现此时点击页面空白,出现的浮层正常消失了。

三、focus()方法有时候无效

有这么一种交互需求,就是点击某个按钮,创建了一个新的输入框,希望这个输入框在创建之后就立即被focus呼起输入软键盘。

然后在iOS Safari浏览器下有时候就会出现 input.focus() 无效的情况。

首先明确一点,iPhone浏览器中直接input.focus()是可以有效的,但是需要有个前提,就是在点击事件中,而且是要在点击事件这个线程中。

也就是直接input.focus()是无法让输入框focus的,但是写在click事件中是有效的,例如:

button.addEventListener('click', function () {
    // zhangxinxu: 有效
    input.focus();
});

注意,并不是说input.focus()语句写在click事件中就是有效的。

例如在Vue等框架中,DOM的渲染是数据驱动的,因此,往往会在click事件中进行数据变化,而此时的DOM渲染实在click事件之后执行的,因此,开发者自然而然会想到使用定时器进行处理。

如下所示:

button.addEventListener('click', function () {
    someDataChange();
    // zhangxinxu: 无效
    setTimeout(function () {
        input.focus();
    }, 1);
});

此时focus行为就不会触发是无效的,因为加了定时器,focus行为已经不和click在一个执行线程中,处于安全和体验的考虑,iOS系统就没有让输入框focus聚焦。

知道了原因问题就好解决了。

比方说click事件更新的是是否focus的标志量,然后在Vue的updated方法中进行focus聚焦,代码示意如下:

// 新增数据
this.xxxData.push({
  type: type,
  content: 'zhangxinxu.com'
})
// 标志量变化
document.isFocusAfterUpdated = true

updated()方法中:

if (document.isFocusAfterUpdated) {
  input.focus()

  // focus标志量还原
  document.isFocusAfterUpdated = false
}

四、小结一下

我目前遇到的iOS Safari浏览器下focus行为相关的“坑爹”行为有3个:按钮元素无法被focus、控件元素focus后可能无法失焦以及focus()聚焦可能无行为发生。

每个问题都提供了相关的指导建议。

Safari可能还有其他和focus相关的坑,欢迎大家补充。

OK,以上就是本文的全部内容,希望可以对你的工作有所帮助。

感谢阅读,欢迎分享!

(本篇完)

分享到:


发表评论(目前6 条评论)

  1. Cavalheiro说道:

    正好最近遇上了这个 bug,一搜就搜到大佬的文章,写得太好了!

  2. HarryLit说道:

    很棒,研究得很仔细,感谢!

  3. gentlecoder说道:

    还有input元素readonly的时候focus该元素,不会触发滚动,使改元素进入视野

  4. ziven27说道:

    :focus-within + :focus 能够支持得更好一点,就能 纯 css 的实现点击显示下拉菜单,点击外面隐藏下拉菜单的功能了。

  5. 阿龙说道:

    focus()方法无效这个问题好像和ios系统版本还是手机型号有关,
    就我试的几个,ios10 Ipone6 会出现这个bug,ios14 iPhonexr, iPhone11是可以正常focus的

  6. 岳俊奇说道:

    大佬也开始用Vue了吗