前端面试复习指南【代码演示多多版】之——CSS

30 阅读10分钟

1. CSS3新特性

最常用的:

  • 选择器:像 :nth-child 让我能精准选中元素,不用加一堆class
  • 圆角和阴影border-radius 和 box-shadow 让UI更精致
  • transform:做位移、旋转、缩放动画
  • 渐变linear-gradient 实现漂亮的背景过渡

布局相关的:

  • Flexbox:解决一维布局(导航栏、列表)
  • Grid:解决二维布局(整体页面架构)
  • 多列布局:做类报纸效果
  • 媒体查询:实现响应式适配

特效相关的:

  • 过渡和动画:让交互更丝滑
  • 文字特效text-shadow 做标题美化
  • 滤镜filter: blur() 实现毛玻璃

新单位remvw/vh 让布局更灵活

2. 盒子模型

2.1 什么是盒子模型

在CSS中,每一个HTML元素都是一个"盒子" ,这个盒子由四个部分组成:

┌─────────────────────────────────┐
│           margin(外边距)       │
│  ┌───────────────────────────┐  │
│  │       border(边框)       │  │
│  │  ┌─────────────────────┐  │  │
│  │  │   padding(内边距)  │  │  │
│  │  │  ┌───────────────┐  │  │  │
│  │  │  │               │  │  │  │
│  │  │  │   content     │  │  │  │
│  │  │  │  (内容区)    │  │  │  │
│  │  │  │               │  │  │  │
│  │  │  └───────────────┘  │  │  │
│  │  └─────────────────────┘  │  │
│  └───────────────────────────┘  │
└─────────────────────────────────┘

2.2 盒子模型的两种计算方式

标准盒子模型(W3C标准)

box-sizing: content-box;  /* 默认值 */

计算公式:

  • 元素总宽度 = width + padding + border
  • 元素总高度 = height + padding + border

IE盒子模型(怪异模式)

box-sizing: border-box;  /* 常用! */

计算公式:

  • 元素总宽度 = width(包含padding和border)
  • 元素总高度 = height(包含padding和border)

3. 对BFC的理解

3.1 它是什么?(Block Formatting Context)

BFC的全称是 Block Formatting Context,中文叫块级格式化上下文

你可以把它想象成一个独立的小宇宙或者一个封闭的盒子

在这个小宇宙里,无论里面的元素只在这个盒子内部产生影响,绝对不会跑到盒子外面去干扰别的元素。同样,外面的元素也伸不进手来影响盒子内部的东西。

3.2 它能干什么?(BFC解决了什么问题)

场景一:边距打架--边距“打架”(我想要两个分开,它们却贴在一起)

你看到的画面:

┌─────────┐
│  盒子A  │  ← 距离下方 20px
└─────────┘
(这里应该有 40px 空隙)
┌─────────┐
│  盒子B  │  ← 距离上方 20px
└─────────┘

实际发生的画面:

┌─────────┐
│  盒子A  │
└─────────┘
    (只有 20px 空隙)
┌─────────┐
│  盒子B  │
└─────────┘

像什么?
两个人约好保持2米距离——

  • 盒子A往后退了1米
  • 盒子B往后退了1米
  • 结果:两人之间还是只有1米,因为后退的距离重叠

BFC怎么帮?
让盒子A或盒子B“退到不同的房间里”:

  • 盒子A在房间1里退1米
  • 盒子B在房间2里退1米
  • 结果:两个房间的距离 = 1米 + 1米 = 2米

场景二:高度塌陷--爸爸不管孩子(我想要爸爸包住孩子,爸爸却缩水了)

你想要的画面:

┌───────────────────┐
│ ╔══════╗ ╔══════╗ │
│ ║孩子1 ║ ║孩子2 ║ │
│ ╚══════╝ ╚══════╝ │
└───────────────────┘

实际发生的画面(孩子浮动了):

╔══════╗   ╔══════╗
║孩子1 ║   ║孩子2 ║
╚══════╝   ╚══════╝

┌─────┐
│ 爸爸│  ← 缩成一条线,包不住孩子
└─────┘

像什么?

  • 爸爸铺开一张野餐垫,两个孩子跑出去玩了(浮动)
  • 爸爸以为孩子不在垫子上了,就把垫子卷起来了
  • 结果:孩子在外面玩,爸爸缩成一团

BFC怎么帮?
告诉爸爸:“你是个负责的家长,不管孩子跑多远,你都得把垫子铺开罩住他们。”

  • 爸爸触发BFC后,会主动计算:“哦,我孩子还在,我得把垫子撑开”

场景三:文字环绕--我想要文字在右边,文字却绕着走

你想要的画面:

┌──────┐ ┌────────────────┐
│      │ │                │
│ 图片 │ │    文字内容     │
│      │ │    应该在右边   │
└──────┘ │    整整齐齐     │
          └────────────────┘

实际发生的画面:

┌──────┐
│      │ ┌─────────────┐
│ 图片 │ │文字开始往上 │
│      │ │绕过来 像这  │
└──────┘ │样绕在图片   │
  文字会 │旁边 不是    │
  一直绕 │整齐的一列   │
  下去   └─────────────┘

像什么?

  • 左边有个圆形花坛(浮动图片)
  • 你本来想在右边铺一条直直的路(文字内容)
  • 结果路遇到花坛就拐弯,绕着花坛走

BFC怎么帮?
告诉右边的文字:“你是个有原则的区域,别绕着走,直接挡住花坛,形成一条直直的边界。”

  • 文字触发BFC后,就不会“流”到花坛旁边,而是自觉地在右边形成一个矩形区域

3.3 怎么召唤BFC?(最简单的口诀)

不需要写 bfc: true;,而是用这些属性来“触发”它:

触发方式怎么写常用度
overflow: hidden;.box { overflow: hidden; }⭐⭐⭐ 最常用,副作用小
display: flow-root;.box { display: flow-root; }⭐⭐ 专门用来创建BFC,无副作用
float: left/right;.box { float: left; }⭐ 但会改变布局,慎用
position: absolute;.box { position: absolute; }⭐ 脱离文档流,慎用
display: flex;.box { display: flex; }⭐⭐ 现代布局常用

4. CSS选择器

4.1 常用选择器

/* 基础选择器 */
*            /* 通用选择器 */
div          /* 标签选择器 */
.class       /* 类选择器 */
#id          /* ID选择器 */

/* 组合选择器 */
div p        /* 后代选择器 */
div > p      /* 子元素选择器 */
div + p      /* 相邻兄弟选择器 */
div ~ p      /* 通用兄弟选择器 */

/* 属性选择器 */
[type="text"]          /* 完全匹配 */
[class^="icon"]        /* 以icon开头 */
[class$="-btn"]        /* 以-btn结尾 */
[class*="btn"]         /* 包含btn */

/* 伪类 */
:hover        /* 鼠标悬停 */
:focus        /* 获取焦点 */
:first-child  /* 第一个子元素 */
:last-child   /* 最后一个 */
:nth-child(n) /* 第n个 */
:not(selector)/* 排除 */

/* 伪元素 */
::before      /* 元素前 */
::after       /* 元素后 */
::first-line  /* 第一行 */

4.2 优先级权重

!important    >    行内样式    >    ID      >    类/属性    >    标签    >    通配符
                   1000           100            10             1            0

讲解

  • 权重可以累加:#nav .item a = 100 + 10 + 1 = 111
  • 相同权重:后面的覆盖前面的
  • !important:最高优先级,慎用(难以维护)

5. 布局系统

5.1 浮动

float: left/right;        /* 浮动 */
clear: both;              /* 清除浮动 */

/* 清除浮动方法 */
.clearfix::after {
  content: "";
  display: block;
  clear: both;
}

5.2 定位

position: static;         /* 默认 */
position: relative;       /* 相对自身 */
position: absolute;       /* 绝对定位 */
position: fixed;          /* 固定视口 */
position: sticky;         /* 粘性定位 */

/* 偏移属性 */
top/right/bottom/left
z-index  /* 层叠顺序 */

5.3 Flex布局(一维布局) ⭐

/* 容器属性 */
display: flex;
flex-direction: row/column;        /* 主轴方向 */
flex-wrap: wrap;                   /* 换行 */
justify-content: flex-start/center/space-between; /* 主轴对齐 */
align-items: center;                /* 交叉轴对齐 */
align-content: space-around;        /* 多行对齐 */
gap: 10px;                          /* 间距 */

/* 项目属性 */
flex-grow: 1;                       /* 放大比例 */
flex-shrink: 1;                      /* 缩小比例 */
flex-basis: 0;                   /* 初始大小 */
flex: 1 1 0;                      /* 简写:把剩下的空间都占满 */
align-self: center;                  /* 单独对齐 */
order: 1;                            /* 排序 */

5.4 Grid布局(二维布局)

display: grid;
grid-template-columns: 1fr 1fr 1fr;  /* 三列等宽 */
grid-template-rows: 100px auto;      /* 两行 */
gap: 10px;                            /* 间距 */

/* 合并单元格 */
grid-column: 1 / 3;                   /* 占1-3列 */
grid-row: 1 / span 2;                 /* 占1行跨越2行 */

6. 重排和重绘

6.1 什么是重排和重绘

概念比喻本质代价
重排重新装修房子,敲墙改格局重新计算位置、大小💥 高
重绘只给墙刷个新颜色重新画外观样式⚡ 低

核心关系:

  • 重排 一定导致 重绘(格局变了,肯定要重新画)
  • 重绘 不一定导致 重排(只是换个颜色,格局没变)

6.2 如何减少重排

批量操作样式

用 cssText 或 class 替代逐条修改

// 每次修改都触发重排
const el = document.getElementById('box');
el.style.width = '100px';    // 重排
el.style.height = '200px';   // 重排
el.style.margin = '10px';    // 重排
el.style.padding = '20px';   // 重排
// 触发了4次重排!

// 1.cssText
el.style.cssText = 'width: 100px; height: 200px; margin: 10px; padding: 20px;';
// 只触发1次重排

// 2.class
// CSS里预定义好
.new-style {
  width: 100px;
  height: 200px;
  margin: 10px;
  padding: 20px;
}

// JS里直接加class
el.classList.add('new-style');  // 1次重排

使用 transform

代替 top/left 做动画,把一些渲染任务交给显卡(GPU) 处理

/* 触发重排 */
.box {
  position: absolute;
  left: 100px;
  top: 200px;
  transition: left 0.3s;  /* 动画时不断重排 */
}

/* 不触发重排 */
.box {
  transform: translate(100px, 200px);
  transition: transform 0.3s;  /* GPU加速,丝滑 */
}

脱离文档流

让动画元素绝对定位

/* 让动画元素绝对定位,不影响其他人 */
.animate {
  position: absolute;  /* 或 fixed */
  top: 0;
  left: 0;
}

读写分离(避免强制重排)

// 交错读写 - 触发多次重排
el.style.width = '100px';        // 写
console.log(el.offsetWidth);      // 读(强制重排)
el.style.height = '200px';       // 写
console.log(el.offsetHeight);     // 读(强制重排)
el.style.margin = '10px';        // 写
console.log(el.offsetTop);        // 读(强制重排)

// 读写分离批量写
el.style.width = '100px';
el.style.height = '200px';
el.style.margin = '10px';

// 读写分离批量读(只触发一次重排)
const width = el.offsetWidth;
const height = el.offsetHeight;
const top = el.offsetTop;

缓存布局信息

不要循环读 offset 属性

// 每次循环都读,每次读都触发重排
for(let i = 0; i < 100; i++) {
  console.log(el.offsetHeight);  // 读100次,重排100次
}

// 只读一次,后面用缓存
const height = el.offsetHeight;
for(let i = 0; i < 100; i++) {
  console.log(height);  // 用缓存,不触发重排
}

批量操作DOM

用 DocumentFragment 或先隐藏

错误示范:

// 每次添加都触发重排
const ul = document.getElementById('list');
for(let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `item ${i}`;
  ul.appendChild(li);  // 100次重排
}

正确姿势:

方法A:用DocumentFragment(虚拟容器)

const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();  // 虚拟容器

for(let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `item ${i}`;
  fragment.appendChild(li);  // 操作虚拟DOM
}

ul.appendChild(fragment);  // 1次重排

方法B:先用display:none隐藏

const ul = document.getElementById('list');
ul.style.display = 'none';  // 隐藏(1次重排)

// 批量添加
for(let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `item ${i}`;
  ul.appendChild(li);
}

ul.style.display = 'block';  // 显示(1次重排)
// 总共2次重排,比100次好多了

用 visibility 替代 display(如果只是隐藏)

场景用哪个原因
元素要消失且不影响布局display: none触发重排,但别人会占位
元素要消失但保持占位visibility: hidden只重绘,不重排
/* 只重绘,不重排 */
.hidden {
  visibility: hidden;
}

避免 table 布局

table 重排代价大

/* 避免 */
table {
  display: table;
}

/* 推荐 */
div {
  display: flex;
  display: grid;
}

使用 will-change(提前告知浏览器优化)

/* 告诉浏览器这个元素要变了,提前优化 */
.animate {
  will-change: transform, opacity;
}

7. 浏览器渲染过程

  DOM树构建  →  2. CSSOM树构建  →  3. 渲染树构建  →  4. 布局(重排) →  5. 绘制(重绘)
      ↓                  ↓                  ↓                  ↓                  ↓
  解析HTML         解析CSS         合并DOM+CSSOM       计算位置大小        填充像素

8. 响应式--媒体查询

/* 媒体查询 */
@media (max-width: 768px) {
  /* 手机 */
}

@media (min-width: 769px) and (max-width: 1024px) {
  /* 平板 */
}

/* 单位 */
%            /* 百分比 */
vw/vh        /* 视口宽高 */
rem          /* 相对于根元素 */
em           /* 相对于父元素 */

9. Sass和Less的区别

9.1 先看代码长得什么样

Less 写法(像普通CSS)

@蓝色: #1890ff;    // 变量用 @
@字体大小: 14px;

.button {
  color: @蓝色;     // 直接用变量
  font-size: @字体大小;
}

Sass 写法(也像普通CSS)

$蓝色: #1890ff;    // 变量用 $
$字体大小: 14px;

.button {
  color: $蓝色;     // 直接用变量
  font-size: $字体大小;
}

唯一的区别:  Less用 @,Sass用 $。就这么简单!


9.2 核心区别:一个像"复制粘贴",一个像"编程"

Less:像"高级复制粘贴"

// 定义一段样式
.圆角(@半径) {
  border-radius: @半径;
}

// 需要的地方直接"复制"过来
.button {
  .圆角(5px);  // 就是把上面的代码贴到这里
}

.card {
  .圆角(10px); // 再贴一次
}

Sass:像"写程序"

// 定义
@mixin 圆角($半径) {
  border-radius: $半径;
}

// 调用
.button {
  @include 圆角(5px);  // 像在"执行"一段程序
}

// Sass还能这样(Less做不到)
@for $i from 1 through 3 {
  .col-#{$i} {
    width: $i * 100px;
  }
}
// 自动生成:
// .col-1 { width: 100px; }
// .col-2 { width: 200px; }
// .col-3 { width: 300px; }

9.3 总结:该怎么选?

你的情况推荐原因
刚接触、只想让CSS更好写Less像CSS一样简单,半小时上手
团队项目、需要严谨规范Sass功能强大,大厂都在用
写简单页面、不用复杂逻辑Less够用就行
写组件库、UI框架Sass循环判断很有用

一句话记法:

  • Less = CSS plus(CSS基础上加点料)
  • Sass = 写CSS像写代码(有逻辑、能计算)

10. CSS的常见单位

10.1 px像素

是什么?

绝对单位,屏幕上的1个点。

代码示例

.box {
  width: 200px;      /* 固定宽200像素 */
  font-size: 16px;   /* 固定字16像素 */
  border: 1px solid #000;  /* 1像素边框 */
  margin: 20px;      /* 固定间距20像素 */
}

特点

✅ 优点:  稳定、精确、好控制
❌ 缺点:  不随屏幕变化,大屏上显小,小屏上显大

适用场景

  • 边框(border: 1px
  • 小图标尺寸
  • 固定宽度的侧边栏
  • 圆角(border-radius: 8px

10.2 百分比 %

是什么?

相对单位,相对于父元素的相同属性

代码示例

.parent {
  width: 500px;
  height: 300px;
  font-size: 20px;
}

.child {
  width: 50%;        /* = 250px (父宽度的50%) */
  height: 50%;       /* = 150px (父高度的50%) */
  font-size: 80%;    /* = 16px (父字体的80%) */
  margin-left: 10%;  /* = 50px (父宽度的10% - 注意不是高度!) */
  padding: 5%;       /* = 25px (父宽度的5% - 也不是高度!) */
}

特点

✅ 优点:  随父元素变化,实现响应式
❌ 缺点:  多层嵌套时计算复杂

两个大坑(面试常考)

/* 坑1:margin/padding的%永远相对于父元素的宽度 */
.parent { width: 500px; height: 1000px; }
.child { margin-top: 10%; }  /* = 50px,不是100px! */

/* 坑2:父元素没高度时,height:%失效 */
.parent { }  /* 没有设置高度 */
.child { height: 50%; }  /* 不生效! */

适用场景

  • 响应式布局(width: 100%
  • 图片自适应(max-width: 100%
  • 流体布局

10.3 em

是什么?

相对单位,相对于当前元素或父元素的字体大小。

代码示例

/* 情况1:相对于父元素 */
.parent {
  font-size: 20px;
}
.child {
  font-size: 0.8em;  /* = 16px (20 * 0.8) */
  width: 2em;        /* = 32px (相对于自己的font-size 16px) */
  padding: 1em;      /* = 16px (相对于自己的font-size) */
}

/* 情况2:em会叠加 */
html { font-size: 16px; }        /* 16px */
div { font-size: 1.5em; }        /* 24px (16*1.5) */
div p { font-size: 0.8em; }      /* 19.2px (24*0.8) */
div p span { font-size: 0.5em; } /* 9.6px (19.2*0.5) */

特点

✅ 优点:  组件内部比例协调,随文字大小整体缩放
❌ 缺点:  会叠加,层级深了难以预测

适用场景

  • 按钮内边距(让按钮大小随文字变化)
  • 卡片内部间距
  • 组件库开发

10.4 rem

是什么?

相对单位,相对于根元素(html) 的字体大小。

代码示例

/* 设置根元素字体 */
html {
  font-size: 16px;  /* 1rem = 16px */
}

/* 使用rem */
.title {
  font-size: 2rem;     /* = 32px */
  margin: 1.5rem;      /* = 24px */
}

.content {
  font-size: 1rem;     /* = 16px */
  width: 10rem;        /* = 160px */
  padding: 1rem 2rem;  /* = 16px 32px */
}

/* 移动端适配:改变根字体 */
@media (max-width: 375px) {
  html {
    font-size: 14px;  /* 小屏上整体缩小 */
  }
}

特点

✅ 优点:  全局统一、可控、不叠加
❌ 缺点:  所有元素都听根元素的

适用场景

  • 全局字体大小
  • 页面主要间距
  • 移动端适配

10.5 vw / vh

是什么?

视口单位,相对于浏览器窗口。

单位含义
vw视口宽度的1%
vh视口高度的1%

代码示例

/* 全屏区域 */
.hero {
  width: 100vw;      /* 满屏宽 */
  height: 100vh;     /* 满屏高 */
}

/* 随屏幕缩放的文字 */
h1 {
  font-size: 8vw;    /* 屏幕越宽字越大 */
  /* 屏幕宽375px → 30px */
  /* 屏幕宽414px → 33px */
}

/* 正方形(宽高相等) */
.square {
  width: 30vw;
  height: 30vw;      /* 用vw保证宽高相等 */
}

/* 居中弹窗 */
.modal {
  width: 80vw;
  height: 60vh;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

特点

✅ 优点:  直接跟随视口,适合全屏和响应式
❌ 缺点:  手机上有工具栏,100vh可能超出

适用场景

  • 全屏背景图/轮播图
  • 大标题文字
  • 弹窗、浮层

10.6 对比总结表

单位参考对象会不会叠加适用场景一句话记法
px不叠加边框、小图标、固定尺寸打死不变
%父元素会叠加流体布局、响应式听爸爸的话
em父/自身会叠加按钮、卡片内部听爹的话
rem根元素不叠加全局字体、间距听老祖宗的话
vw/vh视口不叠加全屏、大标题看窗户大小