启用 :focus only on keyboard use (or tab press)

Enable :focus only on keyboard use (or tab press)

我想在不需要时禁用 :focus,因为我不喜欢焦点位于其上时导航的外观。它使用与 .active 相同的样式并且令人困惑。但是我不想为使用键盘的人摆脱它。

我想在按下 Tab 键时在正文中添加一个 class enabled-focus 然后添加 body.enabled-focus a:focus{...} 但这会为每个添加很多额外的 CSS具有焦点的元素。然后在第一次按下鼠标时从正文中删除 class。

我该怎么做?有更好的解决方案吗?

这个问题你可能会经常遇到。这类问题的好处是,一旦你找到了解决办法,就不会再困扰你了。

最优雅的解决方案似乎是最简单的:不要删除 :focus 上的大纲,而是在 :active 上删除 - 毕竟,:active 是动态伪 class 显式处理单击或以其他方式激活可聚焦元素时应应用的样式。

a:hover, a:active { outline: none; }

此方法唯一的小问题:如果用户激活 link 然后使用浏览器的后退按钮,轮廓就会变得可见。哦,众所周知,旧版本的 Internet Explorer 会混淆 :focus、:hover 和 :active 的确切含义,因此此方法在 IE6 及以下版本中失败。

提示

有一个简单的解决方法可以通过添加一个简单的 overflow:hidden 来防止轮廓“溢出”,它可以检查元素本身可点击部分周围的轮廓。

更新:这个问题可能不再相关

have mentioned the :focus-visible pseudo class - which now has decent browser support...

我想补充一点,基于 spec which covers the :focus-visible pseudo class,浏览器现在应该仅在对用户有帮助时 指示焦点 - 例如在以下情况下用户通过键盘或其他一些非指点设备与页面交互

这基本上意味着原始问题不再相关,因为现在,当用户 clicks/taps 一个按钮(或另一个可聚焦元素)时,用户代理将不再显示聚焦环 - 甚至尽管按钮已聚焦 - 因为在这种情况下聚焦环对用户没有帮助。

来自 the spec:

While the :focus pseudo-class always matches the currently-focused element, UAs only sometimes visibly indicate focus (such as by drawing a “focus ring”), instead using a variety of heuristics to visibly indicate the focus only when it would be most helpful to the user. The :focus-visible pseudo-class matches a focused element in these situations only...

事实上,从版本 90 开始,Chromium 的用户代理样式表从 :focus 切换到 :focus-visible,并且由于这一变化,按钮点击不再调用焦点环

此外,从 version 87 开始,Firefox 还在其用户代理样式中使用 :focus-visible。

综上所述,如果需要自定义焦点样式,因为焦点样式现在已从 :focus 转移到 :focus-visible,当使用自定义焦点样式覆盖默认样式时 - :focus-visible 应该使用伪 class。

像这样:

button:focus-visible {
  /* remove default focus style */
  outline: none;
  /* custom focus styles */
  box-shadow: 0 0 2px 2px #51a7e8;
  color: lime;
}

向后兼容性:

像这样使用 :focus-visible 的可能问题是,不支持 :focus-visible 的浏览器将显示默认的焦点环,它可能不清晰或不可见 - 取决于设计.

Šime Vidas,在 this article 中描述了当前使用 :focus-visible 伪 class 的可行策略 - 即使在尚不支持 :focus-visible 的浏览器中也可以使用-

A good way to start using :focus-visible today is to define the focus styles in a :focus rule and then immediately undo these same styles in a :focus:not(:focus-visible) rule. This is admittedly not the most elegant and intuitive pattern, but it works well in all browsers:

Browsers that don’t support :focus-visible use the focus styles defined in the :focus rule and ignore the second style rule completely (because :focus-visible is unknown to them).

In browsers that do support :focus-visible, the second style rule reverts the focus styles defined in the :focus rule if the :focus-visible state isn’t active as well. In other words, the focus styles defined in the :focus rule are only in effect when :focus-visible is also active.

button:focus {
  outline: none;
  background: #ffdd00; /* gold */
}

button:focus:not(:focus-visible) {
  background: white; /* undo gold */
}

原答案:

This excellent article by Roman Komarov 提出了实现 仅键盘焦点样式 用于 按钮 链接的可行解决方案 和其他容器元素,例如 spansdivs(通过 tabindex 属性人为地使其可聚焦)

解决方案:

button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-size: inherit;
}

.btn {
  all: initial;
  margin: 1em;
  display: inline-block; 
}

.btn__content {
  background: orange;
  padding: 1em;
  cursor: pointer;
  display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
    position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content  {
    background: salmon;
}

.btn:active > .btn__content  {
    background: darkorange;
}

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8;
    color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
    outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

<a class="btn" href="#x">
    <span class="btn__content" tabindex="-1">
        I'm a link!
    </span>
</a>

<span class="btn" tabindex="0">
    <span class="btn__content" tabindex="-1">
        I'm a span!
    </span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing - behold - focus styles</p>

Codepen

  1. tabindex="-1" 将原始交互元素的内容包裹在一个额外的内部元素中(见下面的解释)

所以不要说:

<button id="btn" class="btn" type="button">I'm a button!</button>

这样做:

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>
  1. 将 css 样式移动到内部元素(布局 css 应保留在原始外部元素上)-因此外部元素的宽度/高度来自内在等

  2. 从外部和内部元素中删除默认焦点样式:

    .btn:焦点, .btn__content:焦点{ 大纲:none; }

  3. 将焦点样式添加回内部元素仅当外部元素具有焦点时:

    .btn:焦点 > .btn__content { 框阴影:0 0 2px 2px #51a7e8; /* 仅键盘焦点样式 / 颜色:石灰; / 仅限键盘的焦点样式 */ }

为什么这样做?

这里的技巧是用 tabindex="-1" 设置内部元素 - 参见 MDN:

A negative value (usually tabindex="-1" means that the element should be focusable, but should not be reachable via sequential keyboard navigation...

所以元素是 focusable 通过鼠标点击或编程,但另一方面 - 它不能通过键盘到达 'tabs'.

因此,当单击交互元素时 - 内部元素 获得焦点。不会显示焦点样式,因为我们已将其删除。

.btn:focus,
.btn__content:focus {
    outline: none;
}

请注意,在给定时间只能关注 1 个 DOM 元素(以及 document.activeElement returns 这个元素)- 所以 只有内部元素会被聚焦。

另一方面:当我们使用键盘进行 Tab 时 - 只有外部元素会获得焦点(记住:内部元素有 tabindex="-1" 并且不是无法通过顺序键盘导航访问)[请注意,对于本质上不可聚焦的外部元素,如可点击的 <div> - 我们必须通过添加 tabindex="0"]

人为地使它们可聚焦

现在我们的 CSS 开始并将仅键盘焦点样式添加到 the inner element

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles */
    color: lime; /* keyboard-only focus styles */
} 

当然,我们要确保当我们按 Tab 键并按 enter - 我们没有破坏我们的交互元素,javascript 将 运行.

这里有一个演示,说明情况确实如此,但请注意,尽管您只能免费获得此功能(即按 Enter 键以引发点击事件),但对于按钮和链接等固有的交互元素...对于其他span 等元素 - 您需要手动对其进行编码:)

//var elem = Array.prototype.slice.call(document.querySelectorAll('.btn'));
var btns = document.querySelectorAll('.btn');
var fakeBtns = document.querySelectorAll('.btn[tabindex="0"]');


var animate = function() {
  console.log('clicked!');
}

var kbAnimate = function(e) {
  console.log('clicking fake btn with keyboard tab + enter...');
  var code = e.which;
  // 13 = Return, 32 = Space
  if (code === 13) {
    this.click();
  }  
}

Array.from(btns).forEach(function(element) {
  element.addEventListener('click', animate);
});

Array.from(fakeBtns).forEach(function(element) {
  element.addEventListener('keydown', kbAnimate);
});
button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-size: inherit;
}

.btn {
  all: initial;
  margin: 1em;
  display: inline-block; 
}

.btn__content {
  background: orange;
  padding: 1em;
  cursor: pointer;
  display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
    position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content  {
    background: salmon;
}

.btn:active > .btn__content  {
    background: darkorange;
}

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8;
    color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
    outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

<a class="btn" href="#x">
    <span class="btn__content" tabindex="-1">
        I'm a link!
    </span>
</a>

<span class="btn" tabindex="0">
    <span class="btn__content" tabindex="-1">
        I'm a span!
    </span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing + enter - behold - our interactive elements work</p>

Codepen


注意:

  1. 尽管这似乎是一个过于复杂的解决方案,但对于非 javascript 解决方案而言,它实际上非常令人印象深刻。更简单的 css-only 'solutions' 涉及 :hover:active 伪 class 样式根本不起作用。 (当然,除非您假设交互式元素会像模式说中的按钮一样在单击时立即消失)

button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  font-size: inherit;
}

.btn {
  margin: 1em;
  display: inline-block; 
  background: orange;
  padding: 1em;
  cursor: pointer;
}

.btn:hover, .btn:active {
  outline: none;
}
<h2>Remove css :focus outline only on :hover and :active states</h2>

<button class="btn" type="button">I'm a button!</button>

<a class="btn" href="#x">I'm a link!</a>

<span class="btn" tabindex="0">I'm a span!</span>

<h3>Problem: Click on an interactive element.As soon as you hover out - you get the focus styling back - because it is still focused (at least regarding the button and focusable span) </h3>

Codepen

  1. 此解决方案并不完美:windows 上的 firefox 仍会在单击时获得按钮的焦点样式 - 但这似乎是一个 firefox 错误(请参阅 the article

  2. 当浏览器实现 :fo­cus-ring pseudo class - there may be a much simpler solution to this problem - (see the article) 对于它的价值,有 a polyfill for :focus-ring - see this article by Chris DeMars


仅键盘焦点样式的实用替代方案

因此实现仅键盘焦点样式非常困难。一种更简单并且可能既满足设计者的期望又易于访问的替代/解决方法是像悬停样式一样设置焦点样式。

Codepen

因此,虽然从技术上讲,这并没有实现纯键盘样式,但它实际上消除了对纯键盘样式的需求。

在尝试 Danield 接受的解决方案时,我找到了一种基于 inner/outer div 概念的更简单的替代方法。

1) 创建一个外部元素和内部元素。给外层元素 tabindex="0" 和内层元素 tabindex="-1"

<div role="button" class="outer" tabindex="0">
    <span class="inner" tabindex="-1">
        I'm a button!
    </span>
</div>

2) 在css中,聚焦时移除内部元素的轮廓:

.inner:focus{
    outline: none;
}

3) 将任何鼠标或单击事件处理程序应用于内部元素。将任何焦点事件(onfocus、onblur、onkeydown)应用到外部元素。

例如:

<div role="button" class="outer" tabindex="0" onfocus="focusEventHandler()" onkeydown="handleKeyDown.bind(this, myEventHandler)">
    <div class="inner" tabindex="-1" onClick="myEventHandler()">
        I'm a button!
    </div>
</div>

**保持尺寸和定位,使内部元素与外部元素完全重叠。将带有样式的整个 "button" 定位在外部元素上。

这是如何工作的:

当用户点击 "button" 时,他们点击的是移除了焦点轮廓的内部元素。无法单击外部元素,因为它被内部元素覆盖。当用户使用键盘切换到 "button" 时,他们会到达外部元素(tabindex="0" 使元素可通过 'tab' 访问),该元素获得焦点轮廓,但内部元素是无法通过选项卡访问(使用 tabindex="-1")并且在单击时不会收到焦点轮廓。

删除 outline 对可访问性来说太糟糕了!理想情况下,焦点环仅在用户 打算使用键盘时显示

2018答案:使用:focus-visible. It's currently a W3C proposal for styling keyboard-only focus using CSS. Until major browsers support it, you can use this robust polyfill。它不需要添加额外的元素或更改 tabindex.

/* Remove outline for non-keyboard :focus */
*:focus:not(.focus-visible) {
  outline: none;
}

/* Optional: Customize .focus-visible */
.focus-visible {
  outline-color: lightgreen;
}

我还写了一篇更详细的文章 post 以防您需要更多信息。

没有明确的解决方案。 我做了一个 Hackish 解决方案: 在您的主容器上应用点击事件并在点击时编写以下代码

    _handleMouseClick = (event) => {
        if(event.detail){
            document.activeElement.blur();
        }
    }

当您使用鼠标单击时,您将在该单击上获得 event.detail = 1 模糊该元素,以便它删除轮廓 在键盘上单击我们得到 event.detail = 0 所以在键盘情况下表现正常

在css文件中

     body.disableOutline *:focus{
        outline: none !important;
    }

在主要 js 中

     document.addEventListener('click', _handleMouseClick,true);
            document.addEventListener('keydown',_keydown,true);
            function _handleMouseClick(event){
                if(event.detail){
                    document.getElementsByTagName("body")[0].classList.add("disableOutline");
                }
            }
            function _keydown(e){
                document.getElementsByTagName("body")[0].classList.remove("disableOutline");
            }

案例研究:Facebook 登录页面

Facebook 现在(2018 年 6 月)在他们的登录页面上使用了一小部分 Javascript。

Javascript 检测用户何时单击鼠标或使用键盘,并在 body 上打开和关闭 class:<body class="using-mouse">

然后 CSS 规则可以使用 class 来显示或隐藏相关元素上的适当焦点样式。

这是一些示例代码(也可用 on CodePen)。比较点击和 Tab 键。

// Let the document know when the mouse is being used
document.body.addEventListener('mousedown', function() {
  document.body.classList.add('using-mouse');
});

// Re-enable focus styling when Tab is pressed
document.body.addEventListener('keydown', function(event) {
  if (event.keyCode === 9) {
    document.body.classList.remove('using-mouse');
  }
});

// Alternatively, re-enable focus styling when any key is pressed
//document.body.addEventListener('keydown', function() {
//  document.body.classList.remove('using-mouse');
//});
/* The default outline styling, for greatest accessibility. */
/* You can skip this to just use the browser's defaults. */
:focus {
  outline: #08f auto 2px;
}

/* When mouse is detected, ALL focused elements have outline removed. */
body.using-mouse :focus {
  outline: none;
}
<input>
<button>Submit</button>

注意上面的:focus相当于*:focus,匹配所有元素。如果您只想从按钮中删除样式,则可以将 button:focus 放在那里。


案例研究:GMail 登录页面

另外,无论用户是使用鼠标还是键盘,GMail 只是在样式上使用比未聚焦按钮更重的阴影来设置聚焦按钮的样式。

这很容易实现和理解,不需要任何 Javascript。

:focus {
  outline: none;
  box-shadow: 0 0px 16px #0005;
}

但这是一种妥协。它传达了鼠标用户并不真正感兴趣的焦点信息,对于键盘用户来说可能有点太微妙

不过,这种折衷可能比任何一个极端(对所有用户都有一个强大纲,或者根本没有大纲)要好。


Whosebug 的主要按钮使用与 GMail 类似的方法,但外观更具风格:

box-shadow: inset 0 1px 0 0 rgba(102,191,255,0.5), 0 0 0 4px rgba(0,149,255,0.15);

就我个人而言,为了便于访问,我会使用更强烈(对比度更高)的颜色。

&:focus:not(:hover) { }

它不会在 100% 的情况下工作,但我认为对于大多数人的需求来说这应该足够了。

它将阻止 :focus 状态在点击时触发,因为鼠标必须悬停在元素上才能点击它。

https://codepen.io/heyvian/pen/eopOxr

2020 年更新

:focus-visible 已稳定 Chrome。只需使用它!仅缺少对 IE 和 Safari 的支持,因此添加后备方案(如下)。

需要一个很好的功能查询来区分 IE11 + Safari 和其他浏览器?这是一个 SCSS mixin:

@mixin focus-visible-fallback {
  @supports (contain: none) {
    &:focus-visible {
      outline: none;
      @content;
    }
  }

  /* Safari & IE11 */
  @supports not (contain: none) {
    &:focus {
      outline: none;
      @content;
    }
  }
}

注意:如评论中所述,无论使用:focus还是:focus-visible<input>将始终获得焦点。

原创POST

直到 :focus-visible 未出现在所有流行的常青浏览器中,您可以在 CSS 的全局部分使用这个简单的技巧,无需任何 polyfill:

@media (pointer: coarse) {
  *:focus {
    outline: none;
  }
}

然后像往常一样添加焦点效果,:focus

此时您可能了解到,从可访问性的角度来看,将 outline: none 默认设置为焦点元素是一个糟糕的想法。确实如此。

但是,如果您将此规则限定在 pointer: coarse 媒体查询中,它将变得非常有用,因为它只适用于手机和平板电脑,而不适用于台式机。这正是您想要实现的目标。

我唯一能想到的问题是使用键盘的移动用户,他们使用键盘在内容中切换,但我不确定是否有很多这样的用户。所以,最终 :focus-visible 将是一个更好的解决方案,但现在这应该足够了。

blueprintjs 库有一个很好的解决方案。

https://blueprintjs.com/docs/#core/accessibility

但是,我还不明白他们是怎么做到的。

正如其他人所提到的,:focus-visible 选项具有很好的浏览器支持 https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible

我觉得这篇文章很有用,想分享它https://css-tricks.com/keyboard-only-focus-styles/

SCSS 中的示例:

button {

 &:focus-visible {
    border: 2px solid #004EA3;
  }
}

正如一些人所说,:focus-visible 是纯 CSS 解决方案的方法。我想提供我仅使用 CSS 解决此样式问题的最简单方法,但是它在浏览器支持方面有一些缺点并且对所有人都没有用:

@supports not selector(:focus-visible) {
    :focus {
        // Fallback styles for browsers that doesn't support :focus-visible.
    }
}

:focus-visible {
    // Tab focus styles
}

仅在确实需要防止干扰 :focus-visible 样式时应用 :focus:focus-visible 将被不支持它的浏览器简单地忽略,因此不需要 @supports 或类似的东西。

详细了解 @supports selector and :focus-visible 的浏览器支持。

如果你碰巧像我一样在 JS 中使用 CSS,你可以利用 CSS.supports() 有条件地呈现回退 CSS 以防你需要关心不支持的浏览器'支持@supports selector。类似于 if (CSS.supports("selector(:focus-visible)")) {...}.