阅读 1236

(11)让“盒子”动起来:② “定位”和 BFC | CSS

原创:itsOli  @前端一万小时

本文首发于公众号「前端一万小时」

本文版权归作者所有,未经授权,请勿转载!

本文节选自“语雀”私有付费专栏「前端一万小时 | 从零基础到轻松就业」
复制代码

2020.09.07 更新:
以下链接为本文最新勘误篇章——《让“盒子”动起来:② “定位”和 BFC》


1. 有几种定位方式?分别是如何实现定位的?参考点是什么?使用场景是什么?
2. z-index 有什么作用?如何使用?
3. BFC 是什么?如何生成 BFC?BFC 有什么作用?举例说明。
4. 在什么场景下会出现外边距合并?如何合并?如何不让相邻元素外边距合并?给个父子外边距合并的范例?
复制代码

🙋上方面试题“参考答案详解”,请点击此处查看获取方式!



前言: 这一篇我们主要探讨“定位”和 BFC 是怎样让“盒子”动起来的。

学习方法依然是:打开 JS Bin,拷贝代码运行查看效果,然后搞定每一行代码的前世今生。


1 什么是“定位”

“定位”就是通过设置 position 属性的值来脱离正常的文档流


2 “定位”分哪几类

2.1 相对定位

🔗源码及效果展示
HTML

<div class="box1">1</div>
<div class="box2">2</div>
<div class="box1">3</div>
复制代码

CSS

.box1, .box2 {
  width: 50px;
  height: 50px;
  border: 2px solid;
}
.box2 {
  position: relative;
  /*
  🚀relative 是相对于它原本正常流的位置进行偏移,
  视觉上看它是移动了,但它原本所占用的文档流的空间是没有变化的,
  对其他元素来说,它还是在那里的,故其他元素位置是不会有变化的。
   */

  top: 10px;
  left: 10px;
}
复制代码

2.2 绝对定位

当你设置为绝对定位后,文档流上的其他元素就看不见你了。就认为你不存在!如果存在多个绝对定位元素,那么这些绝对定位元素也互相看不见。

🔗源码及效果展示
HTML

<div class="box1">1</div>
<div class="box2">2</div>
<div class="box1">3</div>
复制代码

CSS

.box1, .box2 {
  width: 50px;
  height: 50px;
  border: 2px solid;
}
.box2 {
  color: red;
  position: absolute;
 
  top: 10px;
  left: 10px;

  /*
  ❓这个值是相对于谁做偏移呢?
  答:首先它会从自己的父元素里边去找,
  看看自己的父元素里边有没有定位,这个定位包括 relative、absolute、fix,
  如果有的话就相对于它;
  如果没有的话,就再从父元素的父元素里边去找,如果有的话就相对于它;
  如果再没有,就一直找到我们的 body。
   */
}
复制代码

2.2.1 ❓绝对定位的值是相对谁来取值做偏移

🔗源码及效果展示
HTML

<div class="container">
  <div class="box1">1</div>
  <div class="box2">2</div>
  <div class="box1">3</div>
</div>
复制代码

CSS

html {
  border: 3px solid blue;
  /* 3️⃣最后,如果都没有,那么 box2 的绝对定位值就相对于这里,到头了。 */

}
body {
  margin: 40px;
  border: 1px solid red;
  /*
  2️⃣其次,如果下边 container 没有 position,
  而这里有定位(relative、absolute、fixed),
  那么 box2 的绝对定位值就相对于这里:
  position: relative;
   */
}

container {
  margin-top: 40px;
  padding: 40px;
  background: yellow;
  /*
  1️⃣首先,如果 box2 父元素这里有定位(relative、absolute、fixed),
  那么 box2 的绝对定位值就相对于这里:
  position: relative;

  ❗️而这里用 relative 是最好的,因为一个元素本身设置 relative,
  却不设置值的话,就等同于没设置。

  那它就依然在普通流中,它还是一个普通元素,
  那它自己的位置也没发生变动,却可以给其子元素作为相对的锚点。
   */
}

.box1, .box2 {
  width: 50px;
  height: 50px;
  border: 2px solid;
}
.box2 {
  position: absolute;
  top: 0px;
  left: 10px;
}

/*
❗️所以,我们在使用绝对定位的时候,一定要设置好定位的参考点(锚点)。
一般来说,我们绝对定位的参考点都是相对于其父容器。

所以原则上是:一个元素设置绝对定位 absolute,
那它的父容器设置定位为 relative。
 */
复制代码

2.2.2 z-index

元素使用了绝对定位后,就如浮动一样,有了“块盒子”的特性。

由于使用绝对定位之后,产生元素覆盖的问题,z-index 可以解决元素之间覆盖顺序的问题,设置它的层叠顺序。同级元素,数值越大,越靠近视觉点;不同父元素,只要父元素越大,那么整体就越靠近视觉点,而不管其子元素大小情况

  • z-index:
    显示器显示的图案是一个二维平面,用 x 轴和 y 轴来表示位置属性。

    为了表示三维立体的概念,如显示元素的上下层的叠加顺序,引入了 z-index 属性来表示 z 轴上一个元素在叠  加顺序上的上下立体关系。

    z-index 值较大的元素将叠加在 z-index 值较小的元素之上。对于未指定此属性的定位对象,z-index 值为正数的对象会在其之上,而 z-index 值为负数的对象在其之下。

    z-index 属性适用于定位元素(position 属性值为 relative 或 absolute 或 fixed 的对象),用来确定定位元素 在垂直于显示屏方向(称为 Z 轴)上的层叠顺序,也就是说如果元素是没有定位的,对其设置的 z-index 会是无效的

  • 相同 z-index 谁上谁下?

    • 如果两个元素都没有定位发生位置重合现象或者两个都已定位元素且 z-index 相同发生位置重合现象,那么 按文档流顺序,后面的覆盖前面的。
    • 如果两个元素都没有设置 z-index,使用默认值,一个定位一个没有定位,那么定位元素覆盖未定位元素。
  • 父子关系处理:

    • 如果父元素 z-index 有效,那么子元素无论是否设置 z-index 都和父元素一致,会在父元素上方;
    • 如果父元素 z-index 失效(未定位或者使用默认值),那么定位子元素的 z-index 设置生效。

2.3 固定定位

position: fixed;
复制代码

相对于浏览器的窗口进行定位。因此当滚动产生时,固定定位元素依然处于窗口的某个位置不动


3 用“定位”还是用“浮动”

  • 大布局、自适应,用“浮动”————浮动一般和响应式结合的比较多;

  • 小元素、固定宽高,用“定位”————一般只适用于一些很小的 icon ;

🏆结合实际情况是关键。

比如说,网页头像上的未读消息,我们一般都是选择用“定位”才能很好的实现。凡是能盖住其他东西的也是“绝对定位”。


4 小实战:实现 navbar

🔗源码及效果展示
HTML

<nav>
  <ul>
    <li><a href="#">首页</a></li>
    <li><a href="#">作品</a></li>
    <li><a href="#">更多</a>
      <ul class="child">
        <li><a href="#">GitHub</a></li>
        <li><a href="#">博客</a></li> 
        <li><a href="#">知乎</a></li>
      </ul>     
    </li>
  </ul>
</nav>
复制代码

CSS

* {
  margin: 0;
  padding: 0;
}
ul {
  list-style: none;
}
a {
  color: #333;
  text-decoration: none;
}
nav {
  width: 500px;
  margin: 10px auto 0;
  box-shadow: 0px 2px 4px 1px rgba(0,0,0,0.3);
  /*
  🚀这里边的这几个值:第一、二个值表示这个阴影的水平、垂直的偏移;
  4px 表示模糊度;1px 是这个模糊的一个延展;rgba 是这个模糊的颜色。
   */
}
nav::after {
  content: '';
  display: block;
  clear: both;
}
/* 🚀由于我们用了浮动,我们需要清除浮动来撑开父容器。 */

nav>ul>li {
  position: relative;
  /* 🚀🚀🚀给下边 .child 的绝对定位”偏移值“加一个“锚点”。 */

  float: left;
  /* 🚀把这个选择器层级里的 li 水平排列。 */

}
nav>ul>li:hover .child {
  display: block;
}
/* 🚀🚀 .child 默认是隐藏的,当我鼠标放到“更多”这个 li 上的时候才显示,并显示为 block。 */


nav a {
  display: block;
  padding: 10px 10px;
  /*
  记着,我们要想点击的范围很大,我们是需要在 a 链接上来加 padding 的。
  如果在 li 上加 padding,那么点击范围依然在字上。

  但要注意,由于 a 链接是个行内元素,所以如果这里只单单加 padding,
  那其实他的宽高是没有变化的,只是背景色变大了,它会出现很多诸如“遮挡”的后果。
  故,我们需要让其显示为 block。

  我们没有用 inline-block 是因为:用 inline-block 即它同时还是有行内特性,
  会导致"收缩"——即宽度由文本内容宽度决定。
  想要撑开,而不受内容宽度影响,那就需要用 block。
   */

  min-width: 50px;
  /* 这里加了一个“最小的宽度”的用途是把这个 a 链接里边的文字内容撑开,
  使任何一个可点击的文本都不换行。
   */
}
nav a:hover {
  color: #fff;
  background: rgba(0, 0, 0, 0.4);
}
nav .child {
  position: absolute;
  /*
  🚀🚀🚀设置了绝对定位,我们才可以想把这个 .child 放哪里就放哪里,
  因为父容器发现不了它。而其他方式(比如浮动、或不用)是达不到理想的效果的。
   */

  top: 100%;
  /* 100% 就表示按照 li 的高度。 */

  box-shadow: 0px 2px 4px 1px rgba(0, 0, 0, 0.3);
  display: none;
  /*
  🚀🚀 .child 默认是"隐藏"的——display: none; ,
  当我鼠标放到“更多”这个 li 上的时候再显示。
  */
}
复制代码

5 BFC

❗️相关前置知识,请先阅读文章:
《(07)CSS 基本视觉格式化:① “块盒子”格式化 | CSS》
《(08)CSS 基本视觉格式化:② “行内盒子”格式化 | CSS》

  • 在正常流中,盒子要么属于块级格式化上下文,要么属于内联格式化上下文。

    每个渲染区域用 fomating context 表示,它决定了其子元素如何去定位,以及和其他元素的关系和相互作用。

    BFC 全称 Block Formating Context。

  • BFC 是一个独立的渲染区域,只有 block-level box 参与。它规定了内部 block-level box 如何布局,并且与这个区域外部毫不相关。

5.1 哪些元素会具有 BFC 的条件

display 属性为 block、list-item、table 的元素,会产生 BFC。也就是“块元素”。

5.2 什么情况下可以让元素产生 BFC

  • float 属性不为 none:

如果一个元素增加了一个属性叫 float,那这个元素本身也产生了一个块级格式化上下文。

  • position 为 absolute 或 fixed ;
  • display 为 inline-block、table-cells、flex、或者 inline-flex ;
  • overflow 不为 visible (为 hidden、auto、scroll )。

我们之所以要了解这个 BFC,是因为我们希望通过了解它的特性来实现我们需要的一些效果。或者当出现了某个问题的时候,我们能够去解释这个问题,知道是为什么,进而解决它或找到替代方案。

5.3 BFC 布局规则特性

  • ①:在 BFC 中,盒子从顶部开始垂直地一个接一个的排列;

  • ②:盒子垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻盒子的 margin 会发生重叠;

  • ③:在 BFC 中,每一个盒子的左外边距应该和包含块的左边接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此!

  • ④:BFC 的区域不会与浮动的盒子产生交集,且是紧贴浮动的边缘;

  • ⑤:计算 BFC 的高度时,浮动盒子的高度也参与计算;

  • ⑥:BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此!

5.4 BFC 常见的用途

  • 清除元素内部浮动

对应规则:⑤ 计算 BFC 的高度时,浮动盒子的高度也参与计算。

  • 解决外边距合并问题

对应规则:② 盒子垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻盒子的 margin 会发生重叠。

  • 制作自适应两栏布局

对应规则: ③ 在 BFC 中,每一个盒子的左外边距应该和包含块的左边接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此!

④ BFC 的区域不会与浮动的盒子产生交集,且是紧贴浮动的边缘。

5.4.1 清除元素内部浮动

❌问题:
🔗源码及效果展示

<ul class="navbar">
  <li><a href="#">1 首页</a></li>
  <li><a href="#">2 产品</a></li>
  <li><a href="#">3 服务</a></li>
  <li><a href="#">4 关于</a></li>
</ul>
复制代码
.navbar {
  list-style: none;
  border: 1px solid #ccc;
  /*
  加一个背景色也没效果:
  background: pink;
   */

}
.navbar>li {
  float: left;
  margin-left: 15px;
}

/*
🚀由于浮动元素脱离了文档流,所以他的父元素是看不见他的。
这里对于 navbar 来说,他认为里边没有什么 li 来把它撑开,
因为 li 已经浮动了,那没有东西撑开它,它就会认为高度为 0。
 */
复制代码

✔️解决方式:

对应规则:⑤ 计算 BFC 的高度时,浮动盒子的高度也参与计算。

🔗源码及效果展示

<ul class="navbar">
  <li><a href="#">1 首页</a></li>
  <li><a href="#">2 产品</a></li>
  <li><a href="#">3 服务</a></li>
  <li><a href="#">4 关于</a></li>
</ul>
复制代码
.navbar {
  list-style: none;
  border: 1px solid #ccc;
  overflow: hidden;
}
.navbar>li {
  float: left;
  margin-left: 15px;
}
复制代码

5.4.2 解决外边距合并问题

❌问题:
在文章《(07)CSS 基本视觉格式化:① “块盒子”格式化 | CSS》中,我们知道:

垂直格式化的另一个重要方面是垂直相邻 margin 的合并。 这种合并行为只应用于 margin,如果元素有 padding 和边框,padding 和边框是不会合并的。当两个或更多垂直 margin 相遇时,他们将形成唯一一个 margin,这个 margin 的高度等于两个发生叠加的 margin 的高度中的较大者。

❗️注意:当一个元素包含在另一个元素中时,彼此相邻的 margin-bottom 和 magin-top 也会发生叠加,取较大者。

🔗源码及效果展示

<div class="box1">1</div>
<div class="box2">2</div>
复制代码
.box1 {
  color: #fff;
  width: 100px;
  height: 100px;
  margin-bottom: 50px;
  background: blue;
}

.box2 {
  color: #fff;
  width: 100px;
  height: 100px;
  margin-top: 100px;
  background: pink;
}
复制代码

✔️解决方式:
将垂直方向上的盒子放在不同的 BFC 中,margin 就不会重叠了。

对应规则:② 盒子垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻盒子的 margin 会发生重叠。

🔗源码及效果展示

<div class="box1">1</div>

<div class="ct">
  <div class="box2">2</div>
</div>
<!-- 🚀我们在 box2 的外层包裹一层容器,并触发该容器生成一个 BFC。
那么两个 box 就不属于同一个 BFC,就不会发生 margin 重叠了! -->
复制代码
.ct {
  overflow: hidden; /* 🚀触发该容器生成一个 BFC */
}

.box1 {
  color: #fff;
  width: 100px;
  height: 100px;
  margin-bottom: 50px;
  background: blue;
}

.box2 {
  color: #fff;
  width: 100px;
  height: 100px;
  margin-top: 100px;
  background: pink;
}
复制代码

5.4.3 制作自适应两栏布局

❓问题:
在文章《(10)让“盒子”动起来:① 浮动 | CSS》中,我们通过加 margin-left 或 margin-right 的方式制作“有缝隙”的两栏布局的效果。

❗️但如果要求两栏布局中间没有缝隙,该怎么办呢?

❌由于:

③ 在 BFC 中,每一个盒子的左外边距应该和包含块的左边接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此!

🔗源码及效果展示

<div class="aside">侧边栏固定宽度</div>
<div class="main">
  Hey guys,我正在这个平台发放“前端一万小时”这个专栏的一系列文章。<br>
  这个专栏我已经完成了“从零基础到就业”相关的文章。<br>
  "从零基础到就业"包含 150+ 篇干货文章,300+ 道经典笔试、面试题。<br>
  如果你对本系列文章感兴趣,欢迎关注 「公众号:前端一万小时」,
  并点击菜单栏“轻松入职”来加入我们的“一万小时计划”!祝顺利^^……
</div>
复制代码
.aside {
  color: #fff;
  width: 150px;
  height: 100px;
  background: red;
  float: left;
}
.main {
  color: #fff;
  background: blue;
  height: 200px;
}
复制代码

✔️解决方式:

④ BFC 的区域不会与浮动的盒子产生交集,且是紧贴浮动的边缘。

通过触发 main 生成 BFC,来实现自适应无缝隙两栏布局:

🔗源码及效果展示

.main {
  overflow: hidden; /* 🚀触发 BFC */
}

.aside {
  color: #fff;
  width: 150px;
  height: 100px;
  background: red;
  float: left;
}
.main {
  color: #fff;
  background: blue;
  height: 200px;
}
复制代码


后记: 通过两篇兄弟文章,我们算是让“盒子”动了起来。接下来 3 篇文章,我们就让“这个可以动的盒子”更优雅的“展示”。

祝好,qdywxs ♥ you!


🏆答读者问: