持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情
CSS 元素的显示与隐藏
使用 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
。
- 如果希望元素不可见,同时不占据空间,辅助设备无法访问,但资源有加载,DOM 可访问,则可以直接使用
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);
}
- 如果单纯希望元素看不见,但位置保留,依然可以点可以选,则直接让透明度为 0。例如:
.opacity {
opacity: 0;
filter: Alpha(opacity=0);
}
大家可以根据实际的隐藏场景选择合适的隐藏方法。不过,实际开发场景千变万化,上面罗列的实践不足以覆盖全部情形。
下面我们来介绍两个最为重要是属性:display
1. 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。
2. visibility 与元素的显隐
有一些人简单地认为 display:none
和 visibility:hidden
两个隐藏的区别就在于:display:none
隐藏后的元素不占据任何空间,而 visibility:hidden
隐藏的元素空间依旧保留。实际上并没有这么简单,visibility
是一个非常有故事的属性。
- 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 与 CSS 计数器
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
的列表就完全没有参与计数运算。
- **visibility **与 **transition **
下面的 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
元素是不会被朗读的。