面试前得搞定这堆CSS问题,到底哪些才是绕不开的硬骨头?

6,473字
27–41 分钟
in

不少朋友准备前端面试时,面对五花八门的CSS问题,心里总会犯嘀咕:感觉啥都可能问,但又不知道重点该扑在哪儿。其实吧,从这些年摸爬滚打的经验来看,虽然HTML、CSS、JavaScript常常打包出现,但CSS这块儿,总有那么十来个核心问题,反反复复出现,像钉子户一样牢牢焊在面试官的题库里。这篇文章就把这些高频硬骨头扒拉出来,配上能直接上手操作的流程,争取把这块儿整得明明白白,面试时心里不慌。

目录

响应式网站到底咋搭?

聊到响应式,这事儿说白了就是让网页在不同设备上,都能有个得体的样子。手机上看不挤,大屏上看不空,排版和图片能自动适应。

基础三板斧

开干之前,得先备好几样基础工具。相对单位(比如%emrem)是根基,能让尺寸随父级或根元素变化。媒体查询则是断点设置的关键,告诉页面在什么宽度下换什么造型。流体布局确保内容容器能伸缩自如。通常大伙儿习惯移动端优先,先写好手机上的样式,再通过媒体查询往大屏扩展,这样逻辑更顺,性能也更佳。

/* 先搞定全局容器,给个大屏上限,居中显示 */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 16px;
}

/* 图片得乖一点,别撑破自家门 */
img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* 等手机小屏登场,再把间距调一调 */
@media (max-width: 600px) {
  .container {
    padding: 8px;
  }
  /* 导航栏也换换姿势,比如竖着排 */
  nav ul {
    flex-direction: column;
  }
}

动手搭的时候,导航菜单在小屏上怎么处理(比如折叠起来)、图片怎么用srcset按需加载,都是加分项。弄完后,记得打开浏览器开发者工具里的设备模拟,翻来覆去地拖拽窗口大小,看看各个断点下的表现是不是都服帖。

预处理器为啥是项目常客?

以前写大项目CSS,最怕代码满天飞,改个全局颜色得翻山越岭。预处理器(像Sass、Less)就是为了解决这事儿来的。它给CSS加了变量、嵌套、混合宏这些好使的玩意儿,让代码复用和修改都轻松不少。

一个Sass实战小片段

假设要统一管理主题色,并且做一个通用的按钮样式,流程可以这样走:先定义好颜色变量,用@mixin写一个通用的阴影效果,再用@function算出间距值。之后用%btn-base定义基础按钮样式,最后通过@extend继承,配合变量和混合宏,生成最终的按钮样式。这样以后想改主题色,只需要修改变量值,所有按钮颜色会一次性更新。

// 定义主题色变量
$primary: #007bff;
$secondary: #6c757d;
$font-size-lg: 1.25rem;

// 混合宏,用来复用阴影效果
@mixin shadow($opacity: 0.12) {
  box-shadow: 0 2px 8px 0 rgba(24, 39, 75, $opacity);
}

// 函数,用来计算间距值
@function space($multiplier: 1) {
  @return $multiplier * 8px;
}

// 占位符,定义基础按钮样式
%btn-base {
  display: inline-block;
  font-size: $font-size-lg;
  border-radius: 6px;
  text-align: center;
  cursor: pointer;
}

// 实际使用的按钮样式
.button {
  @extend %btn-base;
  background: $primary;
  color: #fff;
  padding: space(1.5) space(3);
  @include shadow(0.15);

  &:hover {
    background: lighten($primary, 10%);
  }

  &.secondary {
    background: $secondary;
    color: #23272f;
    border: 2px solid $secondary;
  }

  @media (max-width: 600px) {
    padding: space(1) space(2);
    font-size: 1rem;
  }
}

虽然现在原生CSS也支持变量和嵌套了,但在实际工程里,预处理器的成熟生态和强大功能,比如代码文件拆分、自动添加浏览器前缀,还是让它成为很多团队的标配。

文字咋跟着屏幕灵活缩放?

字体大小这事儿,搞不好就是“车祸现场”:手机上看字小得费眼,大屏上看字又大得愣头愣脑。关键在于不用绝对单位px,转而拥抱相对单位,让文字能根据屏幕尺寸和用户设置自适应。

几种流体字体的做法

最稳当的做法是设置根元素的font-size,然后用rem给各级标题和正文。更现代的做法是用clamp()函数,一下子搞定最小、最大和中间值,让文字平滑缩放。还可以配合媒体查询,在特定宽度下“咔嚓”一下切到另一档大小。

/* 根元素字体大小,方便后续用rem调整 */
html {
  font-size: 100%; /* 默认16px,也尊重用户浏览器设置 */
}

body {
  font-size: 1rem; /* 随根元素变化 */
}

h1 {
  font-size: 2.5rem; /* 大标题用rem */
  line-height: 1.2;
}

/* 用clamp()实现流体字,既有下限又有上限 */
h2 {
  font-size: clamp(1.5rem, 4vw, 3rem);
}

/* 直接用视口宽度单位,不过得小心极端情况 */
h3 {
  font-size: 6vw;
}

/* 配合媒体查询,在屏幕变大时微调正文大小 */
p {
  font-size: 1rem;
}

@media (min-width: 600px) {
  p {
    font-size: 1.2rem;
  }
}

写的时候得留个心:em单位是相对父元素的,嵌套多了容易乱套,rem相对根元素更可控。另外,不能因为想追求效果就忽略了那些需要放大字体的特殊用户,绝对单位px会阻止浏览器缩放文字,尽量避免用在正文上。

z-index 失灵了?得查查堆叠上下文

有时候明明给元素设了很高的z-index,它就是死活不上来,躲在别的元素屁股后头。这通常是因为堆叠上下文在作祟。z-index只对设置了position属性(relative、absolute、fixed等)的元素有效,而一个新堆叠上下文的诞生,会把它的子元素圈起来,让子元素只在这个圈子里比大小。

堆叠上下文排查指南

假设页面上有个弹窗(.modal)怎么也盖不住遮罩层(.overlay)。先检查弹窗是否设置了position,如果没有,z-index就是摆设。如果有了,再看它的父级元素有没有触发新堆叠上下文,比如父级设置了opacity小于1、transformfilter,或者positionz-index。一旦父级创建了新上下文,子元素的z-index就只在这个父级内部有效,外面比它大的也压不住它。

/* 这个父元素自己创建了一个堆叠上下文 */
.parent {
  position: relative;
  z-index: 2;
  background: #b3e6fc;
}

/* 子元素虽z-index高,但只限父元素内部排序 */
.child {
  position: absolute;
  z-index: 10;
  background: #4f46e5;
}

/* 这个兄弟元素z-index虽小,但和.parent比 */
.sibling {
  position: relative;
  z-index: 1;
  background: #fca311;
}

在实际项目中,遇到z-index失效,最直接的办法就是打开浏览器开发者工具,在Elements面板里找到问题元素,看它的堆叠上下文(Computed样式里可以查到)。沿着DOM树往上找,直到发现那个“肇事”的父级,调整它的属性或者重构结构,让弹窗跳出这个上下文。

display 的 block、inline、inline-block 傻傻分不清?

这三个家伙决定了元素在文档流里的“排班表”。block是“霸道总裁”,自己占一整行,宽高随便调。inline是“社恐人士”,只占自己内容那么宽,排在一行里,宽高调不动。inline-block则是个“两面派”,既能和兄弟排排坐,又能有自己的宽高。

显示值独自占行可设宽高常见元素
blockdiv, p, h1
inlinespan, a, strong
inline-block自定义按钮、图标

选型指南

比如要做一组导航链接,想让它们横着排,但又可以分别控制内边距和背景色,那inline-block就是正解。如果用inline,背景色和大小就不好控制。如果用block,它们就会上下排列,不能保持一行。

box-sizing: border-box 凭啥成了最佳实践?

这事儿得从盒子模型说起。默认的content-box模型下,给元素设个width: 200px,再添padding: 20px,实际占位就变成了240px。这在布局时特别容易算崩,尤其是百分比宽度时,加个内边距就挤到下一行去了。border-box彻底解决了这个痛点,让widthheight直接包含了内边距和边框。

全局应用流程

在项目里,通常会在根元素上直接统一设置,让所有元素都继承这个模型。这样,之后任何元素的宽高设定都变得非常直观。

/* 一个通配规则,让所有元素都用border-box */
*,
*::before,
*::after {
  box-sizing: border-box;
}

这么一设,比如再写.box { width: 200px; padding: 20px; border: 4px solid; },它的总宽度就是稳稳的200px,内边距和边框会自动从内容区域里“扣”出来。这让响应式布局里的百分比宽度计算瞬间清爽不少,不用再心算加加减减了。

图片响应式,不光是设个 max-width 那么简单

让图片响应式,最基础的当然是img { max-width: 100%; height: auto; },保证图片不超出容器,并且比例不变。但面对复杂的场景,比如要精准控制长宽比,或者在不同屏幕上加载不同分辨率的图片,就得来点高级操作了。

给图片上个“紧箍咒”——固定长宽比

有时需要把图片卡在一个固定的比例里,比如视频封面的16:9。老法子是用padding-bottom技巧,通过给父容器一个相对定位和百分比内边距来撑出比例,再把图片绝对定位填进去。现在的新玩法是直接用aspect-ratio属性,一句话搞定。

/* 老派但兼容性好的做法,手动制造一个16:9的容器 */
.image-container {
  position: relative;
  width: 100%;
  padding-bottom: 56.25%; /* 9/16 = 0.5625 */
  overflow: hidden;
}

.image-container img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover; /* 图片按比例裁剪填满 */
}

/* 现代CSS的优雅做法 */
.modern-image {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
  display: block;
}

除了CSS控制,还要记得在HTML里用srcsetsizes属性,告诉浏览器在不同宽度下该加载哪张图。性能这块儿也得盯着,特别是移动端网络,不能给手机加载一张几兆的4K大图。

CSS 性能咋优化,让页面丝滑起来?

CSS性能优化,说白了就是让浏览器能更快地解析样式,减少卡顿。这事儿可以从几个方向下手。

给样式“减肥”与“分流”

先得看看项目里有没有一堆压根没用的样式。可以用PurgeCSS这类工具,扫描HTML和JS,把没用到的类名从CSS里剔除,打包后的文件能瘦一大圈。接着,把样式按页面或组件拆分,用动态import()实现按需加载,这样用户打开首页时只加载必要的CSS,首屏渲染自然就快了。

选择器别写“天书”

浏览器解析CSS是从右往左的,像header nav ul li a.active这种,浏览器得先找所有.active,再回溯一堆祖先元素,效率很低。换成简洁的类名,比如.nav-link-active,匹配效率会高得多。

/* 复杂选择器,效率低 */
.header .navigation .menu-list .menu-item .menu-link.active {
  color: red;
}

/* 简化后的写法 */
.nav-link-active {
  color: red;
}

还有一些属性,比如box-shadowfilter,在动画里用多了会导致重绘,能免则免。动画尽量用transformopacity,它们能被浏览器单独放在一个图层里,跑起来更顺滑。平时多用浏览器开发者工具的Coverage面板,看看哪些样式没被用上,定期清理。

CSS-in-JS 还是传统 CSS 文件,到底咋选?

这问题在面试里特别容易引战。其实不用站队,把两边的优缺点掰扯清楚,再说说实际项目里怎么组合用,就能显得很稳。

方案优点缺点
CSS-in-JS样式与组件绑定,避免冲突;动态样式强,能基于props轻易改变。运行时消耗,增大了JS包体积;服务端渲染时可能闪一下无样式内容。
外部CSS导入浏览器能并行加载,渲染快;文件易缓存,利于大型项目优化。全局样式容易打架;基于状态动态改样式麻烦些。

实战混搭风

在实际的项目里,可以搞个混合策略。全局的、通用的,比如重置样式、主题变量、布局框架,统统放在传统的CSS文件里,利用浏览器缓存机制。而具体到某个组件,需要精细控制、或者样式与业务状态强相关(比如按钮加载中的状态、选中的样式),就用CSS-in-JS。这么做既能享受CSS-in-JS的封装便利,又不会丢掉全局样式的高性能和可维护性。

碰上“圣杯布局”这类复杂排版,怎么下手?

面试官甩过来一个布局需求,比如经典的“圣杯布局”(页眉、页脚固定,中间三栏,左右两栏宽固定,中间自适应),别慌。关键在于展示解决问题的思路,而不是追求唯一解。

用 Grid 搭建三栏布局的丝滑流程

思路先摆正:用CSS Grid是最简单清晰的。第一步,在父容器上定义网格,分成三列:左边栏固定宽度、中间栏自适应、右边栏固定宽度。第二步,用grid-template-areas给每个区域起个好记的名字。第三步,把页眉、中间区域、页脚放到对应的网格里。整个过程不需要任何浮动或清除浮动。

<div class="page">
  <header class="header">页眉</header>
  <nav class="left-nav">左侧导航</nav>
  <main class="main-content">主要内容区,随着屏幕宽度缩放</main>
  <aside class="right-sidebar">右侧栏</aside>
  <footer class="footer">页脚</footer>
</div>
.page {
  display: grid;
  grid-template-columns: 200px 1fr 200px; /* 左固定、中自适应、右固定 */
  grid-template-rows: auto 1fr auto;       /* 页眉、内容区、页脚,内容区占满剩余高度 */
  min-height: 100vh;
  gap: 1rem;
}

.header {
  grid-column: 1 / -1; /* 页眉跨三列 */
}

.left-nav {
  grid-column: 1 / 2;
}

.main-content {
  grid-column: 2 / 3;
}

.right-sidebar {
  grid-column: 3 / 4;
}

.footer {
  grid-column: 1 / -1; /* 页脚跨三列 */
}

边写边跟面试官唠,说清楚Grid如何通过grid-template-columns中的1fr单位实现中间栏的自适应,auto1fr怎么配合让中间内容区撑开页脚。这样做不仅展示了技术能力,也体现了清晰的逻辑。