一些面试时关于 CSS 的问题
1. 水平垂直居中问题
这可以说是最经典的问题了,水平垂直居中,这个问题从入门前端一直到面试,甚至到工作之后都会时不时遇到,最近的面试也被问过这之类的问题,这里还是好好总结一番,以作备忘。HTML 部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS居中</title>
</head>
<body>
<!-- <div class="grandfather"> -->
<div id="parent">
<div id="child"></div>
</div>
<!-- </div> -->
</body>
</html>
方法一:
设置子元素 position 为 absolute ,然后让 top 和 left 都为 50%,再使 margin 为子元素的长和宽的负 1/2 即可:
说明: 这种方法局限性很大,不做推荐。
注意: 此处父元素得添加 postion:relative/absolute ,因为 absolute 定位是基于最近的设有 position 的父元素进行定位,如果父元素没有设置 position ,则其会基于 body 定位。但是如果 body 也没有的设置 position 的话则基于视口高度计算位置:document.documentElement.clientHeight 。可以试试去掉 parent 的 position 会发生很奇妙的事呢!
附加: 关于子元素 position 设置为 relative 而引发的问题。如果此时父元素的 position 为 absolute,那么不会有什么奇怪的现象发生;但是如果此时父元素的 position 为 relative ,那么你会发现父元素会向上方移动一点距离,这又是为什么呢???这涉及到外边距塌陷(margin-collapse)问题了,这点在后面的一些2. 外边距问题中去解释了(提前透露:父级向上移动了-50px)。
推荐指数: ★
#parent {
background-color: black;
position: relative; /*或者absolute*/
height: 300px;
width: 300px;
}
#child {
background-color: #ccc;
position: absolute;
left: 50%;
top: 50%;
margin: -50px 0 0 -50px;
height: 100px;
width: 100px;
}
方法二:
CSS里面还有一种 display: table-cell 的属性,我们可以将父元素的 display 设置为 table-cell 属性,然后将子元素的 display 设置为 inline 或者 inline-block ,此时便可以用我们熟悉的 text-align: center 和 vertical-align: middle 来使得子元素水平垂直居中了。
说明: 这种方法最适合子元素都为行内元素(或者带有行内元素性质的块级元素)的布局,故而可以根据情况选用。
推荐指数: ★★★☆
#parent {
background-color: black;
display: table-cell;
text-align: center;
vertical-align: middle;
height: 300px;
width: 300px;
}
#child {
background-color: #ccc;
display: inline-block;
height: 100px; /*可以改为33.3%*/
width: 100px; /*可以改为33.3%*/
}
方法三:
方法一中只能解决子元素定大小问题,有时候子元素大小变化可以用以下方法解决:
说明: 这种方法非常棒,兼容性也很不错,强推!
附加: 关于子元素的 position 设为 relative 而使得垂直居中无效问题。关于这个问题其实很好理解,因为,postion: relative 的移动是基于自身原本的位置嘛,top 、 bottom 、 left 、 right 都为 0 ,换句话说就是位置不动,然而,你有见过块级元素能够 margin: auto 上下自动补齐的吗!没有吧~ 故而起作用的只有左右自动计算补齐而已,也就只有水平居中对齐了!
推荐指数: ★★★★★
#parent {
background-color: black;
position: relative;
height: 300px;
width: 300px;
}
#child {
background-color: #ccc;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
height: 33.3%;
width: 33.3%;
}
方法四:
由于CSS3的来临,我们也应该跟上时代的潮流,故而对上述方法有有所改进,在此我们可以尝试一下 transform 元素的 translate 2D 平移,让其想着 X 、Y 轴负方向移动自身长度的一半距离即可达到效果。
说明: 毕竟技术向新的方向发展,可以多尝试一下新技术,推荐。
注意: 由于浏览器的支持性问题,使用的时候可以检测 CSS 支持性,也可以通过 Autoprefixer CSS online 来写兼容性代码。
附加: 关于 CSS3 开启 GPU 硬件加速提升网站动画渲染性能问题。这一点不知道对此位置平移转换有没帮助,不过当做是拓展来介绍了,在此我就不详细说了,推荐看 CSS3 页面渲染加速。
推荐指数: ★★★★
#parent {
background-color: black;
position: relative;
height: 300px;
width: 300px;
}
#child {
background-color: #ccc;
position: absolute; /*或者relative*/
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 33.3%;
width: 33.3%;
}
方法五:
这个是另外一种 CSS3 的解决方法(我比较喜欢),即 flex 布局。通过设置父元素的 display 属性为 flex,然后设置其 align-content 和 justify-content 来使得子元素能够水平垂直居中。
说明: 这个方法在布局上非常灵活,在不考虑兼容的情况下,强烈推荐此方法进行布局。这真的是个非常棒的布局方法!
注意: 由于浏览器的支持性问题,使用的时候可以检测 CSS 支持性,也可以通过 Autoprefixer CSS online 来写兼容性代码。
附加: 关于flex的详细介绍可以参考阮老师的博客: Flex 布局语法篇和 Flex 布局实例篇。
推荐指数: ★★★★☆
#parent {
background-color: black;
display: flex;
display: -webkit-flex; /*Safari*/
align-content: center;
-webkit-align-items: center;
justify-content: center;
-webkit-justify-content: center;
height: 300px;
width: 300px;
}
#child {
background-color: #ccc;
height: 33.3%;
width: 33.3%;
}
2. 外边距问题
现象描述
第一种情形是,当你在一个 div 元素内插入一个块级子元素,然后设置其 margin-top 值,这是你会发现并不是子元素在父元素内撑开了一段距离,而是父元素向上撑开了一段距离。
原以为: 实际上:
body body
-------------------- --------------------
parent *
----------- | 50px
* *
| 50px parent
* -----------
child child
----------- -----------
parent parent
-------------------- --------------------
body body
另一种情况是,当你在 div 元素内插入多个块级元素,你给其中相邻的两个设置 margin-top 和 margin-bottom ,你会发现,他们之间的距离为相对应的 margin-top 和 margin-bottom 中的最大值,当设置第一个块级元素的 margin-top 属性则会像第一种情形那样,父级框移动了。
原以为:
parent
------------------------
| 10px
childA
| 10px ==> | 30px
| 20px ==> |
childB
| 20px
------------------------
parent
实际上:
body
--------------------------
| 10px
parent
----------------
childA
| max(10px, 20px) ==> 20px
childB
| 20px
----------------
parent
--------------------------
body
问题分析
详细解答在官方文档的 8.3.1 合并 margin 这一节,太长了,我就不复制了,但是简单来说可以用W3C上的话来总结:
外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。
合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。
这样就清楚了!下面说说我的理解(很实用):
- Case 1:
父亲与儿子并排站(没边界嘛,父亲位置不定),儿子说:我站在相对前方目标 1m 的地方,问:父亲站在离前方目标多远处?
显然是 1m 的地方嘛!因为目标不明确(父亲也是相对嘛),故而父亲同样也是站在相对前方 1m 处。 - Case 2:
ChildA: 我站在距离 ChildB 1m 的位置。
ChildB: 我站在距离 ChildA 1m 的位置。
问: ChildA、ChildB距离多远?
根据相对性,这不就是 1m 嘛!
依次类比,很相似吧(没边界挡着 == 站在同一起跑线)! (σ゚∀゚)σ..:*☆ 哎哟不错哦!!!
问题解决
其实分析时已经表明了,因为都站在同一起跑线,故而大家都不分先后,要让它们分开,设置点障碍就行了,如:
情况 1:
Method 1: 给父元素添加 border: 1px solid #xxxxxx(划分界限,父子没并排站,儿子前方有了目标)。
Method 2: 给父元素添加 overflow: hidden 属性(相当于父级给自己定了一个隐藏边界)。
Method 3:让父元素为绝对定位(因为相当于让父元素站在一个定点,子元素的前方目标明确了,是父亲相对自己距离为 0,距离也就拉开了)。
Method 4:为父元素声明浮动(浮动会脱离文档流,此时浏览器会给顶元素位置,即所能达到的最左上方)。
个人推荐: Method 2。
这个情况可以说是前一个情况的完整版,其实整体上就是BFC问题(前者也是),BFC的详细内容我就不细说了,推荐 BFC 神奇背后的原理。
情况 2:
首先得让父级元素按照情况 1中方式处理,其次是处理子元素。
Method 1:自己计算好相邻元素的距离,然后直接设置。
Method 2:给每个子元素添加一个 wrapper ,使得每个子元素都是 BFC 区域。
个人推荐: Method 2。
3. 奇怪的布局问题
现象描述
当你在 div 元素内插入多个行内块级元素,你给其中任意一个或者多个设置 margin-top 想要使得它/它们表现得与众不同,可是,到头来所有元素都会移动,而且唯一准确的只有所设值最大的那个子元素,其他子元素则混淆。
(注:所有子元素高度为 height: 50px)
原以为:
parent
----------------------------------------------------
| 50px childB | 25px
childA childC
----------------------------------------------------
parent
实际上:
parent
----------------------------------------------------
| 50px | 50px + 25px | 50px - 25px
childA childB childC
----------------------------------------------------
parent
问题分析
其实这个问题和IFC问题很相似,也就是行内元素基线的选择问题。 IFC 的介绍中有这么一段话:
IFC ( Inline Formatting Contexts )直译为"内联格式化上下文",IFC 的 line box(线框)高度由其包含行内元素中最高的实际高度计算而来(不受到竖直方向的 padding/margin 影响)
IFC 中的 line box 一般左右都贴紧整个 IFC ,但是会因为 float 元素而扰乱。
这就说到很明白了。由于这里是 display: inline-block 故而,其线框高度包含了 margin 、 border 、 padding 、 content ,这样就知道为什么其他元素的 margin 后的结果与预期不一致的问题了。(此处把 childC 的高度变为 300xp 则是以 childC 的基线为基准定位其他兄弟元素,因为此时 childC “最高”)。
友情推荐: 什么是 BFC 、IFC 、GFC 和 FFC
问题解决
要解决这个问题,想让不同子元素呈现不同效果,我们可以给每个子元素添加一个 wrapper ,并让 wrapper 以 BFC 的形式包裹子元素,然后你就可以肆无忌惮的改变每个子元素位置了!(参考1. 水平垂直居中问题)
/*wrapper style*/
.wrapper {
display: inline-block;
height: 100%; /*让 wrapper 基线对齐*/
overflow: hidden; /* BFC */
}
其他问题: 为什么其他子元素的 margin: mpx 不是在基线上位置向下移动 mpx 而是向上移动呢?(m > 0)
这个问题其实也很好说明,由于此处默认对齐方式是都是基于 bottom 的,故而都是在 bottom 这条起始线开始变化(可以打开浏览器看盒子模型), childB 由于没有 margin 故而其底边线为此时公用的底边界,其他盒子在此基础上有 margin-bottom 的开始向上移动(“最高”的盒子会撑高整个高度,其它盒子只会上升 margin-bottom 值),因此会出现此现象。
此题代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS外边距塌陷</title>
</head>
<style type="text/css">
#parent {
background-color: black;
height: 500px;
overflow: hidden;
width: 500px;
}
#parent div {
display: inline-block; /*包含了 wrapper */
}
#childA {
background-color: greenyellow;
height: 100px;
margin: 25px 0;
width: 100px;
}
#childB {
background-color: aliceblue;
height: 100px;
width: 100px;
}
#childC {
background-color: orangered;
height: 100px;
margin: 50px 0;
width: 100px;
}
.wrapper {
height: 100%;
overflow: hidden;
}
</style>
<body>
<div id="parent">
<div class="wrapper">
<div id="childA"></div>
</div>
<div class="wrapper">
<div id="childB"></div>
</div>
<div class="wrapper">
<div id="childC"></div>
</div>
</div>
</body>
</html>