← 返回场景决策

🎨 CSS/HTML 场景决策

什么时候用 / 什么时候不用 / 边界在哪里 — 10 大核心场景全解析

Flex vs Grid BFC position 居中方案 响应式 CSS动画 CSS变量 语义化标签 CSS-in-JS 伪元素

1. Flex vs Grid — 布局方案决策

Flex = 排队 一维排列,横着排或竖着排,简单灵活
Grid = 停车场 二维网格,行和列同时控制,精确到位
什么时候用 Flex
  • 导航栏:Logo + 菜单横排 — flex + justify-content: space-between
  • 按钮组:一排按钮 — flex + gap
  • 卡片内布局:图标 + 文字 + 操作 — flex + align-items: center
  • 单行/单列布局:只要一个方向的排列 — 一维就够了
  • 内容不固定:不知道有多少元素 — flex 自动换行或缩小
什么时候用 Grid
  • 整体页面布局:header + sidebar + main + footer — grid-template-areas
  • 卡片网格:3×4 的商品展示 — grid-template-columns: repeat(3, 1fr)
  • 仪表盘:不同大小模块拼在一起 — grid-row/column span
  • 固定行列:已知行列数 — Grid 布局更精确
  • 对齐要求严格:行列都要精确对齐 — Grid 原生支持

📊 Flex vs Grid 决策表

维度FlexGrid
维度一维(行或列)二维(行+列同时)
内容驱动 vs 布局驱动内容驱动(弹性适应内容)布局驱动(先定义格子再填内容)
导航栏✅ 最佳杀鸡用牛刀
卡片网格可用但不精确✅ 最佳
页面骨架嵌套太深✅ grid-template-areas
动态数量元素✅ 自动换行需改 repeat
精确行列对齐✅ 原生支持

💻 页面骨架 — Grid Template Areas

📀 layout-system
📁 src/styles/
layout.css
responsive.css
variables.css
/* 经典后台布局 */ .layout { display: grid; grid-template-areas: "header header" "sidebar main" "footer footer"; grid-template-columns: 240px 1fr; grid-template-rows: 60px 1fr 40px; min-height: 100vh; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .footer { grid-area: footer; } /* 响应式:移动端侧边栏折叠 */ @media (max-width: 768px) { .layout { grid-template-areas: "header" "main" "footer"; grid-template-columns: 1fr; } .sidebar { display: none; } }
面试金句 grid-template-areas 是 CSS 中唯一能"画图式"定义布局的属性——直接用 ASCII 字符画布局图,HTML 结构只需绑定 grid-area 名称即可。响应式只需改变 areas 布局,无需改 HTML 结构,这是 Grid 相比 Flex 做页面骨架的核心优势。

💻 导航栏 — Flex 最佳场景

📀 frontend-app
📁 src/components/
Navbar.vue
Sidebar.vue
layout/
AppLayout.vue
/* 简单又强大的导航栏 */ .navbar { display: flex; align-items: center; /* 垂直居中 */ justify-content: space-between; /* 两端对齐 */ padding: 0 24px; height: 60px; } .navbar-links { display: flex; gap: 16px; /* 替代 margin 的现代方案 */ } /* <nav class="navbar"> <div class="logo">Logo</div> <div class="navbar-links"> <a>首页</a> <a>关于</a> <a>联系</a> </div> </nav> */
面试金句 gap 是 Flexbox 的现代间距方案,替代了传统的 margin 嵌套(最后一个元素需 :last-child 去 margin)。justify-content: space-between 让首尾元素分别贴左右边缘、中间元素均分,是导航栏 Logo 在左、菜单在右的经典布局。

2. BFC — 块格式化上下文决策

BFC = 独立办公室 里面的东西不管怎么翻,不影响外面
没有BFC = 开放办公区 别人的东西会飘到你的桌上(margin塌陷)
什么时候需要创建 BFC
  • 清除浮动:子元素 float 后父元素高度塌陷 — overflow: hidden 创建 BFC
  • 防止 margin 合并:相邻块元素 margin-top 和 margin-bottom 合并 — 创建 BFC 隔离
  • 文字不环绕浮动元素:文字不该绕着 float 图片走 — 给文字容器创建 BFC
  • 自适应布局:避免 float 元素覆盖正常流 — BFC 区域不会与 float 重叠
什么时候不要滥用 BFC
  • overflow: hidden 切断内容:有下拉菜单/弹窗的容器不能用 overflow:hidden
  • 用 BFC 代替 Flex:现代布局优先用 Flex/Grid,别用 float + BFC

📊 创建 BFC 的方式

方式推荐度副作用适用场景
overflow: hidden/auto✅ 最常用可能切断内容清除浮动
display: flow-root✅ 最佳无副作用现代浏览器首选
display: flex/grid本身就是BFC布局顺便解决
float: left/right❌ 不推荐自身脱离文档流别用了
position: absolute/fixed❌ 不推荐脱离文档流别用了

💻 BFC 清除浮动对比

📀 frontend-app
📁 src/styles/
clearfix.css
layout.css
/* ❌ 问题:子元素浮动后父元素高度为0 */ .parent { border: 1px solid red; /* 高度塌陷!看不到边框 */ } .child { float: left; width: 100px; height: 100px; } /* ✅ 方案1:overflow 创建 BFC */ .parent { border: 1px solid green; overflow: hidden; /* 或 auto */ } /* ✅ 方案2:display: flow-root(最干净) */ .parent { border: 1px solid blue; display: flow-root; /* 无副作用,专为BFC设计 */ } /* ✅ 方案3:现代做法——直接用 Flex */ .parent { display: flex; /* Flex 本身就是 BFC,顺便解决布局 */ }
面试金句 display: flow-root 是 W3C 为清除浮动专门设计的 BFC 创建方式,无 overflow:hidden 的内容截断副作用。现代开发中,float 布局已被 Flex/Grid 取代,BFC 清除浮动的主要价值在于理解 CSS 渲染机制(面试高频),实际编码中直接用 Flex 即可。

3. position 定位方案决策

static = 站队 默认位置,一个挨一个
relative = 站队但稍微挪 还在队伍里,但歪了点
absolute = 出队站到指定位置 脱离队伍,找最近的 relative 祖先对齐
fixed = 钉在屏幕上 相对于浏览器窗口,滚动也不动
sticky = 平时站队,到位置就粘住 滚动到阈值前是 static,到后变 fixed

📊 position 决策表

脱离文档流参照物典型场景
static无(正常流)默认,不需要定位
relative❌(还占位)自身原始位置为 absolute 子元素提供参照
absolute最近的非 static 祖先下拉菜单、Tooltip、Badge
fixed浏览器视口固定导航栏、回到顶部
sticky❌→✅(动态)最近的滚动祖先表头吸顶、侧边栏粘滞

💻 sticky 吸顶表头

📀 frontend-app
📁 src/components/
DataTable.vue
StickyHeader.vue
/* 表头滚动到顶部时吸住 */ .table-header { position: sticky; top: 0; /* 距顶部0px时开始粘滞 */ z-index: 10; /* 确保在内容上方 */ background: #fff; /* 必须设背景,否则内容会透过来 */ } /* ⚠️ sticky 失效的常见原因 */ // 1. 父元素有 overflow: hidden/auto/scroll → sticky 失效 // 2. 父元素高度不够 → 没有滚动空间 // 3. 没设置 top/bottom → 不知道粘在哪里
面试金句 position: sticky 是 relative 和 fixed 的混合体:在阈值前表现为 relative,到达 top/left 阈值后表现为 fixed,但不会脱离父容器最常见坑:父元素设了 overflow: hidden/auto/scroll 会导致 sticky 失效,因为 sticky 的参照物是最近的可滚动祖先。

4. 居中方案 — 万年面试题决策

居中 = 画框挂画 水平居中=左右对齐,垂直居中=上下对齐

📊 居中方案决策表

方案代码适用场景兼容性
Flex 居中 ⭐display:flex; justify-content:center; align-items:center;通用,首选99%
Grid 居中 ⭐display:grid; place-items:center;最简洁97%
绝对定位 + transformposition:absolute; top:50%; left:50%; transform:translate(-50%,-50%)已知/未知宽度都行98%
绝对定位 + margin:autoposition:absolute; inset:0; margin:auto;需要已知宽高98%
行内元素text-align:center; line-height=height单行文字100%

💻 各种居中实现

📀 frontend-app
📁 src/styles/
centering.css
layout.css
/* ✅ 方案1:Flex 居中 — 最推荐 */ .container { display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ height: 100vh; } /* ✅ 方案2:Grid 居中 — 最简洁 */ .container { display: grid; place-items: center; /* 一行搞定!*/ height: 100vh; } /* ✅ 方案3:绝对定位 + transform */ .container { position: relative; height: 100vh; } .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* ✅ 方案4:绝对定位 + margin auto(需已知宽高)*/ .child { position: absolute; inset: 0; /* top:0; right:0; bottom:0; left:0 的简写 */ margin: auto; width: 200px; height: 100px; }
面试金句 居中方案的选择优先级:Grid place-items(最简,1 行)> Flex justify+align(最通用)> 绝对定位 + transform(兼容性好)> margin:auto(需已知宽高)。transform: translate(-50%, -50%) 基于元素自身尺寸计算百分比,所以不受宽高是否已知的影响。
⚠️ 面试答题顺序:先说 Flex(最现代)→ Grid(最简洁)→ 绝对定位 + transform(兼容)→ margin auto(经典),最后说 line-height 只适用于行内元素。

5. 响应式布局决策

响应式 = 一件衣服多个尺码 同一款设计,S/M/L 不同版型
响应式方案选择
  • Media Query:断点式布局 — 最传统、最精确
  • Flex 换行:flex-wrap 自动换行 — 最自然的响应式
  • Grid auto-fit:grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) — 最优雅
  • Container Query:根据容器宽度而非视口 — 最精准(2023+)
  • clamp():font-size: clamp(14px, 2vw, 18px) — 流式缩放

📊 响应式方案决策表

方案参照物最佳场景浏览器支持
Media Query视口宽度整体页面布局切换100%
Flex 换行容器宽度卡片列表自动换行99%
Grid auto-fit容器宽度不等宽卡片网格97%
Container Query父容器宽度组件级响应式92%
clamp()视口宽度字号/间距流式缩放97%

💻 Grid auto-fit 自适应卡片

📀 frontend-app
📁 src/styles/
responsive.css
variables.css
/* 不用 media query,自动响应式!*/ .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; } /* 容器宽度 > 1120px → 4列 */ /* 容器宽度 840-1120px → 3列 */ /* 容器宽度 560-840px → 2列 */ /* 容器宽度 < 560px → 1列 */ /* clamp() 流式字号 */ h1 { font-size: clamp(24px, 5vw, 48px); /* 最小24px,理想5vw,最大48px */ }
面试金句 repeat(auto-fit, minmax(280px, 1fr)) 是 CSS 最优雅的响应式方案:auto-fit 让浏览器根据可用空间自动计算列数,minmax(280px, 1fr) 设定每列最小 280px、最大平分剩余空间。零 media query 实现自适应卡片网格。clamp(24px, 5vw, 48px) 实现字号流式缩放(最小值、理想值、最大值)。

💻 Container Query 组件级响应式

📀 frontend-app
📁 src/styles/
container-query.css
responsive.css
/* 父容器声明查询容器 */ .sidebar { container-type: inline-size; } /* 根据父容器宽度(不是视口宽度)变化 */ @container (min-width: 400px) { .card { display: flex; /* 宽了就横排 */ gap: 16px; } } @container (max-width: 399px) { .card { display: block; /* 窄了就竖排 */ } } /* 同一个 .card 组件,放在 sidebar 里是竖排,放在 main 里是横排 */ /* 不用关心视口宽度,只关心自己所在容器宽度!*/
面试金句 Container Query(@container) 是 2023 年所有主流浏览器正式支持的新特性,实现了组件级响应式:Media Query 基于视口宽度,Container Query 基于父容器宽度。解决了同一个组件在不同位置(侧边栏 300px vs 主区域 800px)需要不同布局的问题,是组件化开发的关键基础设施。

6. CSS 动画方案决策

transition = 开关门 只有两个状态,从A到B平滑过渡
animation = 连续剧 多个关键帧,可以循环播放
什么时候用 transition
  • hover 效果:按钮变色、卡片浮起 — 两个状态之间过渡
  • 展开/折叠:max-height 从 0 到 auto — 简单状态变化
  • 显示/隐藏:opacity 0→1 — 淡入淡出
  • 输入焦点:border-color 变化 — 简单属性过渡
什么时候用 animation
  • 循环动画:loading 旋转、脉冲呼吸 — infinite 循环
  • 多关键帧:弹跳效果、复杂路径 — 多个百分比状态
  • 自动播放:不需要触发条件 — 页面加载就播放
  • 序列动画:先变色再移动再缩放 — 多步骤

📊 动画方案决策表

方案触发方式关键帧循环性能场景
transition状态变化触发2个(起止)hover/展开
animation + @keyframes自动/触发多个loading/弹跳
requestAnimationFrameJS手动任意最好游戏/物理动画
Web Animations APIJS控制多个JS精确控制动画

💻 高性能动画属性选择

📀 frontend-app
📁 src/styles/
animation.css
performance.css
/* ✅ 只改这些属性 → GPU 加速,60fps */ .fast-animation { transform: translateX(100px); /* 移动 */ opacity: 0.5; /* 透明度 */ } /* ❌ 改这些属性 → 触发重排重绘,卡顿 */ .slow-animation { left: 100px; /* ❌ 触发 layout */ top: 100px; /* ❌ 触发 layout */ width: 200px; /* ❌ 触发 layout */ margin: 20px; /* ❌ 触发 layout */ } /* 性能等级:transform/opacity > box-shadow > color/background > width/height/margin */
面试金句 浏览器渲染流水线:JavaScript → Style → Layout → Paint → Compositetransform 和 opacity 只触发 Composite 阶段(GPU 合成层),跳过 Layout 和 Paint,所以是唯一能保证 60fps 的动画属性。width/top/left/margin 触发 Layout(重排),导致整棵渲染树重新计算,是动画卡顿的元凶。

💻 常用 CSS 动画

📀 frontend-app
📁 src/styles/
keyframes.css
animation.css
/* Loading 旋转 */ @keyframes spin { to { transform: rotate(360deg); } } .spinner { animation: spin 1s linear infinite; } /* 脉冲呼吸 */ @keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.05); opacity: 0.8; } } .pulse { animation: pulse 2s ease-in-out infinite; } /* 弹入效果 */ @keyframes bounceIn { 0% { transform: scale(0.3); opacity: 0; } 50% { transform: scale(1.05); } 70% { transform: scale(0.9); } 100% { transform: scale(1); opacity: 1; } } .bounce-in { animation: bounceIn 0.5s ease-out; }
面试金句 @keyframes 定义关键帧动画,百分比(0%/50%/100%)控制各阶段状态。animation 简写:duration | timing-function | delay | iteration-count | direction。ease-in-out 模拟物理减速(先加速后减速),linear 匀速适合旋转。所有动画只操作 transform 和 opacity,保证 GPU 加速。

7. CSS 变量 — 主题与设计系统决策

CSS 变量 = 全局配置文件 改一处,所有引用它的地方都跟着变
SCSS 变量 = 编译时替换 编译后就写死了,运行时不能改
什么时候用 CSS 变量 (--var)
  • 主题切换:暗色/亮色模式 — 改 :root 变量,全局生效
  • 设计系统:颜色/间距/字号统一管理 — 一处定义处处引用
  • 运行时变化:用户可调节的配置 — JS 动态改 CSS 变量
  • 组件主题:组件级作用域 — .card { --card-padding: 16px }
什么时候用 SCSS 变量 ($var)
  • 编译时计算:颜色变暗/变亮 — SCSS darken/lighten 函数
  • 不需要运行时改变:值在编译时确定 — 静态设计 Token
  • 复用复杂计算:@mixin + $var — 条件逻辑在编译时处理

💻 暗色/亮色主题切换

📀 frontend-app
📁 src/
theme.css
main.js
/* 亮色主题(默认)*/ :root { --bg: #ffffff; --text: #1a1a1a; --primary: #3b82f6; --card-bg: #f3f4f6; --border: #e5e7eb; } /* 暗色主题 */ [data-theme="dark"] { --bg: #F7F6F3; --text: #cdd6f4; --primary: #58a6ff; --card-bg: #FFFFFF; --border: #30363d; } body { background: var(--bg); color: var(--text); transition: background 0.3s, color 0.3s; } // JS 切换主题 document.documentElement.dataset.theme = isDark ? 'dark' : 'light'; // 或者跟随系统 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
面试金句 CSS 变量(Custom Property)实现主题切换的核心优势:运行时可修改(SCSS 变量编译后写死)。通过 [data-theme="dark"] 选择器覆盖 :root 变量,所有引用 var() 的元素自动更新。prefers-color-scheme media query 可跟随操作系统暗色模式,实现无感知主题适配。

8. 语义化标签 — HTML 结构决策

语义化 = 贴标签的文件柜 每个抽屉写清楚里面是什么,找人一目了然
div 汤 = 没标签的杂物箱 全是 div,不知道哪个是导航哪个是内容
语义化标签的好处
  • SEO:搜索引擎理解页面结构 — header/nav/main/article 权重不同
  • 无障碍:屏幕阅读器根据语义导航 — 盲人用户能跳转到导航区
  • 可维护性:代码自解释 — <nav> 比 <div class="nav"> 更清晰
  • 浏览器默认:语义标签自带无障碍角色 — 不用手写 role="navigation"

📊 语义化标签对照表

语义标签替代的 div含义使用频率
<header><div class="header">页头/区域头⭐⭐⭐
<nav><div class="nav">导航区域⭐⭐⭐
<main><div class="content">主内容(每页1个)⭐⭐⭐
<article><div class="post">独立内容块⭐⭐
<section><div class="section">内容分区⭐⭐⭐
<aside><div class="sidebar">侧边栏/附属内容⭐⭐
<footer><div class="footer">页脚/区域尾⭐⭐⭐
<figure><div class="image-wrapper">图片/图表容器
<time><span class="date">日期时间

💻 语义化页面结构

📀 frontend-app
📁 src/views/
ArticlePage.html
HomePage.html
<!-- ✅ 语义化结构 --> <header> <nav aria-label="主导航"> <a href="/">首页</a> <a href="/about">关于</a> </nav> </header> <main> <article> <h1>文章标题</h1> <time datetime="2024-01-15">2024年1月15日</time> <p>文章内容...</p> <figure> <img src="chart.png" alt="数据图表"> <figcaption>图1:月度增长趋势</figcaption> </figure> </article> <aside> <h2>相关推荐</h2> ... </aside> </main> <footer> <p>版权信息</p> </footer> <!-- ❌ div 汤 --> <div class="header"> <div class="nav">...</div> </div> <div class="content"> <div class="post">...</div> <div class="sidebar">...</div> </div>
面试金句 语义化标签的核心价值不在于样式(CSS 可以给 div 加相同样式),而在于机器可读性:搜索引擎爬虫、屏幕阅读器、浏览器插件通过语义标签理解页面结构。<main> 每页只能有一个,<article> 表示可独立分发的内容(文章/评论),<section> 表示 thematic grouping。

9. CSS-in-JS vs CSS Modules — 样式方案决策

CSS-in-JS = 内联定制西装 量体裁衣,完美贴合,但贵(运行时开销)
CSS Modules = 品牌成衣 批量生产,自动分类(hash类名),性价比高
原生 CSS = 基础款 简单直接,Vue SFC scoped 天然隔离

📊 样式方案决策表

方案运行时作用域动态样式框架绑定推荐度
Vue SFC scoped编译时隔离v-bind() 有限Vue⭐⭐⭐
CSS Modules编译时hash有限无关⭐⭐⭐
Tailwind CSS原子化有限无关⭐⭐⭐
Styled Components运行时hash✅ 强React⭐⭐
Emotion运行时hash✅ 强React⭐⭐
全局 CSS无隔离有限无关

💻 Vue 3 SFC scoped 样式(最推荐)

📀 vue-app
📁 src/components/
Card.vue
Button.vue
<style scoped> /* 自动添加 data-v-xxx 属性,样式只对当前组件生效 */ .card { padding: 16px; border-radius: 8px; } .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.1); } </style> <style> /* 不加 scoped 的 style 块 = 全局样式 */ /* 适合引入第三方库的全局样式覆盖 */ .el-dialog { border-radius: 12px; } </style>
面试金句 Vue <style scoped> 在编译时给当前组件的每个元素添加 data-v-xxxxxx 属性,CSS 选择器自动追加属性选择器(.card → .card[data-v-xxx]),实现组件级样式隔离。无运行时开销,是 Vue 项目首选样式方案。可在 scoped 中用 :deep(.child) 穿透到子组件。

💻 CSS Modules (React/Vite)

📀 react-app
📁 src/components/
Button.jsx
Button.module.css
// 文件名必须带 .module.css // Button.module.css .btn { padding: 8px 16px; border-radius: 6px; } .primary { background: #3b82f6; color: white; } // Button.jsx import styles from './Button.module.css'; function Button({ variant, children }) { return ( <button className={`${styles.btn} ${styles[variant]}`}> {children} </button> ); }
面试金句 CSS Modules 在构建时将类名编译为带 hash 的唯一名(.btn → .Button_btn_1a2b3c),天然实现样式隔离。与 CSS-in-JS 的区别:CSS Modules 无运行时开销(编译时完成),CSS-in-JS(Styled Components)在运行时动态生成样式。React 项目首选 CSS Modules 或 Tailwind,Vue 项目用 SFC scoped。

10. 伪元素 vs 伪类 — 选择器决策

伪类 = 状态探测器 检测元素当前状态(hover/focus/first-child)
伪元素 = 隐形替身 创建一个不存在的DOM元素(::before/::after)
什么时候用伪元素 (::before / ::after)
  • 装饰性元素:不需要 DOM 节点的装饰线、图标 — 纯展示
  • 清除浮动:.clearfix::after { content: ''; display: block; clear: both; }
  • 图标叠加:required 表单字段的 * 号标记
  • 三角形:CSS 三角形 — border 技巧
  • 计数器:::before { content: counter(section) ". "; }
什么时候用伪类 (:hover / :focus 等)
  • 交互状态::hover, :focus, :active — 用户操作反馈
  • 结构选择::first-child, :nth-child(2n) — 选择特定子元素
  • 表单状态::checked, :disabled, :valid — 表单验证反馈
  • 否定选择::not(.active) — 排除某些元素

💻 伪元素实战

📀 frontend-app
📁 src/styles/
pseudo-elements.css
forms.css
/* 必填字段 * 标记 */ .required::after { content: '*'; color: red; margin-left: 4px; } /* CSS 三角形 */ .tooltip::after { content: ''; position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); border: 8px solid transparent; border-top-color: #333; /* 只有顶边有色 = 三角形 */ } /* 序号计数器 */ ol { counter-reset: section; } li { counter-increment: section; } li::before { content: counter(section) ". "; font-weight: bold; } /* 自定义 checkbox(隐藏原生 + 伪元素替代)*/ input[type="checkbox"] { display: none; } input[type="checkbox"] + label::before { content: ''; display: inline-block; width: 18px; height: 18px; border: 2px solid #999; border-radius: 3px; margin-right: 8px; vertical-align: middle; } input[type="checkbox"]:checked + label::before { background: #3b82f6; border-color: #3b82f6; }
面试金句 ::before 和 ::after 通过 content 属性在 DOM 中插入"虚拟元素"——不改变 HTML 结构,不影响 SEO。CSS 计数器(counter-reset / counter-increment) 可在纯 CSS 中实现自动编号。自定义 checkbox 技巧:隐藏原生 input + label::before 创建替代视觉 + :checked 伪类切换样式,实现完全可定制的表单控件。
💭 记忆口诀:伪类一个冒号(:hover),伪元素两个冒号(::before)。伪类选状态,伪元素造东西。

🎨 CSS/HTML 场景决策 · 全栈资料库

← 返回场景决策