这几年我前前后后面过的候选人没有几百也有上百了,其中一个很有意思的现象是:一听到“UI 相关”四个字,不少人立刻放松警惕,觉得无非是问几个 CSS 属性、看看你写的页面好不好看。可真到了现场,被问到层叠上下文、合成层、设计稿还原闭环,一下子就露怯了。现在包括 Gemini 在内的 AI 工具确实提高了大家背题的速度,但如果只会让 AI 给答案,没有把答案组织成自己的理解,一到变种追问还是容易翻车。UI 方向的前端面试题,其实远比很多人想的要深,它对面的是你对浏览器渲染、布局算法、性能优化和工程协作的综合理解。这篇文章把我在面试中常问的 UI 方向题目整理成一张复习地图,按基础知识、布局与设计、项目经验与优化、进阶知识四个维度展开,每个题目都给到可以直接拿去用的回答思路和踩坑经验。
1. 基础题别背概念:盒模型、层叠上下文和语义化的真实考点
1.1 盒模型:border-box 怎么用才能用对——并不只是全局 reset 一句话
面试问“说说标准盒模型和怪异盒模型”,表面考概念,实际想看你是不是真的在项目里被宽度计算坑过。标准盒模型(content-box)里,width 只表示内容区域宽度,padding 和 border 是额外叠加在 width 外面的;怪异盒模型(border-box)里,width 是内容 + padding + border 的总宽度。差别看起来就一句话,但放到多列布局里,体感完全不同。
我举个例子:一个 400px 的容器要分成三列,每列之间有 20px 间距,同时每列内部还要有 20px 的 padding。用 content-box 的话,你得做400 / 3再减间距、再减 padding 的连环计算,中间还得考虑 border 的 1px 或 2px,稍微改一个值,整行就换行。用 border-box 以后,宽度设成百分比就是视觉宽度的百分比,padding 和 border 向内挤压,几个列的宽度加起来永远等于容器宽度,改起来省心太多。
所以我的常规做法是在全局 reset 样式里统一设置:
*, *::before, *::after { box-sizing: border-box; }这段代码很多人都会写,但面试时值得多说两层:第一,不是所有元素都理应使用 border-box,跟第三方全局样式冲突时需要针对容器单独覆盖;第二,真要吃透盒模型,不能只背 width 算法,还要知道box-sizing的继承性问题,建议在 reset 里显式设置而不是靠content-box默认值。能讲出“我会用 border-box 作为默认,但它不解决所有宽度认知问题”的人,和只会抄 reset 的人,一听就不一样。
1.2 层叠上下文:为什么 z-index: 99999 还是压不住弹窗
“我设置了 z-index: 99999,为什么弹窗还是被遮住?”这道题我特别爱问。很多人第一反应是“你 z-index 还不够大”,但实际上 z-index 只在同一个层叠上下文内部比较大小。如果一个元素处于某个父级层叠上下文中,父级的堆叠层级是 1,另一个兄弟容器的堆叠层级是 2,那无论你把子元素的 z-index 设到多大,它都被锁在父级那一层里。这就是所谓的“赢者通吃”。
触发层叠上下文的属性远不止 z-index 一个。position 值为 relative/absolute/fixed/sticky 且 z-index 不为 auto、flex/grid 子项目且 z-index 不为 auto、opacity 小于 1、transform 非 none、filter 非 none、will-change 指定了这些属性,还有 backdrop-filter,都会创建层叠上下文。这个知识点特别容易踩坑,尤其在做弹窗、下拉、抽屉这类固定定位组件的时候。
我真实排过的一个案例是这样:项目里有嵌套弹窗,子弹窗总被蒙层盖住。看 DOM 结构没问题,蒙层在子弹窗后面,子弹窗 z-index 也设置了很大的值,理论不该被遮。打开 DevTools 一查,发现子弹窗的父级是一个用了 transform 属性做入场动画的包裹元素,父级已经形成了一个层叠上下文,整体堆叠层级低于蒙层。所以子弹窗内部 z-index 再大,只能在父级上下文里横跳。解决办法是把弹窗组件从动画容器中移出,或者让蒙层也挂到同一层叠上下文中。答这道题时能说清楚“层叠上下文是树形结构,z-index 是局部比较”,再顺手提一下 will-change 和动画的关联,基本就是满分回答。
1.3 语义化和可访问性:UI 开发为什么要在意“一个按钮用 button 还是 div”
很多前端觉得语义化是 SEO 的事,写 UI 时 div 一把梭,最后靠 role 和 tabindex 圆回来。这种思路在面试里很容易暴露。语义化首先是信息的结构化表达,浏览器、爬虫、读屏软件都需要通过标签结构来理解页面。header、nav、main、article、aside、footer 这些标签给内容赋予角色,也让 CSS 选择器和 JS 事件代理有了更可靠的钩子。但对 UI 开发来说,最有价值的语义化讨论是“交互控件该用什么标签”。
一个页面上的“点赞”按钮,到底是 button 还是 a?如果点击后只是触发前端逻辑,就应该是 button;如果点击后会跳转到新的 URL,允许用户在新标签页打开,就应该是 a。button 自带键盘访问和回车/Space 触发行为,a 支持链接语义和新窗口打开。区分它们不是为了遵守规范,而是为了避免真实浏览器里的行为错乱。
面到可访问性时,我一般还会追问焦点管理和 aria。做弹窗、抽屉、Toast 这类组件时,打开后焦点应该移到弹窗内部,关闭后焦点应该回到触发元素;遮罩、进度提示等非文本信息需要有文本替代。UI 开发经常被误解为只是视觉实现,但真正专业的 UI 工程师会把焦点管理和读屏适配纳入验收标准,面试时主动说出这一点,会明显区别于只会切图的候选人。
2. 布局题看功力:Flex 与 Grid 的选型逻辑和设计稿还原闭环
2.1 flex: 1 到底展开成什么?——flex-basis 是很多人没想透的点
“flex: 1 等价于什么?flex: auto 和 flex: 1 有什么不同?”这是布局题里出现率极高的一道。flex: 1 是flex-grow: 1、flex-shrink: 1、flex-basis: 0%的缩写。很多同学知道 flex: 1 可以让子项等分剩余空间,但追问到 flex-basis 0% 和 auto 的区别,就开始卡壳。
flex-basis 决定了项目在空间分配前的初始主轴尺寸。auto 表示取项目自身的宽高属性或内容尺寸;0% 表示初始尺寸为零,空间全部由 grow/shrink 决定。所以 flex: 1(basis: 0%)的行为是“所有子项从同一起跑线瓜分容器空间”,而 flex: auto(basis: auto)则是“先按各自内容尺寸排布,再分配剩余空间”。实际场景中,图片加文字混合的列表用 flex: auto 更容易保持图片原始宽度不被过度挤压;而等分导航、标签栏这类需求,用 flex: 1 才精确。
还有一个高频附加考点是min-width: 0。flex 子项默认min-width: auto,内容最小宽度不能被压缩,超长文本或长 URL 会把 flex 容器撑破。给子项设min-width: 0后,项目才能真的被压缩到比内容更窄。处理表格单元格、英文单词换行时尤其常见,可以说这是 flex 布局里最隐蔽的 Bug。面试时如果能举出一个真实列表结构,现场说“这里我用 flex: 1 而不是 flex: auto,是因为需要严格等分,同时给子项补了 min-width: 0 防止长文本溢出”,比单纯背缩写含义有效太多。
2.2 Grid 和 Flex 的选型:先分清一维和二维,再说谁取代谁
“你是如何选择 Flex 还是 Grid 的?Grid 会不会取代 Flex?”答案是:不存在谁取代谁。Flex 擅长一维布局,关注单个方向上的分配与对齐;Grid 擅长二维布局,可以同时控制行和列。一个常见误区是看到“看起来像个网格”就上 Grid,其实很多只是简单的横向排列,用 flex-wrap 更顺手。
我的选型判断大致是这样:顶部导航加左侧菜单加右侧内容区这种典型后台布局,用 Grid 定义整体骨架很舒服,几个区域用grid-template-columns和grid-template-rows一写,比三层嵌套 flex 干净很多;而一组按钮、标签、卡片内部的排列,Flex 足够且心智负担小。换句话说,Grid 负责页面宏观布局,Flex 负责组件内部微观排列。用表格整理的话大概是这样的逻辑:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 页面整体骨架、复杂二维区域 | Grid | 同时控制行与列,代码更简洁 |
| 组件内部水平/垂直排列 | Flex | 一维分配逻辑简单直观 |
| 内容数量不定、需要自动换行 | Flex + flex-wrap | 更适合流式内容 |
| 有明确行列的卡片矩阵 | Grid | 行列对齐天然成立 |
还有一种问法是让你实现 12 栅格系统,用 Grid 可以这么写:
.grid-12 { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; } .col-4 { grid-column: span 4; }这比用 float 或 flex 做栅格简单很多,gap 还天然支持间距而不需要每个子项单独写 margin。但要注意 gap 会吃掉栅格的实际总宽,设计稿算宽度时要把间距考虑进去。面试时能主动说出这个细节,说明你真的在项目里写过栅格系统,而不是临时背代码。
2.3 设计稿还原:375 设计稿到多端适配的像素级方法
“拿到一个 375px 宽的设计稿,你怎么在不同宽度设备上还原?”这类题已经把范围圈定在移动端适配,但面试官想听的绝不是“用 rem”三个字。适配方案要分场景说清楚。基础是 viewport meta 标签,没有它整个适配都无从谈起。然后是单位选择:流式布局中百分比和 vw 适合宽度类属性;文字大小和间距用 rem,或者直接用 px 配合媒体查询;新的clamp()可以一个属性内同时实现下限值、期望值和上限值,例如:
.title { font-size: clamp(28px, 5vw, 40px); }这句话的意思是最小 28px,最大 40px,中间按视口宽度 5% 线性变化。面试时提到 clamp() 远比只说 rem 显得跟得上实践。
像素级还原还有一些高频细节。1px 边框在 Retina 屏幕上会显得过粗,常规做法是借助transform: scale(0.5)缩小;背景图出现模糊时,先检查设计稿的 @2x/@3x 切片是否加载;页面字体出现锯齿时,可能和系统字体栈、抗锯齿属性有关。回答时不要一次把方法全堆出来,而是说“我会先定适配基准,再单独处理 1px、大屏上限这类边界问题”,这样面试官会觉得你有完整方案,而不是零散知识点的堆叠。
3. 项目题考复盘:首屏优化、UI 卡顿定位与组件设计的回答框架
3.1 首屏优化:从 UI 侧能落地的指标和手段
“项目首屏加载很慢,从 UI 角度你能做什么?”这题进入项目经验面,面试官不满足于背优化名词,希望听到“你基于什么数据做了哪些改动,效果如何”。UI 和首屏最相关的部分是图片、字体、CSS 体积和首屏渲染。图片懒加载是见效最快的,把首屏之外的 img 换成loading="lazy",再给真实图片区域设置宽高或 aspect-ratio 占位,避免布局偏移。字体方面,中文字体文件动辄几 MB,如果只展示少量数字和英文,可以用font-display: swap配合字体子集化方案,把大字体拆成常用字符集。
骨架屏也是一个 UI 侧非常常见的优化手段,价值不只是“好看”,而是通过占位结构让用户感知加载进度,降低等待焦虑。实现时可以用纯 CSS 动画画灰色块,也可以在构建期生成和页面结构一致的图片。搭配异步组件或路由级代码分割时,骨架屏能很好地承接加载态。
回答这类题,尽量把指标带上。LCP(最大内容绘制)和 CLS(布局偏移)这两个指标和 UI 强相关。图片不设宽高、字体加载导致文字跳动、异步内容插入,都会让 CLS 变高。一个标准的回答句式是:“当时我通过 Lighthouse 测出首屏 CLS 在 0.4 左右,后来给所有图片设置了 aspect-ratio,给字体加了 fallback 和 swap,优化后降到 0.1 以下。”有数据,有手段,有效果,这才是项目经验题该有的答法。
3.2 UI 卡顿排查:用 Performance 面板把“卡”变成数据
“页面滚动很卡,或者切 Tab 卡顿,你怎么定位和解决?”这是非常贴近实战的一道题。前端侧的卡顿,几乎最终都会落到主线程任务过重。标准步骤是打开 DevTools Performance 面板录制一段操作,观察主线程的长时间任务(Long Task)、样式重计算、绘制和合成事件。凡是耗时超过 50ms 的任务,基本就能构成一次肉眼可见的卡顿。这套思路不只适合 Web,做 C# 桌面端或者 Unity UI 刷新卡顿排查时也一样,本质都是找主线程阻塞源。
常见原因之一是强制同步布局,代码经常长这样:
const height = el.offsetHeight; el.style.height = height * 2 + 'px';第一句读取布局信息,第二句修改样式,如果中间夹着大量 DOM,浏览器可能在读取时触发一次布局计算,而你在循环里反复做这件事,性能会急剧恶化。正确做法是先把一次性需要的读取收集起来,再批量写入,或者使用 requestAnimationFrame 分帧处理,这就是常说的“读写分离”。
我自己排过的一个真实案例是:数据表格每一行都绑定了动态进度条动画,滚动时肉眼可见的掉帧。用 Performance 录制后发现每次滚动都触发整列合计行的强制重算,而且表格在一个很大的 transform 容器里。后来的优化包括:表格改成虚拟滚动只渲染可视区域的行,进度条动画移到合成层执行,关闭不必要的行 hover 阴影。三步做完,从“明显卡顿”变成“完全顺手”。面试能把这个链路讲清楚,比别人背十个优化名词有效得多。
3.3 组件设计题:一个 Button 如何引出工程化全貌
“如果让你设计一个 UI 库的 Button 组件,你会考虑哪些方面?”这道题考的不是你会不会写按钮,而是有没有用工程化思维做组件。先从 API 设计说起:type、size、disabled、loading、htmlType、icon 这些属性怎么命名,要不要区分点击事件和原生事件。props 的命名会影响使用体验,也影响后续文档生成。然后是样式体系:颜色、圆角、间距、字体应该来自设计令牌,而不是在组件里硬编码。这里有个实际权衡:CSS Variables 适合运行时切换主题,Sass/Less 变量适合构建期处理,要支持动态换肤,根节点上挂--primary-color这类变量是常见做法。
组件还需要考虑状态与可访问性:loading 时要不要禁用?禁用状态下焦点怎么处理?加载中的按钮 icon 是否需要 aria-hidden?点击是否带来重复提交风险?这些细节往往能分出普通开发者和能独立负责组件库的人。最后可以提按需加载:组件样式如何实现按需引用,是否支持 tree-shaking,包体积控制在什么量级。一道 Button 题,表面不难,但能把 API、设计令牌、无障碍、构建优化串起来的人,非常少。面试时按这个结构答,对方会明显感觉到你不只是“会写页面”。
4. 进阶题拼原理:渲染流程、合成层和团队 UI 规范的底层脉络
4.1 渲染流程题:HTML、CSS 和 JS 的阻塞关系与关键渲染路径
“浏览器从拿到 HTML 到显示页面,经历了哪些步骤?CSS 和 JS 分别有什么阻塞影响?”这道题是进阶面几乎必考的。整个链路是:HTML 解析生成 DOM 树,CSS 解析生成 CSSOM 树,二者合并成渲染树,随后进行布局、分层、绘制和合成。布局关心元素的位置和尺寸,绘制关心像素填充,合成关心把不同的层叠加到屏幕上。
阻塞关系是另一个重点。CSS 是渲染阻塞资源,浏览器在构建出完整 CSSOM 之前不会渲染,所以 CSS 要尽量放在 head 里并做压缩合并;普通 script 标签会阻塞 HTML 解析,因为脚本可能修改 DOM。async 和 defer 的关键区别在于:defer 按文档顺序执行,且等 DOM 解析完再执行;async 下载完立即执行,可能阻塞解析,多个 async 脚本的顺序也不保证。能把这些资源加载行为讲清楚,说明你对性能有系统认识,而不仅仅是背了几个属性。
加分表述是:重排和重绘的成本差异,以及为什么 transform 能跳过重排重绘。重排的成本远高于重绘,重绘又比合成高得多。所以优化的总体原则是:尽量只触发合成层变化,不触碰布局和绘制。答到这里,你已经从 CSS 属性层面上升到渲染原理层面,这类人正是 UI 方向进阶面想筛选出来的。
4.2 动画性能题:为什么 transform + opacity 能跑满 60fps
“实现一个流畅的位移动画,你会怎么写?为什么推荐 transform 而不是 left/top?”动画流畅度取决于主线程是否被布局和绘制拖住。用 left/top 改变位置时,浏览器每帧都要重新计算布局、触发绘制,主线程很容易在移动端上崩帧。而 transform 可以触发合成,动画在合成线程上执行,主线程负担小,自然更接近 60fps。
比较规范的写法是:
element.animate( [ { transform: 'translateX(0)' }, { transform: 'translateX(200px)' }, ], { duration: 400, easing: 'ease-in-out' } );或者用声明式 CSS:
.box { transition: transform 0.4s ease; }如果面试只背出“用 transform”还不够,最好能解释 will-change。给元素设置will-change: transform是提前告知浏览器,然后浏览器把元素提升到独立合成层,减少动画开始时的首次提升耗时。但注意别滥用,每个独立合成层都会占用内存,图层数量过多同样会导致性能下降。移动端上给几十个元素设置 will-change,内存可能先崩。实用的做法是只在动画期间设置,结束后移除。能把这个滥用风险说出来,面试官会确认你是真的调过动画性能,而不是背书。
4.3 团队规范题:如何推动 UI 规范落地,而不是变成“只说不动”
“你们团队在做 UI 规范,但大家不执行,你作为前端怎么推动?”这类情境题考的是协作能力和系统思维。我的建议是先别急着写规范文档。很多团队规范文档写得非常完善,但没人看,因为距离实际开发太远。推动规范落地最好的方式是把规范沉淀成工具链和组件库,让正确做法成为默认选项。比如视觉间距这套,与其在文档里写“8px 基准间距”,不如设计一组 spacing 变量,并在样式代码中强制使用。
另一个可落地的点是 lint 和格式化。Stylelint 可以检查颜色、单位、命名规则,比如禁止魔法值颜色,要求使用设计变量。CSS Modules / scoped / CSS-in-JS 的选型也属于工程规范:Vue 项目通常 scoped 够用;组件库或需要动态主题时,CSS 变量加 CSS-in-JS 各有优势。选型的理由要能说清楚,而不是哪个火用哪个。
视觉走查也是团队规范的一部分。UI 开发完成后,由设计或前端负责人对关键页面做一轮像素级检查,问题通过 issue 记录,避免口头沟通过后就淹没在聊天记录里。写了规范和让规范真正长在流程里,是两件不同的事。面试时强调后者,会给面试官留下务实的印象。
5. 面试现场策略:同样一道题,怎么答出面试官想听的差异感
5.1 用 STAR 框架讲“最复杂的 UI 需求”,别把复盘讲成流水账
“你做过最复杂的 UI 需求是什么?”很多候选人从项目背景开始讲半小时,最后面试官只记住“这个项目很复杂”。想讲好这道题,先用 STAR 结构搭骨架:背景、目标、行动、结果。背景部分一两句话交代即可,重点放在行动和结果。
举一个框架示例:背景是后台系统需要支持多角色、多权限的动态菜单和配置页面;任务是把权限数据和 UI 结构解耦,让非前端也能配置页面;行动是你做了路由配置映射、动态渲染组件、建立权限指令与按钮级控制,并处理了异步权限与页面缓存的冲突;结果是配置周期从平均 3 天降到 2 小时,UI 相关 Bug 数量下降 60%。这里的关键是数字,最好是你真实做过并能应对追问的。
不要记流水账的另一个技巧是始终围绕“UI/前端”展开。哪怕项目整体架构很有亮点,也要说明你的具体工作在哪部分,避免让面试官觉得你只是全程参与但说不清自己的贡献。
5.2 被追问到盲区时的三层缓冲:知道、推测、求证
“如果面试官问到一个你确实不知道的问题,怎么办?”这个问题本身几乎必被问到,但实际上考察的是现场行为。第一层:把你已经知道的相关知识快速组织出来。比如问 GPU 合成原理,你不知道细节,但你知道合成发生在主线程外,知道 Layer 和合成树,先把这部分说出来。第二层:基于已知做推断,并明确说明“这是我基于已知知识的推测”。比如猜 transform 会触发独立合成层,所以动画不占用主线程,这个推断基本正确。第三层:坦诚未知,并当场向面试官请教实际场景里的做法,或者提出“我会回去查一下,用一个小 Demo 验证后补充答案”。
这个策略的价值在于,它把“我不会”变成了“我能用已有知识逼近答案,而且知道如何获取正确信息”。面试官很少要求候选人是百科全书,但很看重遇到未知问题时的反应和思路。UI 领域尤其如此,因为 CSS 诡异行为太多,谁都不可能全部记牢,能快速定位并验证才是核心能力。面试时把这个方法自然地用出来,远比支支吾吾或者硬编一个答案体面得多。
我自己面试这么多人的一个体会是:很多看起来基础的 UI 面试题,恰恰是区分层级的试金石。同样问盒模型,初级背定义,中级说用法,高级会扯到布局策略和标准化配置。准备这些题最好的方式不是刷题,而是找自己项目里真实踩过的 UI 问题,把根因、排查过程和结果写成两三段文字,面试时用自己的话讲出来。这样的回答天然带着你个人的技术判断,和背来的答案一对比就高下立判。如果你正在准备面试,不妨先把上面的问题挨个过一遍,每个都想着怎么跟自己的项目经验结合,练上几天,效果会很不一样。