Web开发面试的那些题-CSS篇

Web 开发者面试题集第四篇,关于 CSS。

写在前面

秋招提前批已经基本结束了,即将进入金九银十,正式的号角已经打响。春招,以及秋招提前批一路过来,断断续续也面了一些公司,自己在笔记上也有总结,甚至自己进行过一些猜题。发现基本问到的问题八九不离十,但是有些知识,特别是偏工程的知识点,如果没遇到过,很难产生深刻的印象。结合自己之前的笔记,也想在正式进入 9 月之前,整理一个面试题集系列,加深理解。

这是这个系列的第四篇,关于 CSS。

这个话题感觉被问烂了,网上已经有很多方法,我这里只给出常见且好记忆的几种方法

1
2
3
4
5
6
div {
  background-color: red;
  height: 100px;
  line-height: 100px;
  text-align: center;
}

理解单行文字居中的原理,我们首先需要理解line-height,这里要避免一个误区

包括在我看过的HTML and CSS, Design and Build Website这本书,以及一些文档中,都还将 CSS 的line-height认为是两行文字基线之间的距离,在一些软件例如 Photoshop, Word 中确实是这样,但是 CSS 的line-height是这样的(no表示基线之间距离,yes才是 CSS 的line-height距离)

/master-the-fed-interviews-css/line-height.png
In CSS, the line-height is not the distance between baselines

这样的话,设置了line-height等于容器height才能使单行文字居中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
div {
  background-color: red;
  height: 200px;
  width: 400px;
  position: absolute;
  right: 0;
  bottom: 0;
  top: 0;
  left: 0;
  margin: auto;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
div {
  background-color: red;
  height: 200px;
  width: 400px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -100px;
  margin-left: -200px;
}

和上一个类似,只不过使用transform属性进行位移

1
2
3
4
5
6
7
div {
  background-color: red;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

flex-direction 默认值为row,此时justify-content: center用于水平居中元素,align-items: center用于垂直居中元素,如果设置flex-direction: column,则上面两个属性作用相反

1
2
3
4
5
6
div {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}

**扩展:**需要注意flex-box还有一个属性叫做align-content,在默认情况下flex-direction: row,如果只有一行元素,则align-content不起作用,多行元素时,align-content可以调整行与行之间的距离,比如

/master-the-fed-interviews-css/align-items-and-align-content.png
using align-items: center and align-content: space-around

  1. https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align#line-height-problems-and-beyond

/master-the-fed-interviews-css/IE-box-model-and-W3C-box-model.png

两种盒模型的主要区别在 CSS 属性widthheight的计算方式上,拿width来举例

IE Box Model: width = border + padding + content

W3C Box Model: width = content

默认使用的是 W3C 盒模型计算方式,如果我们要使用怪异盒模型,我们需要使用box-sizing这个属性,这个属性有两个值content-boxborder-box,前一个值为默认值(W3C 盒模型),后一个值表示使用怪异模型

总共四大类

  1. Positioned Elements, 除了static
  2. Relatively Positioned Elements, relative,相对于自身本来的位置
  3. Absolute Positioned Elements, absolutefixed,前者相对于第一个positioned父元素,后者相对于视窗
  4. Stickily Positioned Elements, sticky

这里主要说一下sticky属性,假设一个容器中某个元素使用了position: sticky,且容器高度超过了视窗高度(也就是可以滚动),假设对该元素设置top: 10px,随着容器的滚动,该元素不会移出视窗,在移动到距离视窗 10px 的位置会固定下来(类似fixed),直到该容器全部移出视窗,此时该元素才会随着容器一起移出

首先要清楚,浮动元素会对其后面的元素布局产生影响,所以,清除浮动指的不是清除已经浮动的元素的布局,而是清除前面浮动元素对自己产生的影响,有下面几种情况

浮动元素不占据文档流,因此后面的元素会当它不存在,但是有以下几点需要注意:

1. 如果其兄弟元素是块级元素,会当它不存在而覆盖它

2. 如果是行内元素,则是会围绕它

3. 如果兄弟是块级元素但是其内容是行内元素,那么这些行内元素还是会围绕浮动元素

如果只是简单清除前面浮动元素对自己的影响,使用clear属性就能解决,clear: left表示将之前左浮动的元素视为正常元素,clear属性还有bothright

浮动元素还会对父元素产生影响,如果一个父元素中的子元素全部浮动,这个父元素高度会坍塌,也就是高度为 0 因为父元素如果没有设置高度,其高度是由它最后一个子元素的文档流位置决定的

如果需要使得父元素被撑开,可以使用如下几个方法

  1. 让父元素的最后一个子元素不再浮动,并且使用它来清除浮动

  2. 基于上面一个方法,我们也可以使用伪元素达到这个目的,这叫做 clearfix

1
2
3
4
5
#container::after {
  content: "";
  display: block;
  clear: both;
}
  1. 最后一个方法让父元素使用overflow: auto或者overflow: hidden创建一个 BFC(Block Formatting Context),BFC 可以包含浮动元素,让区域有高度

BFC,也是被问到较多的一个问题,这里说说它的原理和一些使用场景

BFC(Block Formatting Context, 块级格式上下文),可以理解为是元素创建的一块区域,这块区域可以包含它里面的所有元素(包括浮动元素)

BFC 创建方式有很多,可以直接查看 MDN 的解释

这里说几种常见的方式,这几种方式也可以用来解决如下场景问题

  1. 块级元素设置overflow为除默认值visible之外的任一值
  2. 绝对定位元素(absolutefixed)
  3. 浮动元素
  4. flex 布局元素(display: flex下的子元素)
  5. 显式生成 BFC,使display: flow-root,这种方法还处于草案阶段,需要考虑兼容问题

BFC 最初被我知道是用来解决浮动元素无法撑开父元素的问题

1
2
3
#container {
  overflow: auto; /* 或者 overflow: hidden */
}

它还可以解决一个重要的问题,就是Margin Collapsing(边距折叠)

边距折叠有以下几种情况

  1. 假设两个div,上面的div定义了margin-bottom,下面的div定义了margin-top,它们之间的margin由两者的较大值决定
1
2
3
4
<div id="container">
  <h1>The bottom margin of this heading is collapsed...</h1>
  <p>...with the top margin of this paragraph.</p>
</div>
1
2
3
4
5
6
h1 {
  margin-bottom: 15px;
}
p {
  margin-top: 10px;
}

https://magnusbenoni.com/content/images/2016/01/Adjacent.png

解决方案 1:让它们浮动起来,然后再用伪元素清除浮动(下面为了简单起见我直接让后者清除)

1
2
3
4
5
6
7
8
9
h1 {
  margin-bottom: 15px;
  float: left;
}
p {
  margin-top: 10px;
  clear: left;
  float: left;
}

解决方案 2:父级元素使用display: flex这种布局根本不存在 margin collapsing 的问题

上面的方案 1 不是好的解决办法,或者是使用绝对定位,这都打乱了正常的文档流。但是我发现直接设置overflow: auto或者是使用display: flow-root都无法解决,所以最好直接使用 flex 布局

  1. 父级 container 元素下的第一个元素会折叠margin-top,最后一个元素会折叠margin-bottom,表现为第一个元素使用margin-top和最后一个元素使用margin-bottom都无法撑开父元素。而且如果父元素自己也设置了margin-top或者margin-bottom,会与第一个元素或者最后一个元素设置的margin发生重叠
1
2
3
<div>
  <p>The margin of this paragraph ends up outside the parent.</p>
</div>
1
2
3
4
5
6
div {
  margin-top: 10px;
}
p {
  margin-top: 20px;
}

https://magnusbenoni.com/content/images/2016/01/Parent-and-child.png

解决方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* 方案1 */
div {
  overflow: auto;
}
/* 方案2 */
div {
  display: flex;
}
/* 方案3 */
div {
  display: flow-root;
}
  1. 空元素上下边距折叠
1
2
3
4
5
<div id="container">
  <div>Top block.</div>
  <div id="empty"></div>
  <div>Bottom block.</div>
</div>
1
2
3
4
#empty {
  margin-top: 50px;
  margin-bottom: 50px;
}

https://magnusbenoni.com/content/images/2016/01/Empty-3.png

解决方案:

1
2
3
4
5
6
7
8
9
/* 方案1, 注意这里使用 flow-root和overflow都无效*/
#container {
  display: flex;
  flex-direction: column;
}
/* 方案2 */
#empty {
  display: flex; /* 或者display: flow-root 或者overflow: auto */
}

使用overflow创建的 BFC,虽然解决了浮动元素的问题,但是在边距折叠中,只有第二三种情况可以使用这种方式,而且,使用它创建 BFC,还有如下缺点

  1. 上面的情况父元素都没有设置高度,如果父元素需要设置高度,则使用overflow: scroll创建 BFC 可能会出现不必要的滚动条;同理,使用overflow: hidden则可能会截断重要的内容
  2. 道理和第一点类似,overflow属性本来就是用来告诉开发人员如果文档内容溢出应该如何处理的问题,如果开发人员不理解 BFC,他很难知道一段代码是用来创建 BFC 还是处理溢出的情况

因此草案CSS Display Module Level 3提出了display: flow-root用来规范创建 BFC 的方式

  1. https://magnusbenoni.com/collapsing-margins-css/
  2. https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context

重绘是指元素的显示样式发生改变,不涉及到布局位置的改变,例如改变background-colorvisibility等等;而重排则会影响到元素的布局位置,例如设置display: none,改变字体大小,改变浏览器窗口大小等等

重排会影响到其兄弟元素,父子元素都发生改变,会影响页面加载的性能,以下几种做法可以尽量减小这种影响

  1. 改变元素样式时尽可能针对到某个特定元素,避免扩大影响的范围
  2. 避免多次使用 JS 改变行内样式,每次更改行内样式,如果涉及到位置变化,都会引发一次 Layout,我们可以更改其父元素样式,引发一次 Layout 即可
  3. 尽量在绝对定位的元素上使用动画,动画触发多次 Layout,比较影响性能,为了不让其影响到其他元素,应该将其设置在脱离正常文档流的元素上
  4. 减小 table 布局的使用,单元格里一个小小的改变将会影响整个 table 的布局

关于页面加载以及 Layout 这一过程的细节,可以看我的另一篇文章,深入理解浏览器解析渲染 HTML

相关内容