技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTHTML

译在网页中隐藏元素

所属馆藏
HTML
文件格式
Markdown
原始路径
HTML/01-[译]在网页中隐藏元素
本文目录14 个章节
  1. HTML5 hidden 属性
  2. CSS display 属性
  3. opacity
  4. visibility
  5. 把元素移出视口(position)
  6. clip-path
  7. 操作 color 和 font-size
  8. aria-hidden
  9. 动画和交互
  10. inert 属性(补充)
  11. 自定义复选框(sr-only)
  12. 对屏幕阅读器隐藏内容
  13. 总结
  14. 参考资料

在网页中隐藏元素

Category(分类): HTML Status: 未知

本文基于 Ahmad Shadeed 的《Hiding On The Web》翻译整理,并对其中的部分结论进行了勘误与补充。

在平时的开发工作中,经常会有隐藏元素的需求。比如:一个按钮需要在桌面端隐藏、在手机端显示;一个导航栏需要在手机端隐藏、在桌面端显示。

“隐藏”并不像字面上那么简单,它至少包含三层不同的含义:

  • 元素完全从文档流中移除,既不可见,也无法交互;
  • 元素只是视觉上隐藏,但使用屏幕阅读器(screen reader)等辅助技术(assistive technology,简称 AT)仍然能访问;
  • 元素视觉上可见,但对屏幕阅读器隐藏,即屏幕阅读器无法访问。

选择哪种方式,取决于你是想“视觉隐藏”“交互隐藏”还是“无障碍隐藏”。本文会逐一介绍用 HTML / CSS 隐藏元素的各种方法,并说明它们对可访问性、动画和资源加载的影响。

HTML5 hidden 属性

hidden 是一个布尔属性。浏览器为它内置了默认的用户代理样式,效果等价于 display: none,因此被它修饰的元素既不渲染,也不占据布局空间,同时会从可访问性树中移除。

<img src="cover.jpg" hidden alt="文章封面">

hidden 的一个好处是:即使 CSS 因某种原因没有加载成功,元素也会被隐藏,因为它的隐藏效果来自浏览器内置样式,而不依赖外部 CSS。

来看一个例子:页面上有一段包含标题、图片和描述的内容,我们希望图片只在视口宽度大于 400px 时才显示。这里先给 <img> 加上 hidden 属性作为默认隐藏状态:

然后用一段 CSS,让带 hidden 属性的图片在大视口下重新显示:

img[hidden] {
  display: none;
}

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

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

Demo

注意:由于 hidden 等价于 display: none,它无法用于过渡动画display 不是可动画属性)。如果你需要隐藏/显示的动画效果,应该使用下面会讲到的 opacityvisibilitytransform

hidden 属性对可访问性的影响

hidden 会将元素完全从页面隐藏,屏幕阅读器无法访问。如果只是为了视觉表现,应避免使用它。

CSS display 属性

每个元素都有一个默认的 display 值,可能是 inlineblockinline-blocktable 等。把 display 设为 none 就能隐藏元素,并且该元素的所有后代都会随之一并隐藏。

把上面的例子换成 display: none

img {
  display: none;
}

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

display: none 会把元素从文档流中完全移除,就好像它根本不存在一样——原来占据的空间也随之消失,周围的元素会重新排列。可以用一摞书来形象地说明:

把蓝色的那本书用 display: none 隐藏,它就从这摞书里完全消失了,原来占据的位置被后面的书填补。HTML 与此类似,说明文档流被改变了。下面的动画展示了蓝皮书被移除时的状况:

display: none 的图片还会加载吗?

这取决于浏览器实现和图片的 loading 属性,不能一概而论

  • 默认 loading="eager" 的图片,部分浏览器在某些情况下仍可能发起请求;
  • 带有 loading="lazy" 的图片,当其本身或某个祖先display: none 时,现代 Chrome / Firefox / Safari 通常不会发起请求,等到它真正可能显示时再考虑加载;
  • display: none 本质上是渲染/布局属性,不是可靠的“控制资源是否加载”的手段。

因此,==如果想可靠地避免图片加载,应该==:

  • 使用原生 loading="lazy"
  • 或者在真正需要时再通过 JavaScript 设置 src(配合 data-src);
  • 不要依赖 display: none 来“阻止” eager 图片的下载。

原译文断言“display: none 的图片一定会加载”,这个结论在当今主流浏览器中并不总是成立,使用时请以实际测试为准。

<style> 元素

有些元素默认就是 display: none 的,例如 <style>。如果把它设为 display: block,原本不可见的样式块就能显示在页面上:

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

如果再给 <style> 加上 contenteditable 属性,让样式块变得可编辑,效果会更有趣:

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

display: none 对可访问性的影响

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

opacity

给元素设置 opacity: 0 后,元素及其后代都会在视觉上隐藏(这不是因为继承,而是 opacity 会整体作用于元素及其内容)。但这只是视觉上的隐藏:元素仍然占据布局空间,仍然可以接收鼠标事件,也仍然可以被键盘聚焦、被屏幕阅读器访问。

另外需要注意:opacity 小于 1 的元素会创建一个层叠上下文

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

img {
  opacity: 0;
}

把开头的例子用 opacity: 0 改写后,图片在视觉上消失了,但它占据的空间依然存在:

Dusan Milovanovic 指出,可以用 pointer-events: none 来禁用 opacity: 0 隐藏元素上的鼠标事件。这很重要——如果一个元素看不见,却仍然能响应点击、悬停或文本选择,肯定是很奇怪的:

img {
  opacity: 0;
  pointer-events: none;
}

Demo

opacity: 0 对可访问性的影响

使用 opacity: 0 隐藏的元素依然能被屏幕阅读器访问,也能被键盘聚焦。如果不想让它被交互,需要额外配合 visibility: hiddeninertpointer-events: none 等。

visibility

visibility: hiddenopacity: 0 类似,元素会在视觉上隐藏,但不影响文档流,仍然占据布局空间。

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

==visibility 有一个特殊的行为:如果父元素设置了 visibility: hidden,那么它及其后代默认都不可见;但如果某个子元素显式设置 visibility: visible,那么这个子元素会重新变为可见==。

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

img {
  visibility: visible;
}

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

Demo

此外,visibility可动画属性,可以配合 transition 实现显示/隐藏的过渡。这也是为什么后面“菜单动画”的推荐写法会选择它。

visibility: hidden 对可访问性的影响

visibility: hidden 的元素会从可访问性树中移除,屏幕阅读器一般无法访问到它,并且它通常无法被键盘聚焦(这点和 opacity: 0 不同)。

注意:如果隐藏元素的某个后代被显式设为 visibility: visible,该后代通常会重新进入可访问性树。不过各浏览器/辅助技术对此的实现并不完全一致,不建议依赖“父级隐藏 + 子级可见”这种模式来做无障碍处理。如果某块内容需要被屏幕阅读器读到,最好把它从隐藏的祖先中独立出来。

把元素移出视口(position

另一种隐藏思路是:不改变元素的可见性,而是通过定位把它移到屏幕之外,或者把它的尺寸压缩为 0。一个典型场景是网页中的跳过导航(skip navigation)链接:

为了让链接默认不可见,可以把它绝对定位到视口之外:

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

这里的 top: -100%相对于最近的定位祖先(包含块)的高度计算的,不一定等于视口高度——只有当包含块是初始包含块(近似视口)或其高度恰好等于视口高度时,才相当于“上移一个视口高度”。当链接被键盘聚焦时,再把它移回可见位置:

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

Demo

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

移出视口的元素仍然可以被屏幕阅读器读到、被键盘聚焦,它只是视觉上不在屏幕内而已。

clip-path

==clip-path 用来创建一个裁剪区域,只有落在区域内的内容才可见,其余部分被隐藏。==

借助 clippy 工具可以直观地理解。下面的写法把多边形所有顶点都设在 (0, 0),裁剪区域退化为 0,图片也就完全不可见了:

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

也可以用一个半径为 0 的圆来代替多边形:

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

clip-path 实现的隐藏只是视觉上的,屏幕阅读器仍然可以访问,键盘也能聚焦。

Demo

操作 colorfont-size

下面这两种手段不如前面的方法通用,但在特定场景下比较有用。

color: transparent

把文本颜色设为透明,只是视觉上隐藏了文字,适合用于“仅图标按钮”的文本部分。

font-size: 0

把字号设为 0,同样是视觉上的隐藏。

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

<button>
  <svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
    <!-- 路径数据 -->
  </svg>
  <span>Like</span>
</button>

目标是用一种可被访问的方式隐藏文本(只保留图标)。为此可以使用下面的 CSS:

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

文本就这样被视觉隐藏了,但仍可被屏幕阅读器读出。

Demo

aria-hidden

给元素添加 aria-hidden="true" 后,它会从可访问性树中移除,用于提升屏幕阅读器用户的体验。需要注意的是,元素在视觉上依然可见。

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

这里是一个带文字和图标的菜单按钮。为了让 SVG 图标对屏幕阅读器隐藏,给它加上了 aria-hidden

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

  • 隐藏装饰性内容,如图标、分隔符、纯装饰图片;
  • 隐藏重复的文字;
  • 隐藏屏幕外的或折叠起来的内容。

⚠️ 关键警告:不要把 aria-hidden 用在可聚焦元素上

aria-hidden 只是把元素从可访问性树中移除,并不会阻止键盘聚焦。如果把 aria-hidden="true" 用在一个可交互、可聚焦的元素(或包含可聚焦后代的祖先)上,键盘用户依然能 Tab 到它,而屏幕阅读器却无法识别它,这会造成严重的可访问性问题。

<!-- ❌ 错误:键盘能聚焦,但屏幕阅读器读不到 -->
<button aria-hidden="true">删除</button>

<!-- ❌ 同样错误:后代链接仍然可聚焦 -->
<div aria-hidden="true">
  <a href="/settings">设置</a>
</div>

正确做法:

  • 装饰性、非交互内容 → 使用 aria-hidden="true"
  • 当前不可用的内容或控件 → 同时从视觉和交互中移除,优先使用 hiddendisplay: nonevisibility: hidden
  • 整块暂时不可用的区域(如抽屉式导航、模态框背后的内容)→ 使用 inert 属性,统一移除其后代的焦点与交互能力。

动画和交互

速查表

在进入示例之前,先回顾一下前面提到的各种属性。我从 CSS-Tricks 的这篇文章获得灵感,整理了一张速查表,方便根据需要选择合适的方法:

方法是否占布局空间是否可被屏幕阅读器访问是否可被键盘聚焦是否适合动画
hidden / display: none
opacity: 0
visibility: hidden否(一般)是(可动画)
移出视口(position是(占原位)
clip-path(裁剪为 0)
aria-hidden="true"是(仍可见)是(需避免)不涉及
inert是(仍可见)不涉及

当我们需要对“显示/隐藏”做动画(例如展开移动端导航)时,必须以可访问的方式来实现。下面分别看一个不好的例子和一个好的例子。

菜单动画 - 不好的例子

菜单展开时使用划入动画,最简单的写法是这样:

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');
});

效果看起来不错,但它有一个大问题:opacity: 0 不会把导航从可访问性树中移除。即便菜单在视觉上隐藏了,它仍然能被键盘聚焦,也仍然能被屏幕阅读器读到。

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

下面是 macOS 上的辅助工具 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

补充:如果整个隐藏区域包含多个可交互元素,逐个用 visibility: hidden 比较繁琐,更现代的做法是给容器加上 inert 属性(见下文),一次性移除内部所有元素的焦点与交互。

inert 属性(补充)

inert 是一个全局属性,用于把某个区域整体“冻结”:被它修饰的元素及其后代会从 Tab 顺序中移除、无法被聚焦、不响应点击等交互,并从可访问性树中隐藏。它非常适合配合模态框、抽屉导航等场景使用:

<div class="page" inert>
  <!-- 当模态框打开时,整页背景内容被冻结 -->
</div>

<dialog open>...</dialog>
// 打开模态框时
pageEl.inert = true
// 关闭时
pageEl.inert = false

相比手动给每个元素加 aria-hiddentabindex="-1"pointer-events: noneinert 用一行就能完成,而且语义更准确。主流浏览器已普遍支持。

自定义复选框(sr-only

原生复选框很难自定义样式。常见的做法是隐藏真正的 <input>,用 <label> 模拟外观:

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

为了让 <input> 在视觉上消失但保留键盘和屏幕阅读器的可访问性,需要一个俗称 sr-only(screen-reader-only)或 visually-hidden 的工具类。它通过“绝对定位 + 裁剪 + 1px 尺寸”把元素压缩成一个看不见的点:

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

这样既隐藏了原生外观,又能正常访问自定义复选框。.sr-only:focus / .sr-only:focus-visible 时还可以把它恢复显示,方便键盘用户看到焦点。更多细节可以参考这篇文章

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" 避免被屏幕阅读器读出来。

总结

“隐藏”是一个需要分情况讨论的话题,关键是先想清楚你想达到哪一种效果:

目标推荐做法
完全移除(不可见、不占位、不可访问、不可交互)hidden 属性 或 display: none
仅视觉隐藏,仍可访问、可交互opacity: 0(配合 pointer-events: none
视觉隐藏、不可访问、不可聚焦,但保留布局空间visibility: hidden
仅对屏幕阅读器隐藏,视觉仍可见aria-hidden="true"(且元素本身不可聚焦)
整块区域冻结(不可聚焦、不可交互、不可访问)inert 属性
视觉隐藏但保留可访问性(如自定义控件).sr-only / visually-hidden 工具类

几条容易踩坑的要点:

  1. opacity: 0 和移出视口的元素仍然可被聚焦和访问,做菜单/弹层隐藏时要额外处理;
  2. aria-hidden 不能用在可聚焦元素上,否则键盘能聚焦、屏幕阅读器却读不到;
  3. display: none 不是可靠的“阻止图片加载”手段,行为随浏览器和 loading 属性变化;
  4. 需要“显示/隐藏”动画时,优先用 visibility + opacity 组合,或用 inert 管理整块交互区域。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS