我们在之前的章节当中介绍了 CSS 当中的 float 属性,今天我们接着上回继续来看 CSS 当中的 display 属性
display 是设计 CSS 版面配置中最重要的属性,每个 HTML 元素都有一个预设的 display 值,不同的元素属性会有不同的预设值,大部分元素的 display 属性,预设值通常是 block 或 inline 其中一个,若该元素的 display 属性被标示为 block 就被称为『区块元素』,若被标示为 inline 就称为『行内元素』,但是我们在这里并不会介绍它的全部属性,而是只会介绍一些比较常用的,比如 block、inline-block、inline 和 none 等,我们就先从 none 开始看起吧
none && visibility
我们都知道,当元素的 display 属性被设置成 none 以后,界面上将不会显示该元素,并且该元素不占布局空间,但是我们仍然可以通过 JavaScript 来操作该元素,至于为什么会这样,其实是涉及到浏览器的渲染原理,如下
浏览器会解析
HTML标签生成DOM Tree,解析CSS生成CSSOM,然后将DOM Tree和CSSOM合成生成Render Tree,元素在Render Tree中对应0或多个盒子,然后浏览器以盒子模型的信息布局和渲染界面,更多内容可见 浏览器的渲染机制
而设置为 display: none 的元素则在 Render Tree 中没有生成对应的盒子模型,也就是说节点不会被加入 Render Tree 当中,因此后续的布局、渲染工作自然与它无关,但是 DOM 操作还是可以的,但是这里有个需要注意的地方,那就是我们下面将要介绍的 visibility: hidden 则会被加入 Render Tree 当中,所以如果某个节点最开始是不显示的,设为 display: none 是更优的,我们下面再来看一些与其相关的其他特性
原生默认为 None 的元素
其实浏览器原生元素中有不少自带 display: none 的元素,如 link,script,style,dialog,input[type=hidden] 等
hidden 属性
在 HTML5 中新增了 hidden 布尔属性,它可以让开发者自定义元素的隐藏,如下
1 | /* 兼容原生不支持 hidden 属性的浏览器 */ |
1 | <span hidden>Hide and Seek: You can't see me!</span> |
后代元素不可见
当父元素为设置为 display: none 后,它的后代元素都是不可见的,如下示例
1 | *** START *** |
运行后可以发现,页面当中只会显示 *** START *** *** END *** 内容
无法获取焦点
设置为 display: none 后,是无法获取焦点的,比如如下示例,可以使用 TAB 键来进行测试
1 | <input type="hidden"> |
无法响应事件
设置为 display: none 后,是无法响应任何事件的,无论是捕获、命中目标和冒泡阶段均不可以,这是由于 display: none 的元素根本不会在界面上渲染,就是连一个像素的都不占,因此自然无法通过鼠标点击命中,而元素也无法获取焦点,那么也不能成为键盘事件的命中目标,当父元素的 display 为 none 时,子元素的 display 必定为 none,因此元素也没有机会位于事件捕获或冒泡阶段的路径上,因此 display: none 的元素无法响应事件
不影响表单提交
虽然我们无法看到 display: none 的元素,但当表单提交时依然会将隐藏的 input 元素的值提交上去
1 | <form> |
display 变化时将触发 reflow
如果撇开 display: none 的话,我们都知道 block 表示元素位于 BFC 中,而 inline 则表示元素位于 IFC 中,也就是说 display 就是用于设置元素所属的布局上下文,若修改 display 值则表示元素采用的布局方式已发生变化,因此 display 变化时将触发 reflow
下面我们再来看一个经常会与 display: none 放在一起说的 visibility: hidden,虽然他们都可以隐藏元素,但是它与 display: none 最为主要的区别就是 visibility: hidden 可以隐藏某个元素,但隐藏的元素仍需占用与未隐藏之前一样的空间,也就是说,该元素虽然被隐藏了但仍然会影响布局,它与 display: none 表现一致的行为有以下这些
- 和
display: none一样无法获得焦点 - 和
display: none一样不妨碍form表单的提交
下面我们主要来看看两者之间的一些差异点
后代元素可以选择可见
与 display: none 不同的是,如果父元素为 visibility: hidden,而子元素设置为 visibility: visible 的话,子元素是会显示的,如下
1 | <div style="visibility: hidden"> |
可以发现运行以后,虽然 parent 是使用 visibility: hidden 隐藏了,但是如果子元素设置为 visibility: visible 仍然是可以显示的
可以在冒泡阶段响应事件
由于设置为 visibility: hidden 的元素其子元素可以设置为 visibility: visible,因此隐藏的元素有可能位于事件冒泡的路径上,所以在下面代码中将鼠标移至属性为 .visible 的元素时,.hidden 会的元素响应 hover 事件显示
1 | div{ |
1 | <div class="hidden"> |
visibility 变化不会触发 reflow
这个我们在上面也提到过,因为 visibility: hidden 会被加入 Render Tree,而又当 visible 设置为 hidden 时,不会改变元素布局相关的属性,因此不会触发 reflow,只是静静地和其他渲染变化一起等待浏览器定时重绘界面
inline && block && inline-block
看完了 none,下面我们再来看看 inline,block,inline-block 这三个比较类似的属性,我们先从一个问题开始看起,那就是行内元素和块级元素有什么异同呢?
可能你觉得这是一个老掉牙的问题,但其实并没有那么简单,对于初学者来说,脑海里的第一印象是 CSS 的 display 属性,值为 inline 时为行内元素,值为 block 时为块级元素,那值为 inline-block 时呢,元素是块级行内元素吗?
其实我们看问题的角度从一开始就错了,错误地站在了 CSS 的角度,而真正正确的是站在 HTML 的角度,因为这里的元素本质上是指的是 HTML 的 DOM 元素,所以下面我们就来深入的了解一下 inline 元素
inline 元素
在 HTML 中,行内元素是那些仅仅占据定义元素边界的标签空间的元素,而不会去破坏内容流,一个行内元素不会在新的一行开始,而且仅仅占据必要的元素宽度空间,不会多占空间,比如下面这个例子
1 | <p>The following span is an <span>inline element</span></p> |
我们一眼就可以看出,由于 span 是行内元素,所以上面这段话是会显示在一行当中的,但是如果我们此时将 span 的 display 属性调整成 block 以后,就会发现它将会另起一行来进行展示,那么为什么会这样显示呢?
简单来说就是,因为我们手动的指定了 span 的 display 属性为 block,就相当于告诉浏览器我们需要将一个 inline 元素,渲染成 block box 而不是 inline box,所以这里就涉及到一个新的概念 block-level,下面我们就来看看这个 block-level
block-level
简而言之,在概念上 inline 和 block-level 元素有以下区别
Content Model,行内元素仅仅包含数据和其他的行内元素,比如不能在inline elements的内部去添加block elementsFormatting,默认情况下行内元素不会在文档流的开始位置强制生成一个新行,而如果是block元素的话,正好与之相反,会很显著地另起一行来进行展示(但是可以使通过CSS来进行改变)
HTML 元素通常要么是 block-level 元素,要么是 inline elements,一个 block-level 元素,会占据它的父元素,也就是容器的全部空间,从而新建了一个 block,通常从一个新的行开始,占据可用空间的全部宽度(从左到右尽可能的拉伸),但是至于为什么会这样渲染,我们就需要更为深入的来了解一下 Level 3 规范中定义的 display 属性
display 属性
display 属性指明了元素渲染盒子的类型,在 HTML 中,默认 display 属性值取自 HTML 规范或浏览器/用户默认样式表中描述的行为,关于 display 值有以下几种分类,这里我们主要看前面三点
<display-outside>,指明了元素外部的展现类型,这基本是它在流式布局中的作用,例如inline,blockinline,这个元素生成一个或者多个行内盒子block,这个元素生成一个块元素盒子
<display-inside>,指明了元素内部的展现类型,定义了布局formatting context的类型(假设它是一个不可替换的元素),例如table,flex,gridtable,元素就像HTML当中的<table>标签一样表现,它定义了一个block-level盒子flex,元素表现形式是块级元素,根据flexbox model绘制自己的内容grid,元素表现得像块级元素,根据grid model绘制自己的内容
<display-legacy>,CSS2使用单关键字的语法为display属性设置值,在同一个layout模型中,为块级元素和行内元素引用分离的关键词,例如inline-block,inline-table等等inline-block,元素生成一个块元素盒子,它会被周围的内容包裹,就仿佛它是一个inline box一样,等价于inline flow-rootinline-table,它表现地就像HTML标签一样,但是是一个inline box,而不是block-level盒子,在table盒子内部是块级内容,等价于inline tableinline-flex,行内元素表现,布局内容时根据flexbox模型布局,等价于inline flexinline-grid,行内元素展现,布局内容时根据grid模型
<display-listitem>,元素生成了用来存放内容的块盒子以及一个分离的list-item行内盒子,例如list-item<display-internal>,一些布局模型,例如table和ruby,它只在特定的layout mode下才会生效,例如table-row-group,table-header-group等等<display-box>,是否展示box,例如none,contents(实验中)
通过上面的列表,我们可以大致发现 CSS 属性 display 是如何影响元素的了,它大多数情况是通过修改 <display-outside>,<display-inside> 这两个属性值去改变元素外部的流式布局和元素内部的 formatting context,其中 formatting context 会根据 model 类型进行渲染,也因此我们上面的问题也就迎刃而解了,那就是 display 值从 inline 更改为 block 的原理
本质上是改变了 <display-outside>,改变了元素的流式布局,也就是从生成一个 inline 盒子,变为生成一个 block 盒子
特性
最后我们再来看看它们三者之间的特性差异,我们先来看看 block,它比较简单,它可以设置元素为块级元素,可应用盒子模型相关属性,默认 width 为 100%,height 自适应,margin、padding 都有效,如果没有占宽或高的子元素存在,则高度为零,这个也是平常很常见的情况
inline-block 元素是 inline + block 的合体,它的 margin、padding、width 和 height 都有效,但是 block/inline-block 元素包裹的 inline-block 元素,默认超过 width 会换行,而 hieght 则会撑开,但是可以通过 white-space: nowrap 强制不换行,但是就不能实现文本省略显示了,另外它也存在着和我们下面将要介绍的 inline 一样的问题,那就是存在 8 像素的间隔问题
关于 inline,行内元素或者通过 display: inline 修饰为行内元素的都具有行内元素的行为,多个 inline 元素会排成一行,但是它最为出名的问题那就是并列的多个 inline 元素之间会存在 8 个像素左右的间隔,比如下面这个例子
1 | <a>1</a> |
运行以后可以发现,在页面当中显示的时候,它们三者之前并不是仅仅靠在一起的,而是之间互有距离,那么怎么解决这个问题呢?解决办法有很多种,我们一个一个来看,第一种也是最为简便的方式,就是将它们三者放在一行当中,如下
1 | <a>1</a><a>2</a><a>3</a> |
第二种方式就是利用 margin-left: -8px,也就是 marginq 负值实现,也可以在外层使用 letter-spaceing 和 word-spaceing 为负值的方式实现(此种试子元素需要重置被设置属性)
1 | <div class="inline">inline01</div> |
1 | .inline{ |
第三种方式就是在包裹 inline 元素的外层元素加上 font-size: 0px 和 -webkit-text-size-adjust: none 来进行实现
1 | <div class="overWidth"> |
1 | a{ |
此外,inline 元素的 width 和 height 都是无效的,但是 padding 有效,并且 margin 只有左右有效,上下无效,而且 inline 元素包裹 inline 元素,外层元素的 width 和 height 会被内部的撑开,可是使用上面 overWidth 的示例来进行测试
但是如果是被 block/inline-block 元素包裹的 inline 元素,默认 width 超出会自动换行,而 height 则会撑开,如果想要实现强制不换行可以通过 white-space: nowrap 来实现,但是此时超出 width 的部分会有溢出,可以通过 overflow: hidden 和 text-overflow: ellipsis 配合实现省略显示