TanXinNiao / blog

贪心鸟的博客
0 stars 0 forks source link

CSS权威指南(第4版)第七章 视觉格式化基础 #17

Open TanXinNiao opened 3 years ago

TanXinNiao commented 3 years ago

本章全面阐述CSS视觉渲染的理论。为什么要了解这些知识呢?因为CSS是如此开放、 如此强大的一个系统,任何书都不可能涵盖属性的所有组合方式及其效果。在实践中, 你肯定会发现使用CSS的新方式。而且在探索CSS的过程中,你可能会在某些用户代 理中遇到看似奇怪的行为。掌握视觉渲染模型的工作原理之后,你便能判断所遇到的怪 异行为(尽管出乎意料)就是CSS渲染引擎定义的正确方式,还是确有异常。

TanXinNiao commented 3 years ago

元素框基础

不管是什么元素,CSS都假定每个元素都生成一个或多个矩形框,我们称之为元素 框(element box)(以后的规范可能会允许元素生成非矩形框,而其实现在就有这方 面的提议,不过目前生成的框还都是矩形的)。各元素框的中心是内容区域(content area),四周有可选的内边距、边框、轮廓和外边距。之所以说这些区域是可选的,是 因为它们的宽度都可以设为零,即把它们从元素框上删除。

重要概念概览

下面简要说明一下我们将讨论的不同框体,以及后文将用到的重要术语: 常规流动 即渲染西方语言时从左至右、从上到下的顺序,以及传统的HTML文档采用的文本 布局方式。注意,对非西方语言来说,流动方向可能会变。多数元素都采用常规的 流动方式,除非元素浮动了、定位了,放入弹性盒或采用栅格布局了。不过本章只 讨论常规流动方式下的元素。 非置换元素 内容包含在文档中的元素。例如,段落(p)是非置换元素,因为段落中的文本内容 在元素自身中。 置换元素 为其他内容占位的元素。典型的置换元素是img,它指向一个图像文件,那个图像 就插在img元素在文档流中的位置上。多数表单元素也是置换元素(例如<input type="radio">)。 根元素 位于文档树顶端的元素。在HTML文档中,根元素是htmlo在XML文档中,根元 素可以是语言允许的任何元素,例如,RSS文件的根元素是rss。 块级框 段落、标题或div等元素生成的框。在常规流动模式下,块级框在框体前后都“换行”, 因此块级框是纵向堆叠的。display: block声明能把任何元素生成的框体变成块 级框。 行内框 strong或span等元素生成的框体。行内框前后不“换行”。display: inline声明 能把任何元素生成的框体变成行内框。 行内块级框 内部特征像块级框,外部特征像行内框。行内块级框的行为与置换元素相似,但不 完全相同。比如说把一个div元素像行内图像那样插入一行文本,这样一想你就明 白了。

TanXinNiao commented 3 years ago

调整元素的显示方式

负外边距

div (width: 500px; border: 3px solid black;}
p.wide {margin-left: l0px; width: auto; margin-right: -50px; }

你没看错,子元素比父元素宽了!用算式计算,结果是对的: 10px + 0 + 0 + 540px + 0 + 0 -50px = 500px

其中,540px对应于width: auto,即满足算式左右两边所需的值。尽管子元素超出了 父元素,但是这里并没有违背规范,因为七个属性的值加在一起等于总宽度。这是语义 上的伎俩,但却是正确的行为。 下面,加上边框:

div {width: 500px; border: 3px solid black;}
p.wide {margin-left:10px; width: auto; margin-right: -50px;
border: 3px solid gray;}

此时,变化发生在width的计算结果上: 10px + 3px + 0 + 534px + 0 + 3px - 50px = 500pX

反过来,设为auto的右外边距也可能得到负值。比如其他属性设为特定的值时,为了 满足元素不能比容纳块宽的要求,右外边距就有可能为负值。以下述样式为例:

div {width: 500px; border: 3px solid black;}
p.wide {margin-left: lOpx; width: 6OOpx; margin-right: auto; border: 3px solid gray;}

此时,算式要这样列: 10px + 3px + 0 + 600px + 0 + 3px - 116px = 500px

右外边距的计算结果为-116px。即便明确声明为其他具体的值,右外边距也会被强制设 为-116px,因为规则就是这样制定的:倘若元素的尺寸出现过约束,右外边距要被重置 为满足算式所需的任何值(如果是从右至左书写的语言,重置的将是左外边距)。

TanXinNiao commented 3 years ago

百分数

宽度、内边距和外边距设为百分数时,基本的规则依然适用。其实,这些属性的值设为 长度值还是百分数并没什么关系。

然而,百分数和长度单位混在一起使用就不那么容易理清了。以下述标记为例:

<p style="width: 67%; padding-right: 2em; padding-left: 2em; margin-right: auto; margin-left: 5em;">mixed lengths</p>

5em + 0 + 2 em + 67% + 2 em + 0 + auto =容纳块的宽度 为了确保右外边距的计算结果为零,元素的容纳块必须是27.272727 em宽(元素的内容 区是18.272727 em宽)。假使容纳块比这宽,右外边距的计算结果会变成正值;容纳块 比这窄的话,右外边距的计算结果则为负值。

如果混用不同的长度单位,情况更复杂。为了避免情况一直复杂下去,边框不接受百分数,只能设为长度值。这样限制的基本原 因是,只使用百分数其实无法创建完全弹性的元素,除非不添加边框,或者使用某种实 验性的方案,例如弹性盒布局。

TanXinNiao commented 3 years ago

自动调整高度

在常规流动模式下,声明height: auto的块级框是最简单的,此时,框体的高度恰好 能放得下里面的内容。常规流动模式下的块级框如果高度是自动调整的,而且子代都是 块级元素,那么默认的高度是从最上边那个块级子代元素的上边框外侧到最下边那个块 级子代元素的下边框外侧之间的距离。因此,子元素的外边距“游离”在所属元素的外 部(这个行为在下一节说明)。

然而,如果块级元素有上内边距或下内边距,或者有上边框或下边框,那么其高度是从 最上边那个子元素的上外边距的外边界到最下边那个子元素的下外边距的外边界之间的 距离。

<div style="height: auto;
  background: silver;*'>
  <p style="margin-top: 2em; margin-bottom: 2em;">A paragraph!</p>
</div>
  <div style="height: auto; border-top: ipx solid; border-bottom: Ipx solid; background: silver;">
  <p style=,,margin-top: 2em; margin-bottom: 2em;">Another paragraph!</p> 
</div>

如果把前例中的边框改为内边距,div元素的高度不变,外边距依然算在其中。

TanXinNiao commented 3 years ago

折叠纵向外边距

纵向格式化的另一个重要特征是,相邻的纵向外边距会折叠。只有外边距有这种折叠行 为。内边距和边框(如果有的话),绝不与任何区域折叠。

多个外边距同时出现时也会折叠,例如在列表的末尾。假设把下述规则应用到前例上:

ul {margin-bottom: 15px;}
li (margin-top: 10px; margin-bottom: 20px;}
hl (margin-top: 28px;}

列表中最后一个项目的下外边距为20像素,ul元素的下外边距为15像素,而后面的 hl元素的上外边距为28像素。因此,折叠外边距之后,最后一个li元素的底部与hl 元素的顶部之间的距离为28像素

你可能还记得前一节的示例,为容纳块添加边框或内边距之后,子元素的外边距便包含 其中。现在我们在前述规则的基础上为U1元素添加边框,看一下效果:

ul (margin-bottom: 15px; border: 1px solid;}
li {margin-top: 10px; margin-bottom: 20px;}
hl (margin-top: 28px;}

这样修改之后,li元素的下外边距在父元素(ul)的范围内。此时,只有U1和hl之间 的外边距会出现折叠

TanXinNiao commented 3 years ago

负外边距和折叠

负外边距对纵向格式化有一定影响,比如会影响外边距的折叠。如果两个相邻的外边距 都是负值,浏览器取其中绝对值较大的那个,然后从正外边距中减去它的绝对值。也就 是说,把负值与正值相加,得到的结果为两个元素之间的距离。

下面再看一个例子,这里列表项目、无序列表和段落的外边距都会折叠。无序列表和段 落的外边距均为负值,如下所示:

li (margin-bottom: 20px;}
ul (margin-bottom: -15px;}
h1{margin-top: -18px;}

两个负外边距中绝对值较大的那个(-18px)与最大的正外边距(20px)相加,得到 20px-18px = 2pxo因此,列表项目内容区的底边到h1元素内容区的顶边之间只有2 像素

如果负外边距导致元素重叠,很难分清哪个元素在上边。你可能注意到了,本节所举的 例子都没有使用背景色。如果这么做了,后续元素的背景色可能会遮盖前面元素的内容。 这是可以预见的,因为浏览器是从头到尾按顺序渲染元素的,在常规流动模式下,如果 文档中位于后面的元素与前面的元素重叠了,理应遮盖元素的内容。

TanXinNiao commented 3 years ago

列表项目

列表项目有些独特的规则。列表项目前通常有个记号,例如小圆点或数字,但这个记号 其实不在列表项目的内容区中

记号可以放在列表项目内容区的外部,也可以作为行间内容,放在内容区的开头,这取 决于list-style-position属性的值。如果把记号放在内部,列表项目与周围元素之间 的关系就跟块级元素一模一样

TanXinNiao commented 3 years ago

行内元素

各行的边框恰巧与上下边重合。这种情况仅当行内文本没有内边距才会发 生。注意,边框稍微有点重合。例如,第一行的下边框正好位于第二行上边框的下方。 这是因为边框在各行外部的下一像素(假设你用的是显示器)处绘制。既然行与行之间 是相互接触的,那么边框肯定会重叠

基本术语和概念

匿名文本 不在任何行内元素中的字符串。例如,在

I'm so happy!

标记中, ttrmw和“happy!”是匿名文本。注意,空格也是匿名文本的一部分,因为空格也 是一种字符。 字体框 由字体具体定义,也叫字符框。字形可能比字体框高或矮。CSS中的font-size属 性控制字体框的高度。 内容区 对非置换元素而言,内容区有两种定义,CSS规范允许用户代理选择其中一个实现。 内容区可以是各字符的字体框连在一起构成的方框,也可以是元素中各字符的字形 合在一起构成的方框。简单起见,本书采用前者。对置换元素来说,内容区是元素 自身的高度加上内外边距和边框。 行距 行距是font-size和line-height之差。二者之间的差值除以2之后分别添加到内 容区的上部和下部。内容区多出来的这部分空间叫半行距(显然的)。只有非置换 元素有行距。 行内框 内容区加行距后得到的方框。对非置换元素来说,行内框的高度正好等于line-height 属性的值。对置换元素而言,行内框的高度等于内容区的高度,因为置换元素没有 行距。 行框 过一行中各行内框最高点和最低点的方框。也就是说,行框的顶边过最高那个行内 框的顶边,行框的底边过最低那个行内框的底边。

此外,CSS还定义了一些行为和有用的概念: •内容区相当于块级框的内容框° •行内元素的背景填充在内容区加内边距所在的区域里。 •行内元素的边框在内容区外的内边距外侧。 •非置换行内元素的内边距、边框和外边距在对应的方框上没有纵向效果,即对行内 框(及元素所在的行框)的高度没有影响。 •然而,置换元素的外边距和边框对行内框的高度有影响,进而对元素所在的行框的 高度也有影响。

在继续之前,先来看一下构建行框的具体步骤。这一过程能让你了解一行中的各部分是 如何决定行高的。 一行中各元素的行内框高度是这样确定的:

  1. 确定行内各非置换元素和匿名文本的font-size和line-height值,后者减去前者, 得到行距。行距除以2,分别添加到字体框的上部和下部。
  2. 确定各置换元素的 height, margin-top, margin-bottom, padding-top. padding¬bottom. border-top-width 和 border-bottom-width 值,各值相加。
  3. 确定各内容区在一行的基线上方和下方分别超出多少。这不是件简单的事,你要知 道各元素和匿名文本的基线在何处,以及一行的基线在何处,然后把它们对齐。另外, 要把置换元素的底边与一行的基线对齐。
  4. 确定设定了 vertical-align属性的元素纵向偏移有多少。这是为了查明元素的行内 框向上或向下移动了多少,因为纵向对齐改变了元素与基线之间的距离。
  5. 知道所有行内框的位置之后,计算行框的高度:基线与最高那个行内框顶边之间的 距离加上基线与最低那个行内框底边之间的距离。
TanXinNiao commented 3 years ago

纵向对齐

改变行内框的纵向对齐方式后,仍以相同的过程确定高度。假设我们把strong元素的 纵向对齐设为4px:

<p style=nfont-size: 12px; line-height: 12px;n>
This is text, <em>some of which is emphasized</em>, plus other text<br> which is <strong style="font-size: 24px; vertical-align: 4px;'*>strongly emphasized</strong> and that is<br> larger than the surrounding text.
</p>

这个小小的改动把strong元素向上抬升了 4像素,而且是内容区与行内框一起移动。 现在,strong元素所在的行内框的顶边是一行中最高的,因此此次修改纵向对齐还把行 框的顶边向上移了 4像素

控制行高 读过前面几小节我们知道,修改行内元素的line-height值可能会导致一行中的文本 与另一行重叠。但是,之前都是修改单个元素的行高。那么有没有一般性的方法,能让 line-height的值不导致行之间有重叠呢? 一种方法是为字号有变化的元素设定单位为em的行高。例如:

p (line-height: lem;}
big (font-size: 250%; line-height: lem;)
<P>
Not only does this paragraph have "normal" text, but it also<br> contains a line in which <big>some big text</big> is found.<br> This large text helps illustrate our point.
</p>

这里为big元素设定的line-height值增加了行框的整体高度,为显示big元素提供 了足够的空间,不会再出现文本重叠,而且也没有改变段落中其他几行的行高。我们把 big元素的line-height值设为lem,因此big元素的行高将与字号相等。还记得吗, line-height相对元素自身的font-size而言,跟父元素没关系。

TanXinNiao commented 3 years ago

改变断行行为

把一个行内非置换元素分成多行显示时,用户代理将其视为断成多块的 一长行,每换一行就多一块。这其实是默认行为,可以通过box-decoration-break属性 改变。 取值 slice | clone 默认值slice, 值clone把元素各片段视作单独的框。

box-decoration-break属性最常用于行内框,不过只要存在换行的情况都适用,例如在 分页媒体中由于换页而打断的元素。此时,每个片段都是独立的一块。如果设定box-decoration-break: clone,那么每个片段就是一个副本,单独应用边框、内边距、背景 等。多栏布局一样,如果因为换栏而把元素打断了,box-decoration-break的值将影响 渲染方式。 这里我不懂啥意思(挖坑)

TanXinNiao commented 3 years ago

流动显示方式

flow和flow-root需要单独说明。声明为display: flow的元素常规情况下使用块级布局, 如果再加上inline,则生成行内框。 对下述规则来说,前两个得到的是块级框,而第三个得到的是行内框。

#first {display: flow;)
#second (display: block flow;}
#third {display: inline flow;}

出现这种形式的原因是,CSS正在向一种新布局系统发展,在这个系统中有两种显示类 型:外部显示类型和内部显示类型。block和inline等关键字表示外部显示类型,指明 显示框与周围元素的关系。flow关键字表示内部显示类型,指明元素内部的布局情况。

利用这种形式可以声明display: inline table这样的规则,指明元素内部按照表格布 局,而处理与周围内容的关系时则作为行内元素(以前的值具有同等效 果)。

旧值 | 新值                            block | block flow inline | inline flow inline-block | inline flow-root list-item | list-item block flow inline-list-item | list-item inline flow table | block table inline-table | inline table flex | block flex inline-flex | inline flex grid | block grid inline-grid | inline grid 截至2017年年末,只有Firefox和Chrome支持flow和flow-root,其他浏览器都 不支持

TanXinNiao commented 3 years ago

contents 显示方式

display还新增了一个稍带魔法的值,contentSo把display: contents应用到元素上 之后,那个元素不再参与页面的格式化,相当于把它的子元素提升到当前的层级。以下 述简单的CSS和HTML为例:

ul (border: ipx solid red;} li (border: ipx solid silver;} 
<ul>
<li>The first list
<li>List Item II: The Listening.</li>
 <li>List item the third</li>
</ul>

得到的无序列表将有一个红色边框,而且三个列表元素都有银色边框。 如果把display: contents应用到ul元素上,用户代理会按照文档中没有<ul>和 </ul>那两行进行渲染。 现在,列表元素还是列表元素,而且表现也像列表元素,但是ul没有了,就像从未出 现过一样。注意,不仅边框不见了,通常出现在列表内容四周的上下外边距也没了。

截至 2017 年年末,只有 Firefox 支持 display: contents0 Chrome/Blink 系列浏览 器目前正在实现。