一、盒模型:box-sizing到底救了多少人?
📝 标准答案
CSS盒模型分为两种:
标准盒模型(W3C):
width= 内容区宽度(content),padding 和 border 在外部扩展。IE盒模型(怪异):
width= content + padding + border,padding和border向内挤压。
box-sizing属性控制使用哪种盒模型:
box-sizing: content-box(默认):标准盒模型。box-sizing: border-box(推荐):IE盒模型,固定宽高后padding/border不会撑大盒子。
💻 代码演示
/* 标准盒模型(默认) */ .box-standard { box-sizing: content-box; width: 200px; padding: 20px; border: 2px solid red; /* 实际宽度 = 200 + 40 + 4 = 244px ❌ 容易撑破布局 */ } /* IE盒模型(推荐) */ .box-border { box-sizing: border-box; width: 200px; padding: 20px; border: 2px solid red; /* 实际宽度 = 200px ✅ 内容区压缩为 200-40-4=156px */ }<!-- 实际项目中全局重置(Ant Design / Element Plus 都这么干) --> <style> * { margin: 0; padding: 0; box-sizing: border-box; /* 一劳永逸 */ } </style>🎯 面试官追问
Q:父元素宽度500px,子元素左右padding各20px,border各1px,想让子元素占满父元素宽度,宽高怎么设置?
A:子元素
width: 100%; box-sizing: border-box;即可。如果是content-box,100% + 40 + 2 = 542px,直接溢出。
二、BFC(块级格式化上下文)——CSS最核心的"结界"
📝 标准答案
BFC(Block Formatting Context)是页面中一个独立的渲染区域,内部元素的布局不会影响外部元素。
触发BFC的条件(高频考点):
| 触发方式 | 示例 |
|---|---|
根元素(<html>) | 本身就是BFC |
float不为none | float: left/right |
position为absolute/fixed | 脱离文档流 |
display为inline-block / flex / grid / table-cell | 等 |
overflow不为visible | overflow: hidden/auto/scroll |
contain: layout / paint | 现代浏览器 |
BFC的核心特性(面试必背):
内部box垂直排列,margin会折叠(塌陷)。
BFC内部不会与浮动元素重叠(经典两栏自适应)。
BFC内部浮动元素也会参与高度计算(清除浮动)。
💻 代码演示:清除内部浮动
<!-- ❌ 父元素高度塌陷:子元素浮动,父元素高度为0 --> <div class="parent" style="border:2px solid blue;"> <div style="float:left; width:100px; height:100px; background:red;">浮动</div> </div> <!-- 父元素border紧贴着,没有高度 --> <!-- ✅ 触发BFC:父元素高度被浮动撑开 --> <div class="parent" style="border:2px solid blue; overflow:hidden;"> <div style="float:left; width:100px; height:100px; background:red;">浮动</div> </div> <!-- 父元素高度 = 100px + border -->
💻 代码演示:防止外边距塌陷
<!-- ❌ 两个兄弟div的margin会合并(取最大值) --> <div style="margin-bottom: 30px; background:red; height:50px;">A</div> <div style="margin-top: 50px; background:blue; height:50px;">B</div> <!-- 实际间距 = 50px,不是 30+50=80px --> <!-- ✅ 用父容器包裹其中一个并触发BFC --> <div> <div style="margin-bottom:30px; background:red; height:50px;">A</div> </div> <div style="margin-top:50px; background:blue; height:50px;">B</div> <!-- 或者给div加 overflow:hidden 触发BFC,阻止合并 -->
💻 代码演示:两栏自适应布局(经典)
<!-- 左侧固定宽度,右侧自适应(BFC不与浮动重叠) --> <div style="border:1px solid #ccc; overflow:hidden;"> <div style="float:left; width:200px; background:red; height:300px;">左侧固定</div> <!-- 右侧触发BFC,不会与左侧浮动重叠 --> <div style="overflow:hidden; background:blue; height:300px;"> 右侧自适应内容区域(BFC) </div> </div>
🎯 面试官追问
Q:display: flow-root和overflow: hidden触发BFC有什么区别?
A:
display: flow-root是专门用来触发BFC的,不会产生任何副作用(overflow:hidden会裁剪溢出的内容)。兼容性上,flow-root需Chrome 58+、Firefox 53+,现代项目大胆用。
三、Flex布局:flex: 1究竟代表什么?
📝 标准答案
flex是flex-grow、flex-shrink、flex-basis三个属性的缩写。
flex-grow:剩余空间的分配比例(默认0,不放大)。flex-shrink:空间不足时的收缩比例(默认1,允许缩小)。flex-basis:在分配空间之前的初始主轴尺寸(默认auto,即元素自身宽高)。
flex: 1等价于:flex-grow: 1; flex-shrink: 1; flex-basis: 0%。
核心理解:flex:1表示元素在主轴方向上平分剩余空间(把flex-basis设为0,所有元素从同一起跑线开始瓜分)。
📊 Flex核心属性速查
| 属性 | 作用 | 可选值 |
|---|---|---|
flex-direction | 主轴方向 | row(默认)/column/row-reverse |
justify-content | 主轴对齐 | flex-start/center/space-between/space-around/space-evenly |
align-items | 交叉轴对齐(单行) | stretch(默认)/center/flex-start/flex-end |
align-content | 交叉轴对齐(多行) | flex-start/center/space-between |
flex-wrap | 是否换行 | nowrap(默认)/wrap/wrap-reverse |
💻 代码演示:经典导航栏
<style> .nav { display: flex; justify-content: space-between; /* 两端对齐 */ align-items: center; /* 垂直居中 */ background: #1a1a2e; padding: 0 20px; height: 60px; } .nav-links { display: flex; gap: 20px; /* 子项间距,替代margin */ } .nav-links a { color: white; text-decoration: none; } .nav-user { display: flex; align-items: center; gap: 10px; color: white; } /* 圣杯布局:中间自适应 */ .container { display: flex; height: 500px; } .left { width: 200px; background: red; } .center { flex: 1; background: blue; } /* ✅ 占满剩余空间 */ .right { width: 150px; background: green; } </style> <div class="nav"> <div class="nav-links"> <a href="#">首页</a> <a href="#">产品</a> <a href="#">关于</a> </div> <div class="nav-user"> <span>👤 张三</span> <button>退出</button> </div> </div>🎯 面试官追问
Q:flex: 0 0 auto和flex: auto有什么区别?
A:
flex: 0 0 auto= 不放大、不缩小、保持原始尺寸(固定宽度)。
flex: auto=flex: 1 1 auto,放大且缩小,初始尺寸为自身大小(会尽量占满,但不会压缩内容)。
flex: none=flex: 0 0 auto,完全固定。
Q:align-items和align-content的区别?
A:
align-items控制单行内每个元素在交叉轴上的对齐方式;align-content控制多行(换行后)整个行组在交叉轴上的对齐方式。如果flex-wrap: nowrap(单行),align-content无效。
四、Grid布局:与Flex的本质区别是什么?
📝 标准答案
Flex:一维布局。控制主轴(行或列)上的元素排列,适合导航栏、列表、组件内对齐。
Grid:二维布局。同时控制行和列,适合复杂页面整体结构(如后台面板、图片墙、瀑布流)。
💻 代码演示:经典网格
.grid-container { display: grid; /* 三列:第一列200px,第二列1fr(剩余空间),第三列100px */ grid-template-columns: 200px 1fr 100px; /* 两行:第一行100px,第二行auto(根据内容撑开) */ grid-template-rows: 100px auto; /* 行/列间距 */ gap: 16px 24px; /* 行间距16px,列间距24px */ /* 区域命名(高阶) */ grid-template-areas: "header header header" "sidebar main ads" "footer footer footer"; min-height: 100vh; } .header { grid-area: header; background: #f1f1f1; } .sidebar { grid-area: sidebar; background: #ddd; } .main { grid-area: main; background: #eee; } .ads { grid-area: ads; background: #ccc; } .footer { grid-area: footer; background: #bbb; } /* 跨列/跨行 */ .grid-item { grid-column: 1 / 3; /* 从第1条网格线到第3条网格线(占2列) */ grid-row: 1 / 2; /* 占1行 */ }<!-- 等宽响应式卡片(经典场景) --> <style> .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); /* ↑ 每列最少250px,不足则换行,自动填充 */ gap: 20px; } .card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } </style> <div class="card-grid"> <div class="card">卡片1</div> <div class="card">卡片2</div> <div class="card">卡片3</div> <!-- 自动换行,自适应 --> </div>🎯 面试官追问
Q:grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))怎么理解?
A:
auto-fill让容器尽可能多地填充列;minmax(250px, 1fr)每列最小250px,最大占满剩余空间。这是实现纯CSS响应式卡片布局的最佳实践,无需媒体查询。
五、层叠上下文:为什么z-index死活不生效?
📝 标准答案
z-index只在同一个层叠上下文(Stacking Context)中比较才有意义。如果元素不在同一个层叠上下文中,z-index再高也没用。
触发层叠上下文的条件(高频):
| 触发方式 | 示例 |
|---|---|
根元素(<html>) | 根层叠上下文 |
position: relative/absolute/fixed+z-index不为auto | position: relative; z-index: 1 |
opacity小于 1 | opacity: 0.9 |
transform / filter / perspective不为none | transform: scale(1) |
will-change值为上述任意属性 | will-change: transform |
display: flex/grid+z-index不为auto | 子元素产生新上下文 |
层叠顺序(从底到顶):
背景和边框(
background/border)负
z-index(z-index: -1)block块级盒子(正常流)float浮动盒子inline/inline-block内联盒子定位元素(
position,z-index: auto)正
z-index(z-index: 1及以上)
💻 代码演示:常见Bug
<!-- ❌ z-index 不生效场景 --> <style> .parent { position: relative; z-index: 1; /* ⚠️ parent触发了层叠上下文 */ } .child-1 { position: absolute; z-index: 999; /* 在parent内部,只跟parent内的兄弟比较 */ background: red; } .sibling { position: relative; z-index: 2; /* 与parent同层级,z-index:2 > parent的1 */ background: blue; } /* child-1 永远无法盖住 sibling,因为child-1在parent内部 */ </style><!-- ✅ 解决方案:提升parent的z-index或调整结构 --> <style> .parent { position: relative; z-index: 3; /* 大于sibling的2即可 */ } .child-1 { position: absolute; z-index: 999; } .sibling { position: relative; z-index: 2; } </style>🎯 面试官追问
Q:opacity: 0.99和opacity: 1哪个触发层叠上下文?
A:
opacity: 0.99(小于1)触发;opacity: 1不触发。这是一个常见面试坑。
六、CSS选择器权重计算,一道题摸清你的底子
📝 标准答案
权重从高到低排列(!important 最强,但严禁滥用):
| 等级 | 权重值 | 选择器示例 |
|---|---|---|
| !important | ∞(最高) | color: red !important; |
| 内联样式 | 1000 | style="color: red;" |
| ID选择器 | 0100 | #header |
| 类/伪类/属性 | 0010 | .nav、:hover、[type="text"] |
| 标签/伪元素 | 0001 | div、::before |
| 通配符/组合器 | 0000 | *、>、+、~ |
计算规则:把三个数字组合(a-b-c),从左到右比较,不进位(即11个标签选择器(0-0-11)权重仍小于1个类选择器(0-1-0))。
💻 代码演示
/* 计算以下选择器权重 */ #nav .list a:hover /* [1 ID] [1 类] [1 伪类] [1 标签] = 0-2-1 → 权重 21 */ body .container div /* [1 标签] [1 类] [1 标签] = 0-1-2 → 权重 12 */ #header .menu li a /* [1 ID] [1 类] [2 标签] = 1-1-2 → 权重 112 */ .style /* [1 类] = 0-1-0 → 权重 10 */
<!-- 面试经典题型 --> <style> #id .class { color: red; } /* 权重: 1-1-0 = 110 */ .class .child { color: blue; } /* 权重: 0-2-0 = 20 */ div .child { color: green; } /* 权重: 0-1-1 = 11 */ /* red 胜出 ✅ */ </style> <div id="id"> <div class="class"> <span class="child">这是什么颜色?</span> <!-- 红色 --> </div> </div>/* ❌ 史上最蠢面试坑:!important 泛滥 */ .button { color: blue !important; } .btn-primary { color: red !important; /* 后来者居上,红色 */ } /* 正确做法:提升特异性,不用 !important */🎯 面试官追问
Q:11个标签选择器div div div...和 1个类选择器.cls谁权重高?
A:
.cls权重(0-1-0 = 10)高于 11个标签(0-0-11 = 11),因为权重是三位独立比较,不进位。这也是为什么(0-1-0) > (0-0-11)。
Q:!important能互相覆盖吗?
A:能。如果有多个
!important,则按选择器权重再次比较(内联>ID>类>标签),如果再相同则按后声明覆盖。
七、伪类 vs 伪元素::和::你真的分清楚了吗?
📝 标准答案
伪类(Pseudo-classes):单冒号
:。表示元素的状态(如:hover、:focus、:nth-child),不创建新元素。伪元素(Pseudo-elements):双冒号
::(CSS3规范,CSS2用单冒号兼容)。表示元素的特定部分(如::before、::after、::first-line),创建了一个虚拟DOM节点(在文档树中不存在)。
💻 代码演示
/* ✅ 伪类:状态变化 */ a:hover { color: red; } input:focus { border-color: blue; } li:nth-child(2n) { background: #f5f5f5; } /* 偶数行 */ /* ✅ 伪元素:生成内容(必须有 content) */ .clearfix::after { content: ''; display: block; clear: both; } .quote::before { content: '「'; color: #999; font-size: 24px; } .quote::after { content: '」'; color: #999; font-size: 24px; } /* 首字母下沉 */ p::first-letter { font-size: 300%; float: left; color: #d32f2f; }🎯 面试官追问
Q:::before和::after创建的伪元素可以点击吗?
A:默认情况下不可以,因为伪元素是渲染层而非真实DOM,不响应鼠标事件。但可以设置
pointer-events: auto让其响应,或者将事件绑定到父元素上。
Q:为什么清除浮动用::after而不是::before?
A:因为浮动元素在文档流中脱离了常规流,
::after在元素内容的末尾生成一个块级空元素并clear: both,相当于在最后加了一个撑开父元素的“清洁工”。::before在开头生成,无法清除后面浮动元素的影响。
八、移动端适配:rem、vw、flexible 方案全解析
📝 标准答案
移动端适配的核心是让页面在不同屏幕尺寸下都能正常显示。
| 方案 | 原理 | 优缺点 |
|---|---|---|
| rem | 相对根元素(html)字体大小 | 需JS动态计算根字体,兼容性好 |
| vw/vh | 相对视口宽度/高度(1vw=1%视口宽) | 纯CSS,无需JS,但单位换算麻烦 |
| 百分比 | 相对父元素 | 只能解决宽高,无法处理字体 |
| flexible(手淘方案) | rem + viewport缩放,完美还原设计稿 | 需引入阿里lib-flexible,已过时 |
| 容器查询(新) | 相对父容器尺寸 | 2023新特性,兼容性逐步完善 |
💻 代码演示:rem 适配方案(主流)
// 方案:根据设计稿宽度动态设置根字体大小(设计稿以750px为例) (function flexible() { const baseWidth = 750; // 设计稿宽度 const baseFontSize = 100; // 1rem = 100px(方便换算) function setRem() { const clientWidth = document.documentElement.clientWidth || window.innerWidth; // 限制最大宽度(防止PC端过大) const width = Math.min(clientWidth, 750); const fontSize = (width / baseWidth) * baseFontSize; document.documentElement.style.fontSize = fontSize + 'px'; } setRem(); window.addEventListener('resize', setRem); window.addEventListener('pageshow', (e) => { if (e.persisted) setRem(); }); })();/* CSS中使用rem */ /* 设计稿中一个按钮宽200px,高80px,字体28px */ .btn { width: 2rem; /* 200/100 = 2rem */ height: 0.8rem; /* 80/100 = 0.8rem */ font-size: 0.28rem; /* 28/100 = 0.28rem */ border-radius: 0.08rem; }/* ✅ 纯CSS vw方案(无需JS,强烈推荐) */ html { font-size: calc(100vw / 7.5); /* 设计稿750px下,1rem = 100vw/7.5 = 100px */ /* 实际屏幕375px下,1rem = 50px */ } /* 如果设计稿是375px,则用 calc(100vw / 3.75) *//* ✅ 现代方案:clamp() 限制最大最小值 */ html { font-size: clamp(12px, calc(100vw / 7.5), 24px); /* 最小12px,最大24px,中间弹性变化 */ }🎯 面试官追问
Q:1px边框在移动端变粗的问题怎么解决?
A:使用
transform: scale(0.5)缩放或使用border-image。推荐::after+transform方式:css
复制
下载
.border-1px::after { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #ccc; transform: scaleY(0.5); transform-origin: 0 0; }
九、CSS动画性能优化:为什么动画总是掉帧?
📝 标准答案
浏览器渲染流水线:JS > Style > Layout > Paint > Composite。
触发Layout(回流):改变宽高、位置、字体等 →代价最高(16ms内完不成就掉帧)。
触发Paint(重绘):改变颜色、背景、阴影等 →代价中等。
触发Composite(合成):改变
transform、opacity→代价最低(GPU加速,不触发Layout/Paint)。
性能优化核心原则:优先使用transform+opacity做动画。
💻 代码演示
/* ❌ 性能杀手:改变width/height/left/top(触发回流) */ .box-bad { transition: width 0.3s, top 0.3s; } .box-bad:hover { width: 300px; top: 50px; /* 每次变化都会触发布局计算 → 掉帧卡顿 */ } /* ✅ 性能王者:transform + opacity(走合成器) */ .box-good { transition: transform 0.3s, opacity 0.3s; will-change: transform; /* 提前告知浏览器创建独立图层 */ } .box-good:hover { transform: translateY(50px) scale(1.05); opacity: 0.8; }/* 纯CSS实现平滑呼吸灯动画 */ @keyframes breathe { 0%, 100% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1.15); opacity: 1; } } .breath { animation: breathe 2s ease-in-out infinite; will-change: transform, opacity; }🎯 面试官追问
Q:will-change是不是越多越好?
A:大错特错!
will-change会让浏览器为元素创建独立合成层,占用GPU内存。滥用会导致内存飙升,反而更卡。只在动画即将发生前设置,动画结束后移除。
Q:如何用 Chrome DevTools 查看动画性能?
A:Performance面板录制动画过程,看FPS 曲线是否掉到60以下;Layers面板查看合成层数量(过多需优化)。
十、经典布局手写:水平垂直居中的N种姿势
📝 标准答案
水平垂直居中是面试手写代码最高频题,没有之一。以下给出5种方案,面试时说出3种即可碾压。
💻 方案一:Flex(最推荐,一行搞定)
.parent { display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ width: 400px; height: 300px; background: #f0f0f0; } .child { width: 100px; height: 80px; background: #d32f2f; }💻 方案二:Grid(同样简洁)
.parent { display: grid; place-items: center; /* 水平和垂直同时居中 */ width: 400px; height: 300px; background: #f0f0f0; } .child { width: 100px; height: 80px; background: #1976d2; }💻 方案三:绝对定位 + Transform(万能兼容)
.parent { position: relative; width: 400px; height: 300px; background: #f0f0f0; } .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100px; height: 80px; background: #2e7d32; }💻 方案四:绝对定位 + margin: auto(经典)
.parent { position: relative; width: 400px; height: 300px; background: #f0f0f0; } .child { position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto; width: 100px; height: 80px; background: #ed6c02; }💻 方案五:table-cell(兼容IE8+,移动端少用)
.parent { display: table-cell; vertical-align: middle; /* 垂直居中 */ text-align: center; /* 水平居中 */ width: 400px; height: 300px; background: #f0f0f0; } .child { display: inline-block; width: 100px; height: 80px; background: #9c27b0; }🎯 面试官追问
Q:如果子元素宽高不确定(由内容撑开),哪种方案最合适?
A:方案一(Flex)和方案三(absolute + transform)都完美支持。方案四(margin:auto)需要明确宽高,不适合。
Q:父元素宽高也不确定时怎么办?
A:直接上 Flex 或 Grid,
place-items: center无需知道任何尺寸。
📌 总结与记忆口诀
| 题号 | 核心考点 | 一句话记忆 |
|---|---|---|
| 1 | 盒模型 | border-box防撑大,全局重置最省心 |
| 2 | BFC | overflow/float/flex触发,清除浮动防塌陷 |
| 3 | Flex | flex:1= 放大+缩小+基点0,主轴交叉轴要分清 |
| 4 | Grid | 二维布局,repeat(auto-fill, minmax())响应式神器 |
| 5 | 层叠上下文 | z-index失效看父级,opacity/transform会新建 |
| 6 | 权重 | !important > 内联 > ID > 类 > 标签,不进位 |
| 7 | 伪类/伪元素 | :状态,::虚拟,content必须有 |
| 8 | 适配 | rem算根字体,vw纯CSS,clamp限制边界 |
| 9 | 动画性能 | 优先 transform/opacity,will-change慎用 |
| 10 | 居中 | Flex/Grid一骑绝尘,transform兜底万能 |
面试前5分钟速记:盒模型 → BFC结界 → Flex/Grid布局 → 层叠上下文 → 权重计算 → 居中方案。
🔥 下篇预告
《第3章:JavaScript 基础核心考点,闭包原型必杀技》
涵盖:闭包、原型链、this指向、深浅拷贝、垃圾回收、事件循环(与浏览器篇联动)等10道JS硬核原题。
觉得有用,点赞 + 收藏 + 关注,系列持续更新不迷路!
有任何题目想补充,评论区告诉我,下篇安排!