CSS 元素的显示与隐藏

502 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

CSS 元素的显示与隐藏

使用 CSS 让元素不可见的方法很多,剪裁、定位到屏幕外、明度变化等都是可以的。虽然它们都是肉眼不可见,但背后却在多个维度上都有差别。

下面是我总结的一些比较好的隐藏实践。

  1. 如果希望元素不可见,同时不占据空间,辅助设备无法访问,同时不渲染,可以使用<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

  1. 如果希望元素不可见,同时不占据空间,辅助设备无法访问,但资源有加载,DOM 可访问,则可以直接使用display:none隐藏。例如:
.hidden {
 display: none; 
}
  1. 如果希望元素不可见,同时不占据空间,辅助设备无法访问,但显隐的时候可以有transition淡入淡出效果,则可以使用:
.hidden {
 position: absolute; 
 visibility: hidden; 
}
  1. 如果希望元素不可见,不能点击,辅助设备无法访问,但占据空间保留,则可以使用visibility:hidden隐藏。例如:
.hidden {
 visibility: hidden; 
}
  1. 如果希望元素不可见,不能点击,不占据空间,但键盘可访问,则可以使用 clip 剪裁隐藏。例如:
.clip {
 position: absolute; 
 clip: rect(0 0 0 0); 
} 
.out { 
 position: relative; 
 left: -999em; 
}
  1. 如果希望元素不可见,不能点击,但占据空间,且键盘可访问,则可以试试 relative 隐藏。例如,如果条件允许,也就是和层叠上下文之间存在设置了背景色的父元素, 则也可以使用更友好的 z-index 负值隐藏。例如:
.lower {
  position: relative; 
  z-index: -1;
}
  1. 如果希望元素不可见,但可以点击,而且不占据空间,则可以使用透明度。例如:
.opacity {
 position: absolute; 
 opacity: 0; 
 filter: Alpha(opacity=0); 
}
  1. 如果单纯希望元素看不见,但位置保留,依然可以点可以选,则直接让透明度为 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 浏览器下的网络请求如下图所示。 1655790822495.png

我们发现只加载了 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:nonevisibility:hidden 两个隐藏的区别就在于:display:none 隐藏后的元素不占据任何空间,而 visibility:hidden 隐藏的元素空间依旧保留。实际上并没有这么简单,visibility 是一个非常有故事的属性。

  1. 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 后代可见的特性,在实际开发的时候非常有用。

  1. 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>

结果如下图所示。 image.png 可以看到,visibility:hidden 虽然让其中一个列表不可见了,但是其计数效果依然在运行。相比之下,设置 display:none 的列表就完全没有参与计数运算。

  1. **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 才行。如此一来,体验就不好了。 image.png 但是有了 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; 
}

transitionhover 时候声明可以让鼠标光标移出的时候列表无延时地迅速隐藏。

有了上面的 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。

最后,有必要强调一下:

  1. 普通元素的 title 属性是不会被朗读的,除非辅以按钮等控件元素,这里是因为设置了 role="button"所以才可以朗读。
  2. visibility:hidden 元素是不会被朗读的。