面试必看:css 的那些事(上)

311 阅读14分钟

在前端开发这块儿,CSS那可是核心技术之一,重要性怎么强调都不为过。不管是打造好看的用户界面,还是搞定复杂的页面布局,CSS都起着关键作用。今天呢,咱们就好好唠唠CSS面试里常见的几个重点问题,希望能帮大家更好地应付面试,提升自己的CSS本事。讲解的时候,我会结合具体的代码例子,再讲讲页面呈现出来的效果,让大家能更直观地理解这些知识点。

1. 说说你对 CSS 盒子模型的理解

一般我们被问到对xxx的理解,应该从三点出发:是什么?有什么特点?应用场景?

是什么

当浏览器渲染一个容器时,会将其渲染成 4 个区域,即 content(内容区)、padding(内边距)、border(边框)、margin(外边距)。这 4 个区域共同构成了我们所说的盒子模型。

它就像是一个装东西的盒子,内容在盒子内部,内边距是盒子内部与内容的间隔,边框围绕着内容和内边距,外边距则是盒子与其他元素之间的间隔。

从浏览器的检查页面截取的盒子模型,是这样子的,大家可以去自己的浏览器看一下:

image.png

有什么特点

1. 标准盒子模型:在标准盒子模型中,元素的width仅等于content的宽度。例如对于上述代码中的.box元素,它设置了width: 200px,那么在标准盒子模型下,其内容区宽度就是 200px。如果给它添加padding、border和margin,比如:

.box {
  width: 200px;
  padding: 20px;
  border: 10px solid black;
  margin: 30px;
}

image.png

此时,盒子实际占据的宽度为 :

width + 2 * padding + 2 * border + 2 * margin = 200 + 2 * 20 + 2 * 10 + 2 * 30 = 340px。

从页面效果可以看到,.box元素因为这些属性的设置,整体占据的空间变大,且内容区始终保持 200px 宽度。

2. IE 盒子模型:IE 盒子模型中,元素的width等于content + padding + border的宽度。假设同样的.box元素在 IE 盒子模型下(在现代浏览器中可通过特定设置模拟),代码如下:

.box {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 10px solid black;
  margin: 30px;
}

image.png

这里通过box-sizing: border-box;启用了类似 IE 盒子模型的计算方式。

此时,盒子实际占据的宽度为 :width + 2 * margin = 200 + 2 * 30 = 260px,

其中 200px 已经包含了content、padding和border的宽度。从页面效果能看到,与标准盒子模型不同,width所设定的值涵盖了内容区、内边距和边框的宽度,整体占据空间的计算方式发生了变化。

两张图对比看一下也能看出明显的区别。

2. 说说你对 CSS 选择器的理解

是什么

不熟悉的友友可以用这个代码测试一下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #wrap {
      width: 300px;
      height: 300px;
      border: 1px solid black;
    }
    div {
      width: 200px;
      height: 200px;
      border: 1px solid red;
    }
  </style>

</head>

<body>

  <div class="wrap" id="wrap" style="border-color: blue;">
    <div class="box" id="box">
      <h2>hello</h2>
    </div>
  </div>

  <h2>world</h2>
  <h2 abc="true">world2</h2>

</body>

</html>

1. id 选择器(#) :通过元素的id属性来选择特定的元素。

例如 #myDiv,只能匹配 id 为 myDiv 的唯一元素。

2. 类名选择器(.) :根据元素的 class 属性来选择元素。

例如 .myClass,可以匹配所有 class 为 myClass 的元素。代码中的 .box 就是类名选择器,所有 class 为 box 的元素都会应用其设置的样式。

3. 标签选择器(如 h2) :直接根据 HTML 标签名来选择元素。

例如 h2,会选中页面中所有的 <h2>标签元素。在代码中,两个 <h2> 标签元素都会受到通用 h2 标签选择器样式的影响(如果有设置的话)。

4. 后代选择器(.wrap h2) :选择某个元素的所有后代元素。

例如.wrap h2,会选中 class 为 wrap 的元素内部所有的<h2>标签元素,无论层级多深。在代码中,#wrap 内部的 <h2>hello</h2> 会被 .wrap h2 这样的后代选择器选中(假设存在该选择器样式设置)。

5. 子代选择器(.wrap > h2) :只选择某个元素的直接子元素。

例如 .wrap > h2,只会选中 class 为 wrap 的元素的直接子元素中的 <h2> 标签元素。由于 #wrap 的直接子元素是 .box,而不是<h2>,所以 .wrap > h2 在这段代码中不会选中任何元素。

6. 相邻兄弟选择器(.wrap + h2) :选择紧接在某个元素后的相邻兄弟元素。

例如 .wrap + h2,会选中 class 为 wrap 的元素后面紧邻的 <h2> 标签元素。在代码中,<h2>world</h2> 紧跟在 #wrap 元素之后,所以如果有 .wrap + h2 的样式设置,该 <h2> 元素会被选中。

7. 兄弟选择器(.wrap ~ h2) :选择某个元素之后的所有兄弟元素。

例如 .wrap ~ h2,会选中class 为 wrap 的元素之后的所有 <h2> 标签元素。在代码中,<h2>world</h2><h2 abc="true">world2</h2> 都在 #wrap 元素之后,都会被 .wrap ~ h2 选中(假设存在该选择器样式设置)。

8. 群组选择器(.wrap, h2) :将多个选择器组合在一起,选择多个元素。

例如 .wrap, h2,会选中 class 为 wrap 的元素以及所有 <h2> 标签元素。在代码中,#wrap 元素以及两个 <h2> 元素都会被 .wrap, h2 选中。

9. 伪类选择器(如 a:hover、a:visited) :用于选择处于特定状态的元素。

例如 a:hover 表示鼠标悬停在链接上时的状态,a:visited 表示链接被访问过之后的状态。虽然代码中没有链接元素展示伪类选择器效果,但如果有 <a> 标签,就可以通过 a:hover 设置鼠标悬停时的样式,比如改变颜色等,从页面交互效果(截图展示鼠标悬停时的样式变化)能直观看到伪类选择器的作用。

10.伪元素选择器(如 a::after) :用于创建一些不在文档树中的元素,并为其添加样式。

例如 a::after 可以在链接元素的后面添加一些内容并设置样式。同样,代码中未展示,但如果有 <a> 标签,设置 a::after {content: ' - 点击后查看';},在页面上链接元素后面就会出现额外添加的内容。像这样:

image.png

11. 属性选择器(如 [type=text]) :根据元素的属性及属性值来选择元素,例如 [type=text]会选中所有 type 属性值为 text 的元素,常见于表单元素。在代码中,<h2 abc="true">world2</h2>,如果有 [abc="true"] 这样的属性选择器设置,该<h2>元素会被选中。

有什么特点

选择器的优先级遵循 !important > 内联 > id > 类名 > 标签选择器 的规则。

例如:

/* 内联样式 */
<div style="color: red;">这是红色文字</div>
/* id选择器 */
#myDiv {
  color: blue;
}
/* 类名选择器 */
.myClass {
  color: green;
}
/* 标签选择器 */
div {
  color: yellow;
}

如果一个元素同时满足多个选择器的条件,那么最终样式会按照上述优先级来确定。

如果给某个样式加上!important,例如 color: purple!important;,则该样式的优先级最高,会覆盖其他规则。

在上述代码中,#wrap 元素通过内联样式 style="border-color: blue;" 设置了边框颜色为蓝色,由于内联样式优先级高于 #wrap 选择器本身设置的 border: 1px solid black 中的黑色边框,所以最终#wrap元素的边框颜色为蓝色。

3. 说说 em/rem/vh/vw 的区别

1. em:相对于声明了 font-size 的父元素的字体大小。

例如,如果父元素的font-size为 16px,子元素设置font-size: 1.5em;,那么子元素的字体大小就是16px * 1.5 = 24px。在实际代码中,如果有如下结构:

<div style="font-size: 16px;">
  <p style="font-size: 1.5em;">这是一段文字</p>
</div>

image.png

p元素的字体大小就会根据父元素的 font-size 计算得出 24px,从页面展示效果能看到这种相对计算的效果。

2. rem:相对于根元素(即<html>标签)的字体大小。

假设在代码中,标签设置了font-size: 10px,有一个.box元素设置font-size: 2rem;,那么.box元素内的文字大小就会是 20px。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Rem Unit Example</title>
  <style>
    html {
      font-size: 10px;
    }

    .box {
      font-size: 2rem;
      border: 1px solid #ccc;
      padding: 10px;
      margin: 10px;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>

image.png

3. vh:相对于视窗高度的百分比。

1vh 等于视窗高度的 1%。比如,视窗高度为 800px,那么 1vh 就是 800px * 0.01 = 8px。常用于创建自适应高度的布局,例如使某个元素的高度始终为视窗高度的一半,可以设置 height: 50vh; 。以如下代码为例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      height: 50vh;
      background-color: red;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>

动画.gif

无论视窗高度如何变化,.box元素的高度始终会保持为视窗高度的 50%,从不同视窗高度下的页面截图能清晰看到这种自适应效果。

4. vw:相对于视窗宽度的百分比。1vw 等于视窗宽度的 1%。假设视窗宽度为 1200px,那么 1vw 就是1200px * 0.01 = 12px。常用于创建自适应宽度的布局,比如使某个元素的宽度始终为视窗宽度的三分之一,可以设置width: 33.33vw;。例如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 33.33vw;
      height: 500px;
      background-color: blue;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>

动画.gif

随着视窗宽度的改变,.box元素的宽度会始终保持为视窗宽度的三分之一左右,通过不同视窗宽度下的页面截图可展示其自适应特性。

移动端适配方案

1. rem:通过设置根元素的 font-size 为不同屏幕宽度下的合适值,然后使用 rem 单位来设置元素的尺寸,从而实现移动端适配。

2. 媒体查询:通过 @media 规则,针对不同的屏幕尺寸范围设置不同的样式。例如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      margin: 0;
      padding: 0;
    }
   .box{
      width: 200px;
      height: 200px;
      background-color: red;
    }
    @media screen and (max-width: 1000px)  {
      .box{
        width: 100px;
        height: 100px;
        background-color: blue;
      }
    }
  </style>
</head>
<body>
  <div class="box">
    <div class="context">
      <h2>hello</h2>
    </div>
  </div>
</body>
</html>

动画.gif

当屏幕宽度小于等于 1000px 时,.box元素的宽度、高度会变为 100px,背景颜色变为蓝色。通过在不同屏幕宽度下查看页面(截图展示不同宽度下的效果),能清晰看到媒体查询在移动端适配中的作用。

pc 端适配方案

1. % + 媒体查询:使用百分比单位设置元素的尺寸,使其相对于父元素进行自适应,再结合媒体查询针对不同屏幕宽度范围进行微调。例如,一个容器设置width: 50%;,会根据父容器的宽度自适应,同时可以通过媒体查询在不同屏幕宽度下调整其宽度比例。

2. rem:同样可以在 PC 端使用 rem 进行适配,原理与移动端类似,通过合理设置根元素的font-size,然后用 rem 单位来布局。

4. CSS 中有哪些方式可以隐藏元素?区别?

举个栗子,本来是这样的页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 10px;
      height: 10px;
      background-color: red;
      /* overflow: hidden; */
      /* display: none; */
      /* opacity: 0; */
      /* visibility: hidden; */
      /* clip-path: polygon(0 100px, 50px 0, 100px 100px, 0 10px); */
      /* clip-path: polygon(0 0,0 0,0 0,0 0); */
      /* transform: scale(0);  */
      /* position: absolute; */
      /* left: -9999px; */

    }
  </style>
</head>
<body>
  <div class="box"></div>
  <p>hello</p>

  <script>
    let box = document.querySelector('.box');
    box.addEventListener('click', function() {
      console.log('hello');
    });
  </script>
</body>
</html>

image.png

1. display: none; :元素完全不占据文档流,在页面中就像不存在一样,也不会触发任何事件。例如:

image.png

2. opacity: 0; :元素仍然占据文档流,只是透明度为 0,视觉上不可见,但可以触发事件。例如:

image.png

3. visibility: hidden; :元素占据文档流,只是不可见,也不会触发事件。与opacity: 0不同的是,visibility: hidden隐藏的元素虽然不可见,但仍然会影响页面的布局,例如会占据空间。例如:

image.png

4. clip-path: polygon(0 0,0 0,0 0,0 0); :通过裁剪路径将元素裁剪为一个点,从而使其不可见,但仍然占据文档流,不触发事件。例如:

动画.gif

5. position: absolute; :将元素设置为绝对定位,然后将其移出可视区域,例如left: -9999px;,元素虽然在文档流中,但不在可视范围内,不会触发事件。

image.png

6. width: 0;height: 0;overflow: hidden; :将元素的宽高设置为 0,并隐藏溢出内容,元素仍然占据文档流位置,但不可见,不触发事件。

image.png

7. transform: scale(0); :将元素缩放为 0,元素仍然占据文档流,不触发事件。例如:

image.png

5. 说说你对 BFC 的理解

是什么

BFC(Block Formatting Context)即块级格式化上下文,是浏览器渲染的一个特殊区域,有其独特的渲染规则。BFC 容器是一个独立的容器,容器里面的子元素不会影响到外面的元素,反之亦然。可以把它看作是一个隔离的 “小世界”,内部元素的布局和渲染不会干扰到外部。

有什么特点

1. bfc 容器在计算高度时,会把浮动元素也考虑进去:在普通文档流中,浮动元素可能会导致父元素高度塌陷,但如果父元素是一个 BFC 容器,那么在计算高度时会包含浮动子元素的高度。例如:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BFC 容器计算高度示例</title>
    <style>
        .parent {
            border: 10px solid black;
            /* overflow: hidden; 创建 BFC */
        }
        .float-child {
            float: left;
            width: 100px;
            height: 100px;
            background-color: red;
        }
    </style>
</head>

<body>
    <div class="parent">
        <div class="float-child"></div>
    </div>
</body>

</html>

动画.gif

此时,.parent元素的高度会正确包含.float-child元素的高度。

2. 子元素的盒模型不会影响到 bfc 外部的元素:BFC 内部元素的外边距等不会与外部元素发生重叠等干扰。例如,BFC 内部元素设置的 margin-top 不会影响到外部元素的位置。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BFC Margin Isolation Example</title>
    <style>
        .bfc-container {
          /* 触发 BFC */
            /* overflow: hidden;  */
            background-color: lightblue;
        }
        .bfc-child {
            margin-top: 50px;
            background-color: lightcoral;
            height: 100px;
        }
        .outside-element {
            background-color: lightgreen;
            height: 100px;
        }
    </style>
</head>

<body>
    <div class="bfc-container">
        <div class="bfc-child"></div>
    </div>

    <div class="outside-element"></div>
</body>

</html>

动画.gif

3. 其他特性和普通容器一样:BFC 容器在布局和显示上遵循普通块级元素的一些基本规则,如可以设置宽度、高度、边距等。

怎么实现

1. overflow: hidden || auto || scroll; :通过设置元素的overflow属性为hidden、auto或scroll,可以创建 BFC。

2. float: left || right; :设置元素为浮动(left或right),也可以使该元素成为一个 BFC 容器。

3. position: absolute || fixed; :绝对定位(absolute)或固定定位(fixed)的元素会创建 BFC。

4. display: inline-xxx || table-xxxx || flex || grid || tabel; :一些特定的display属性值,如inline-block、table-cell、flex、grid等,也能使元素创建 BFC。

应用场景

1. 清除浮动:利用 BFC 容器计算高度时包含浮动元素的特性,可以解决浮动导致的父元素高度塌陷问题。如上述例子中,通过使父元素成为 BFC 容器(设置overflow: hidden;),成功清除了浮动带来的影响。

2. 防止 margin 重叠:在 BFC 内部,子元素的margin不会与外部元素的margin发生重叠,从而避免了一些布局上的问题。

3. 自适应两栏布局:可以利用 BFC 的特性实现自适应两栏布局。例如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      margin: 0;
      padding: 0;
    }
    .left{
      width: 200px;
      height: 80vh;
      background-color: red;
      float: left;
    }
    .right{
      height: 100vh;
      background-color: rgb(94, 229, 229);
      /* overflow: hidden; */
    }
  </style>
</head>
<body>
  <div class="left"></div>
  <div class="right"></div>
</body>
</html>

动画.gif

通过使右边元素成为 BFC 容器,它会自动适应左边浮动元素的布局,实现两栏自适应。