[译] 在网页中隐藏元素

2,027 阅读12分钟

原文链接:Hiding Elements On The Web,by Ahmad Shadeed

在平时的开发工作中,有时会有隐藏元素的需求。比如,一个按钮,需要在桌面端隐藏,在手机端显示;一个导航栏,需要在手机端隐藏,在桌面端显示。“隐藏”不是字面上展示的这样简单,它还包含几层意思:

  • 元素完全从文档流中移除的隐藏。
  • 元素仅仅是视觉上的隐藏,使用像屏幕阅读器(screen reader)这样的辅助技术(assistive technology,简称“AT”)依然是能访问的。
  • 元素在视觉上可见,不过对屏幕阅读器是隐藏的,即屏幕阅读器是无法访问的。

通过本篇文章,你将会学到利用 HTML/CSS 隐藏元素的方法,内容涵盖了可访问性、动画和使用案例。我们一起来看看吧!

HTML5 hidden 属性

这是在 HTML 标签上使用的一个布尔值属性。在浏览器加载页面的时候,使用 hidden 属性修饰的元素,渲染效果与 display: none 类似。当然,如果使用 CSS 手动重写了 hidden 属性的话,就另当别论了。

来看下面的例子:

这部分包含了一个标题、图片和描述信息。图片只会在视口宽度大于 400px 的时候才显示。这里我给 <img> 加了一个 hidden 属性。

我写了一段 CSS 代码,让 hidden 属性修饰的图片在视口宽度大于 400px 的时候显示。

img[hidden] {
  display: none;
}

@media (min-width: 400px) {
  img[hidden] {
    display: block;
  }
}

下面是在大视口(大于 400px)下的渲染效果。

Demo

你可能要问了,为什么不直接使用 display: none 呢?好问题。图片使用 hidden 属性控制带来的好处是——即使 CSS 由于某种原因没有加载成功,图片也会隐藏。

hidden 属性对可访问性的影响

hidden 会将元素完全从页面隐藏,所以屏幕阅读器是无法访问。如果只是出于视觉表现的目的,一定要避免使用。

CSS display 属性

每个元素都有一个默认的 display 值,可能是 inline-blockblocktable 等等。我们使用 display: none 就能达到隐藏元素的效果,并且该元素的所有后代都和它一起隐藏了。

与上面代码类似,不过这次我们使用了 display: none

img {
  display: none;
}

@media (min-width: 400px) {
  img {
    display: block;
  }
}

display: none 会将元素从文档流中移除,屏幕阅读器也是无法访问到。那么,什么是文档流?我们可以用下面的图片来生动地描述它:

我们把蓝皮书用 display: none 隐藏了,最后发现它从这摞书里完全消失了,就好像被抽走了一样。蓝皮书原来占据的空间不存在了。HTML 于此类似,说明文档流被改变了。

下面的动画,展示了蓝皮书被移除时的状况:

stack-of-books.gif

图片隐藏后,还会加载吗?

会的。举个例子,<img> 默认使用 CSS 隐藏,我们将它设置在某个断点处显示。我们打开 DevTools,检查 networks 选项卡,还是发现图像被加载了,即使没有显示。

Demo

<style> 元素

值得一提的是,某些元素默认就是 display: none 的。比如 <style> 。我们可以把插入到 HTML 页面中的 <style>display 值设置为 block,这样就能看见它了。

<body>
    <style>
       .title { color: #000; }
    </style>
</body>
style {
    display: block;
}

这事如果在让样式块(style block)变得可编辑,就更有趣了。可以通过为 <style> 添加 contenteditable 属性来实现。

style-tag.gif

Demo(译注:在 Firefox 中能看到上面的效果,而在 Chrome 里是看不到的)

display:none 对可访问性的影响

hidden 属性一样,display: none 元素也是从页面中完全隐藏的,屏幕阅读器也无法访问。

opacity

元素设置 opacity 后,包括后代都会被隐藏,这不是因为继承的原因。然而,这只是视觉上的隐藏。还要注意的是,opacity 值小于 1 的元素会创建一个 层叠上下文


上图里,蓝皮书只是在视觉上隐藏了。它所占据的空间仍然保留,与 display: none 效果比较的话,这一摞书的顺序没有发生变化。

img {
    opacity: 0;
}

我们把最开始的例子,用 opacity: 0 重新改写下,结果会是这样的:

图片从视觉上隐藏了,但它占据空间依然是在的。

Dusan Milovanovic 指出,pointer-events: none | auto 可以用来禁用使用 opacity: 0 隐藏的元素上的鼠标事件。这还是很重要的,如果一个元素看不见了,但依然能响应用户的点击、悬停或选择文本的行为,勘定是很奇怪的。

Demo

opacity: 0 对可访问性的影响

使用 opacity: 0 隐藏的元素依然能被屏幕阅读器访问,也能被键盘聚焦。

visibility

使用 visibility: hidden 的隐藏元素与使用 opacity: 0 的元素类似,不影响视觉上的文档流表现。

请注意,蓝皮书从可视流中隐藏了,但并没有影响这摞书的顺序。

有一点,如果 visibility: hidden 是在父元素身上使用,那么它及其它的后代默认都是看不见的。但是,如果有一个子元素上使用了 visibility: hidden,那么这子元素将是可见的。

<article>
  <h1>Spring is on the way</h1>
  <img src="landscape.jpg" alt="">
  <p><!-- Desc --></p>
</article>
article {
    visibility: hidden;
}

img {
    visibility: visible;
}

visibility-child.gif

上例中,<article> 使用了 visibility: hidden,而子元素 <img>visibility: visible 的,结果图片依然是显示的。也就是说,子元素是可以重写父元素的 visibility 属性的。

Demo

visibility: hidden 对可访问性的影响

如果一个元素 visibility: hidden 了,那么它以及它的所有后代元素都会从访问树(accessibility tree)中删除,不会被屏幕阅读器读到。

CSS position 属性

使用 position 属性将隐藏元素的原理,就是把元素移动到屏幕之外,设置其尺寸为 0 (宽和高)。比如,网页里 跳过导航(skip navigation) 链接:

为了让链接定位到屏幕之外,我们可以这么做:

.skip-link {
    position: absolute;
    top: -100%;
}

-100% 会将元素网上推出一个视口高度的距离。结果,链接就被完全隐藏了。当链接被键盘聚焦时,则可以这样设置:

.skip-link:focus {
    position: absolute;
    top: 0;
}

Demo

position: absolute | fixed 对可访问性的影响

元素可以被屏幕阅读器读到,被键盘聚焦。只是移动到视口之外了而已。

clip-path

clip-path 属性用来创建一个裁剪区域,只有在这个区域内的元素内容才是可见的,其他部分则是隐藏的。


上面的图片经过裁剪,两边的透明黑色区域看不见了。

为了用更直观的方式演示,我使用 clippy 工具来解释。在下面的 GIF 图中,我定义了如下的 clip-path

img {
    clip-path: polygon(0 0, 0 0, 0 0, 0 0);
}

clip-path-1.gif

将多边形每个点的坐标设置为 (0, 0),则裁剪区域变为 0。结果,图像不会显示了。同样,还可以用一个圆(circle)来代替这里的多边形(polygon):

img {
    clip-path: circle(0 at 50% 50%);
}

clip-path-2.gif

使用 clip-path 实现的隐形效果只是视觉上的,屏幕阅读器依然可以访问,键盘也能聚焦。

Demo

操作 colorfont-size

尽管这两种技术并不像我们前面讨论的那样普遍,但在某些场景下比较有用。

color: transparent

将文本设置成透明色(transparent),只是视觉上隐藏了。这比较适合仅带图标的按钮。

font-size: 0

将文字大小设置为 0 也是视觉上的隐藏。

来看一个包含如下结构的按钮:

<button>
  <svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="false" focusable="false">
    <!-- Path data -->
  </svg>
  <span>Like</span>
</button>

我们的目标是以一种能被访问的方式隐藏文本。为此,我使用了下面的 CSS:

.button span {
    color: transparent;
    font-size: 0;
}

文本隐藏了。

Demo

aria-hidden

当为元素添加 aria-hidden 后,就会从访问树中删除,这可以用来提升屏幕阅读器用户的体验。需要注意的是,元素依然是视觉可见的。

<button>
    Menu
    <svg aria-hidden="true"><!-- --></svg>
</button>

这里是一个带 label 和图表的菜单按钮。为了让 svg 对屏幕阅读器隐藏,这里添加了 aria-hidden

根据 MDN 文档,aria-hidden 的使用场景包括:

  • 用来隐藏修饰性内容,比如图标、图片。
  • 隐藏重复的文字。
  • 隐藏屏幕外的或折叠的内容。

aria-hidden 是为屏幕阅读器设计的,因此它仅对屏幕阅读器隐藏内容。但是,内容对于视觉用户仍然可见,并且也支持键盘聚焦。

动画和交互

速查表

在我们开始示例之前,我想带大家回顾一下前面提到的属性,我从 CSS-tricks 上的 这篇文章 获得了灵感,做了下面的一张速查表,方便大家在使用时根据需要选择合适的方法。

image.png

View on Codepen

当我们想对隐藏元素使用动画的时候。比如,显示隐藏的移动导航,需要以一种可访问的方式来实现。为了获得访问性体验,我们将探索一些值得学习的好例子,以及一些不好的例子,避免犯错误,从而给屏幕阅读器用户带来更好的体验。

菜单动画 - 不好的例子

我们有一个菜单,在展开时使用一个划入的动画。最简单的做法是使用下面的方法:

ul {
    opacity: 0;
    transform: translateX(100%);
    transition: 0.3s ease-out;
}

ul.active {
    opacity: 1;
    transform: translateX(0);
}

根据这种方法,菜单在添加 .active 类的时候显示,否则折叠。这个类是通过 JavaScript 添加的,如下所示。

menuToggle.addEventListener('click', function(e){
  e.preventDefault();
  navMenu.classList.toggle('active');
});

menu-bad-example.gif

结果看起来不错,但它有一个大问题。使用 opacity: 0 的方式不会将导航从访问树中删除 。即便导航在视觉上隐藏了,可它仍然能被键盘聚焦,而且也能被屏幕阅读器读到。

下面是来自 Chrome DevTools 的访问树截图:

下面的截图,则是 Mac OS 上的访问工具 VoiceOver 看到的页面内容。跟上面一样,

简而言之,访问树是屏幕阅读器用户可以访问的所有内容的列表。在我们的例子中,包含一个导航列表,虽然它视觉上是隐藏的,但还是出现在了访问树中。因此,隐藏菜单时我们需要解决两个问题:

  1. 不能被键盘聚焦
  2. 不能被屏幕阅读器访问

Demo

菜单动画 - 好的例子

为了解决上面的文问题,我们需要在菜单导航上使用 visibility: hidden。这确保菜单不仅在视觉上是隐藏的,屏幕阅读器也无法访问。

ul {
    visibility: hidden;
    opacity: 0;
    transform: translateX(100%);
    transition: 0.3s ease-out;
}

ul.active {
    visibility: visible;
    opacity: 1;
    transform: translateX(0);
}

添加后,现在菜单对屏幕阅读器也是隐藏的了。让我们再来测试一下 VoiceOver  的结果:

Demo

自定义复选框

默认的复选框设计很难自定义。因此,我们需要为复选框设计自定义样式。一般会这么做:

<p class="c-checkbox">
  <input class="sr-only" type="checkbox" name="" id="c1">
  <label class="c-checkbox__label" for="c1">Custom checkbox</label>
</p>

要自定义复选框,需要以可访问的方式隐藏输入框。为此,需要借助 position 等其他属性来实现。有一个常见的CSS 类,称为 sr-onlyvisual-hidden,用来在视觉上隐藏一个元素,但键盘和屏幕阅读器依然能访问。

.sr-only {
  border: 0; 
  clip: rect(0 0 0 0); 
  -webkit-clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
  clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
  height: 1px; 
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
  white-space: nowrap;
}

这样,就可以访问自定义复选框。如果你想了解更多,我写了关于这个主题的 一篇文章

Demo

对屏幕阅读器隐藏内容


在标题中,我使用了一个表情符号。如果隐藏,那么屏幕阅读器会使用下面的方式阅读:

Hiding On The Web grinning face with open mouth

每个表情符号都有一个对应的特定描述,屏幕阅读器阅读时会使用这个描述。想象你现在正在浏览一个网页,突然听到这个标题,读到后面,可能就有点懵逼了。为了避免这种混淆,可以使用 aria-hidden,把表情包设定为对屏幕阅读器隐藏。

<h1>Hiding On The Web <span aria-hidden="true">😃</span></h1>

小故事,大道理小改变,大胜利!

隐藏按钮

在 Twitter 上,有一个名为“See New Tweets”的按钮,默认使用 aria-hidden 对屏幕阅读器隐藏,只在有新推文可才会显示。

隐藏修饰性的内容

用户 ID 和日期之间的点是装饰性的。因此,使用了 aria-hidden="true" 避免被屏幕阅读器读到。

相关文章

(完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。

瞄~