使用 CSS 让元素不可见的方法很多,剪裁、定位到屏幕外、明度变化等都是可以的。虽然它们都是肉眼不可见,但背后却在多个维度上都有差别。
下面是我总结的一些比较好的隐藏实践。
<script>标签隐藏。例如:<script type="text/html">
<img src="1.jpg">
</script>
此时,图片 1.jpg 是不会有请求的。<script>标签是不支持嵌套的,因此,如果希望在<script>标签中再放置其他不渲染的模板内容,可以试试使用<textarea>元素。例如:
<script type="text/html">
<img src="1.jpg">
<textarea style="display:none;">
<img src="2.jpg">
</textarea>
</script>
图片 2.jpg 也是不会有请求的。另外,<script>标签隐藏内容获取使用 script.innerHTML,<textarea>使用textarea.value。
display:none隐藏。例如:.hidden {
display: none;
}
transition淡入淡出效果,则可以使用:.hidden {
position: absolute;
visibility: hidden;
}
visibility:hidden隐藏。例如:.hidden {
visibility: hidden;
}
clip 剪裁隐藏。例如:.clip {
position: absolute;
clip: rect(0 0 0 0);
}
.out {
position: relative;
left: -999em;
}
relative 隐藏。例如,如果条件允许,也就是和层叠上下文之间存在设置了背景色的父元素, 则也可以使用更友好的 z-index 负值隐藏。例如:.lower {
position: relative;
z-index: -1;
}
.opacity {
position: absolute;
opacity: 0;
filter: Alpha(opacity=0);
}
.opacity {
opacity: 0;
filter: Alpha(opacity=0);
}
大家可以根据实际的隐藏场景选择合适的隐藏方法。不过,实际开发场景千变万化,上面罗列的实践不足以覆盖全部情形。
下面我们来介绍两个最为重要是属性:display
对一个元素而言,如果 display 计算值是 none 则该元素以及所有后代元素都隐藏,如果是其他 display 计算值则显示。
display 可以说是 Web 显隐交互中出场频率最高的一种隐藏方式,是真正意义上的隐藏,干净利落。人们对它的认识也比较准确,无法点击,无法使用屏幕阅读器等辅助设备访问,占据的空间消失,但很多人就仅局限于此了,实际上,display:none 的故事并不只有这么一点点。
在 Firefox 浏览器下,display:none 的元素的 background-image 图片是不加载的,包括父元素 display:none 也是如此;如果是 Chrome 和 Safari 浏览器,则要分情况,若父元素 display:none,图片不加载,若本身背景图所在元素隐藏,则图片依旧会去加载;对 IE 浏览器而言,无论怎样都会请求图片资源。
CSS 和 HTML 代码如下:
.bg1 {
background: url(1.png);
}
.bg2 {
background: url(2.png);
}
<div hidden class="bg1"></div>
<div hidden><div class="bg2"></div></div>
Chrome 浏览器下的网络请求如下图所示。

我们发现只加载了 1.png,因此,在实际开发的时候,如头图轮播切换效果,那些默认需要隐藏的图片作为背景图藏在隐藏元素的子元素上,微小的改动就可以明显提升页面的加载体验,可以说是非常实用的小技巧。
另外,如果不是 background-image 图片,而是<img>元素,则设置 display:none 在所有浏览器下依旧都会请求图片资源。
照理说,display:none 的元素应该是无法被点击的,display:none 可以非常彻底地隐藏,肯定不能点击啊!但是,下面这种情况却例外:
<form>
<input id="any" type="submit" style="display:none;">
<label for="any">提交</label>
</form>
此处 submit 类型的“提交”按钮设置了 display:none,但是当我们点击“提交”的时候,隐藏的按钮依然会触发 click、触发表单提交,此现象出现在 IE9 及以上版本浏览器以及其他现代浏览器中。
设置 display:none 的意义在于,当按钮和<label>元素不在一个水平线上的时候,点击<label>元素不会触发锚点定位。但是我并不推荐这么做,因为 submit 按钮会丢失键盘可访问性。
HTML 中有很多标签和属性天然 display:none,如<style>、<script>和 HTML5 中的<dialog>元素(如果浏览器支持)。如果这些标签在<body>元素中,设置 display: block 是可以让内联 CSS 和 JavaScript 代码直接在页面中显示的。例如:
<style style="display:block;">
.l { float: left; }
</style>
页面上就会出现 .l { float: left; } 的文本信息;如果再设置 contenteditable= "true",在有些浏览器下(如 Chrome),甚至可以直接实时编辑预览页面的样式。
还有一些属性也是天然 display:none 的。例如,hidden 类型的<input>输入框:
<input type="hidden" name="id" value="1">
专门用来放置类似 token 或者 id 这样的隐藏信息,这也说明表单元素的显示与隐藏并不影响数据的提交,其真正影响的是 disabled 属性。
HTML5 中新增了 hidden 这个布尔属性,可以让元素天生 display:none 隐藏。例如:
<div hidden>看不见我</div>
IE11 以及其他现代浏览器都支持它,因此,如果要兼容桌面端,需要如下 CSS 设置:
[hidden] {
display: none;
}
display:none 显隐控制并不会影响 CSS3 animation 动画的实现,但是会影响 CSS3 transition 过渡效果执行,因此 transition 往往和 visibility 属性走得比较近。
对于计数器列表元素,如果设置 display:none,则该元素加入计数队列。举个例子,10 个列表从 1 开始递增,假设第二个列表设置了 display:none,则原来的第三个列表计数变成 2,最后总计数是 9。
有一些人简单地认为 display:none 和 visibility:hidden 两个隐藏的区别就在于:display:none 隐藏后的元素不占据任何空间,而 visibility:hidden 隐藏的元素空间依旧保留。实际上并没有这么简单,visibility 是一个非常有故事的属性。
首先,它最有意思的一个特点就是继承性。父元素设置 visibility:hidden,子元素也会看不见,究其原因是继承性,子元素继承了 visibility:hidden,但是,如果子元素设置了 visibility:visible,则子元素又会显示出来。这个和 display 隐藏有着质的区别。
我们看一个例子来切实感受一下,HTML 代码如下:
<ul style="visibility:hidden;">
<li style="visibility:visible;">列表 1</li>
<li>列表 2</li>
<li>列表 3</li>
<li style="visibility:visible;">列表 4</li>
</ul>
列表父元素 visibility:hidden,千万不要想当然地认为此时所有子元素就都不可见了,“列表 1”和“列表 4”依旧 清晰可见。
这种 visibility:visible 后代可见的特性,在实际开发的时候非常有用。
visibility:hidden 不会影响计数器的计数,这和 display:none 完全不一样。举个例子,如下 CSS 和 HTML 代码:
.vh {
visibility: hidden;
}
.dn {
display: none;
}
ol {
border: 1px solid;
margin: 1em 0;
counter-reset: test;
}
li:after {
counter-increment: test;
content: counter(test);
}
<ol>
<li>列表</li>
<li class="dn">列表</li>
<li>列表</li>
<li>列表</li>
</ol>
<ol>
<li>列表</li>
<li class="vh">列表</li>
<li>列表</li>
<li>列表</li>
</ol>
结果如下图所示。

可以看到,visibility:hidden 虽然让其中一个列表不可见了,但是其计数效果依然在运行。相比之下,设置 display:none 的列表就完全没有参与计数运算。
下面的 CSS 是会让.box 元素 hover 时显示.target 子元素,但不会有过渡效果:
.box > .target {
display: none;
position: absolute;
opacity: 0;
transition: opacity .25s;
}
.box:hover > .target {
display: block;
opacity: 1;
}
但是,下面的 CSS 语句却可以让.target 子元素有淡出的过渡效果:
.box > .target {
position: absolute;
opacity: 0;
transition: opacity .25s;
visibility: hidden;
}
.box:hover > .target {
visibility: visible;
opacity: 1;
}
这是为什么呢?因为 CSS3 transition 支持的 CSS 属性中有 visibility,但是并没有 display。
由于 transition 可以延时执行,因此,和 visibility 配合可以使用纯 CSS 实现 hover 延时显示效果,由此提升我们的交互体验。
下图所示是一个很常见的 hover 悬浮显示列表效果,而且有多个触发点相邻,对于这种 hover 交互,如果在显示的时候增加一定的延时,可以避免不经意触碰导致覆盖目标元素的问题。例如,图中2虽然显示的是第一行的下拉列表,但真相即可能是:我本来想去 hover第二行的“操作”文字,但是由于鼠标光标移动路径不小心经过了第一行的“操作”,结果把第二行本来 hover 的“操作”覆盖了,必须重新移出去,避开干扰元素,重新 hover 才行。如此一来,体验就不好了。

但是有了 visibility 属性和 transition 延时,我们就可以把这种不悦的体验消除掉,关键的 HTML 和 CSS 代码如下:
<td>
<a href>操作▾</a>
<div class="list">
<a href>编辑</a>
<a href>删除</a>
</div>
</td>
.list {
position: absolute;
visibility: hidden;
}
td:hover .list {
visibility: visible;
transition: visibility 0s .2s;
}
transition 在 hover 时候声明可以让鼠标光标移出的时候列表无延时地迅速隐藏。
有了上面的 CSS 处理,当我们鼠标光标奔向第二行的“操作”按钮,但不小心经过第一行“操作”按钮时,就不会发生瞬间出现列表而覆盖目标元素的问题了。
visibility隐藏除了和 transition 友好外,与 display:none 相比,其在 JavaScript 侧也更加友好。存在这样的场景:我们需要对隐藏元素进行尺寸和位置的获取,以便对交互布局进行精准定位。此时,建议使用 visibility 隐藏:
.hidden {
position: absolute;
visibility: hidden;
}
原因是,我们可以准确计算出元素的尺寸和位置,如果使用的是 display:none,则无论是尺寸还是位置都会是0,计算就会不准确。例如,假设element是页面上某个display:none 隐藏元素 DOM 对象,则:
console.log('clientWidth: ' + element.clientWidth);
console.log('clientHeight: ' + element.clientHeight);
console.log('clientLeft: ' + element.clientLeft);
console.log('clientTop: ' + element.clientTop);
console.dir(element.getBoundingClientRect());
结果会显示全部都是 0。
最后,有必要强调一下:
title 属性是不会被朗读的,除非辅以按钮等控件元素,这里是因为设置了 role="button"所以才可以朗读。visibility:hidden 元素是不会被朗读的。