CSS 高频面试题

12 阅读54分钟

CSS 高频面试题

1. 盒模型是什么?标准盒模型和 IE 盒模型的区别?

盒模型(Box Model) 是 CSS 中最基础的概念之一。每个 HTML 元素都可以看作一个矩形的盒子,由 4 层组成:content(内容)→ padding(内边距)→ border(边框)→ margin(外边距)。

两种盒模型的核心区别在于 width 属性包含的范围不同:

盒模型box-sizing 值width 包含实际占用宽度
标准盒模型(W3C)content-box(默认)仅 contentwidth + padding×2 + border×2
IE 盒模型(怪异盒模型)border-boxcontent + padding + border就是 width
/* ✅ 标准盒模型:width = content */
.standard {
  box-sizing: content-box; /* 默认值 */
  width: 200px;
  padding: 20px;
  border: 5px solid #000;
  /* 实际占用宽度:200 + 20×2 + 5×2 = 250px */
  /* content 区域宽度 = 200px */
}

/* ✅ IE 盒模型:width = content + padding + border */
.ie-box {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid #000;
  /* 实际占用宽度:200px */
  /* content 区域宽度 = 200 - 20×2 - 5×2 = 150px */
}

/* ✅ 推荐全局设置为 border-box(现代项目标配) */
*, *::before, *::after {
  box-sizing: border-box;
}

为什么推荐 border-box?

  • 设置 width: 200px 就是最终宽度 200px,不用再做加法
  • 修改 padding 和 border 不会改变元素的最终尺寸,布局更可控
  • Bootstrap、Tailwind 等主流框架都默认使用 border-box

💡 面试加分点: margin 不属于任何一种盒模型的 width 计算范围,但会影响元素的"占位空间"。可以通过 getComputedStyle(el) 获取元素的计算样式,或通过 el.offsetWidth(包含 padding + border)和 el.clientWidth(包含 padding,不含 border)来获取元素的实际宽度。


2. BFC(块级格式化上下文)是什么?如何触发?

BFC(Block Formatting Context) 是一个独立的渲染区域。BFC 内部的布局与外部完全隔离,互不影响。理解 BFC 是解决很多 CSS 布局"怪问题"的关键。

BFC 的核心特性:

  1. 内部的块级元素从上到下依次排列
  2. 同一个 BFC 内的相邻块级元素会发生 margin 折叠
  3. BFC 的区域不会与浮动元素重叠
  4. BFC 是一个隔离容器,内部元素不影响外部
  5. 计算 BFC 的高度时,浮动子元素也参与计算(关键!)

触发 BFC 的方式(任一即可):

属性值副作用
overflowhidden / auto / scroll可能裁剪内容
displayflex / grid / inline-block / table-cell / flow-root改变布局模式
positionabsolute / fixed脱离文档流
floatleft / right脱离文档流
displayflow-root(推荐 ✅)无副作用
/* ✅ 场景1:清除浮动(解决父元素高度塌陷) */
/* 问题:父元素没有高度,因为浮动子元素脱离了文档流 */
.parent {
  overflow: hidden; /* 触发 BFC,使浮动子元素参与高度计算 */
}
/* 或者用更语义化的方式(推荐) */
.parent {
  display: flow-root; /* 专门为触发 BFC 设计,无副作用 */
}
.child { float: left; }

/* ✅ 场景2:防止 margin 折叠 */
/* 问题:两个相邻 div 的 margin-top 和 margin-bottom 会合并 */
.box1 { margin-bottom: 20px; }
.box2 { margin-top: 30px; }
/* 结果:两者间距是 30px 而不是 50px(取较大值) */

/* 解决:让其中一个在新的 BFC 中 */
.box2-wrapper { overflow: hidden; } /* 包一层,触发新 BFC */

/* ✅ 场景3:自适应两栏布局 */
.left { float: left; width: 200px; background: #f0f0f0; }
.right { overflow: hidden; background: #e0e0e0; }
/* right 触发 BFC 后,不会与左侧浮动元素重叠,自动填充剩余空间 */

💡 面试加分点: display: flow-root 是 CSS 中专门为创建 BFC 而设计的属性值,没有任何布局副作用,是目前最推荐的触发 BFC 方式。此外,BFC 只解决块级方向(垂直方向)的 margin 折叠,行内方向的 margin 不会折叠。


3. Flex 布局常用属性有哪些?

Flexbox(弹性盒子布局) 是一维布局模型,主要用于在一个方向(水平或垂直)上排列元素。它极大简化了对齐、分布和排序的复杂度。

核心概念: 容器(flex container)和子项(flex item),以及两根轴:主轴(main axis) 和 交叉轴(cross axis)。

/* ========== 容器属性 ========== */
.container {
  display: flex;

  /* 主轴方向 */
  flex-direction: row;            /* 默认:水平从左到右 */
  /* row | row-reverse | column | column-reverse */

  /* 是否换行 */
  flex-wrap: nowrap;              /* 默认:不换行 */
  /* nowrap | wrap | wrap-reverse */

  /* 主轴对齐(分配剩余空间) */
  justify-content: flex-start;    /* 默认:靠主轴起点 */
  /* flex-start | flex-end | center | space-between | space-around | space-evenly */

  /* 交叉轴对齐(单行) */
  align-items: stretch;           /* 默认:拉伸填满容器高度 */
  /* stretch | flex-start | flex-end | center | baseline */

  /* 交叉轴对齐(多行,需配合 flex-wrap: wrap) */
  align-content: flex-start;
  /* flex-start | flex-end | center | space-between | space-around | stretch */

  /* 间距(推荐,替代 margin 方案) */
  gap: 10px 20px;                 /* 行间距 列间距 */
}

/* ========== 子项属性 ========== */
.item {
  flex: 1;           /* 简写:flex-grow flex-shrink flex-basis */
                     /* flex: 1 等价于 flex: 1 1 0% */
  flex-grow: 1;      /* 放大比例,默认 0(不放大) */
  flex-shrink: 1;    /* 缩小比例,默认 1(等比缩小) */
  flex-basis: auto;  /* 初始大小,默认 auto(使用元素自身宽/高) */
  align-self: auto;  /* 覆盖容器的 align-items */
  order: 0;          /* 排列顺序,数值越小越靠前 */
}

flex 简写的常用值:

简写等价于含义
flex: 1flex: 1 1 0%等比例分配剩余空间
flex: autoflex: 1 1 auto按内容大小分配
flex: noneflex: 0 0 auto不伸不缩,保持原始大小
flex: 0 1 auto默认值不放大,可缩小
/* ✅ 经典布局:水平垂直居中 */
.center {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* ✅ 经典布局:等分三列 */
.three-columns .item { flex: 1; }

/* ✅ 经典布局:左侧固定 + 右侧自适应 */
.sidebar { flex: 0 0 250px; }  /* 固定 250px,不伸不缩 */
.main { flex: 1; }              /* 自适应填满剩余空间 */

/* ✅ 经典布局:底部固定(Sticky Footer) */
.page { display: flex; flex-direction: column; min-height: 100vh; }
.header { flex: none; }
.content { flex: 1; }    /* 中间区域撑满 */
.footer { flex: none; }

💡 面试加分点: flex: 1 和 flex: auto 的区别——flex: 1(flex-basis: 0%)先将子项大小归零再按比例分配空间;flex: auto(flex-basis: auto)先保证子项的内容宽度,再按比例分配剩余空间。当子项内容大小不同时,两者的效果截然不同。


4. Grid 布局常用属性有哪些?

Grid(网格布局) 是二维布局模型,可以同时控制行和列。与 Flex 的一维布局不同,Grid 更适合整体页面布局和复杂网格结构。

/* ========== 容器属性 ========== */
.grid-container {
  display: grid;

  /* 定义列 */
  grid-template-columns: 200px 1fr 200px;              /* 三列:固定-自适应-固定 */
  grid-template-columns: repeat(3, 1fr);                /* 三等分 */
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* ✅ 响应式:自动填充 */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));  /* 自动适应 */

  /* 定义行 */
  grid-template-rows: auto 1fr auto;

  /* 间距 */
  gap: 20px;          /* 行列间距相同 */
  gap: 10px 20px;     /* 行间距 列间距 */

  /* 命名网格区域(直观的布局方式) */
  grid-template-areas:
    "header header  header"
    "sidebar main   main"
    "footer footer  footer";
}

/* ========== 子项属性 ========== */
.item {
  grid-column: 1 / 3;      /* 跨第1列到第3列(占2列) */
  grid-row: 1 / 2;          /* 占第1行 */
  grid-area: header;         /* 对应 grid-template-areas 中的名称 */
  justify-self: center;      /* 单个元素水平对齐 */
  align-self: center;        /* 单个元素垂直对齐 */
}

auto-fill vs auto-fit 的区别:

关键字行为适用场景
auto-fill尽可能多地创建列,即使列是空的固定列宽的网格
auto-fit创建的列会自动拉伸填满剩余空间响应式卡片布局
/* ✅ 经典布局:圣杯布局(Header + Sidebar + Main + Footer) */
.holy-grail {
  display: grid;
  grid-template-areas:
    "header header header"
    "nav    main   aside"
    "footer footer footer";
  grid-template-columns: 200px 1fr 150px;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}
.header  { grid-area: header; }
.nav     { grid-area: nav; }
.main    { grid-area: main; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

/* ✅ 经典布局:瀑布流 */
.masonry {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 10px; /* 最小行高 */
  gap: 10px;
}
.masonry-item { grid-row: span 20; } /* 每个元素占不同行数 */

/* ✅ 单行居中(最简写法) */
.center { display: grid; place-items: center; }

💡 面试加分点: Flex 和 Grid 的选择——一维用 Flex,二维用 Grid。Flex 擅长处理单行/单列中元素的对齐和分布(如导航栏、工具栏);Grid 擅长整体页面布局和需要同时控制行列的场景(如仪表盘、卡片网格)。实际项目中两者经常配合使用。


5. CSS 选择器优先级(权重)是怎么计算的?

CSS 选择器优先级决定了当多个规则应用于同一元素时,哪个规则生效。优先级使用 (a, b, c) 三元组表示,从左到右依次比较。

选择器类型权重示例
!important最高(覆盖一切)color: red !important
内联样式 style=""(1, 0, 0)<div style="color:red">
ID 选择器 #id(0, 1, 0)#header
类 .class、伪类 :hover、属性 [type](0, 0, 1).active、:nth-child(2)、[disabled]
标签 div、伪元素 ::before(0, 0, 0, 1)div、p::first-line
通配符 *、组合符 > + ~0*、div > p
:where()0(不贡献权重):where(.active)
:is() / :not() / :has()取参数中最高权重:is(#id, .class) → 按 #id 算
/* 优先级计算示例 */
#nav .item a:hover {
  color: red;
  /* ID(1) + 类(1) + 标签(1) + 伪类(1) = (0, 1, 2, 1) */
}

.container .item {
  color: blue;
  /* 类(1) + 类(1) = (0, 0, 2, 0) */
}
/* 结果:红色生效,因为 (0,1,2,1) > (0,0,2,0) */

/* ❌ 常见错误:试图用数量弥补等级差距 */
.a .b .c .d .e .f .g .h .i .j .k { color: blue; }
/* 11 个类选择器 = (0, 0, 11, 0) */
#id { color: red; }
/* 1 个 ID 选择器 = (0, 1, 0, 0) */
/* 结果:红色生效!ID 选择器永远高于类选择器,数量再多也无法超越 */

优先级冲突的解决策略(推荐优先级从高到低):

  1. 调整选择器结构使其更具体
  2. 利用源码顺序(后面的覆盖前面的)
  3. 使用 :where() 降低权重
  4. 避免使用 !important(除非覆盖第三方库样式)
/* ✅ :where() 降低权重(CSS Layers 时代的利器) */
:where(.framework-style) { color: blue; }  /* 权重为 0 */
.user-style { color: red; }                /* 轻松覆盖 */

/* ✅ CSS Layers(CSS 层叠层,现代项目推荐) */
@layer framework, custom;
@layer framework { .btn { color: blue; } }
@layer custom { .btn { color: red; } }     /* custom 层级更高,红色生效 */

💡 面试加分点: CSS 优先级不是简单的十进制相加(如 100 + 10),而是分级比较。一个 ID 选择器永远比任何数量的类选择器优先级高。CSS @layer 是现在管理样式优先级的新标准,它在"选择器优先级"之上增加了"层级优先级"。


6. 如何实现水平垂直居中?

水平垂直居中是 CSS 中最经典的面试题。不同方案适用场景不同,以下按推荐程度排列:

方法是否需要知道子元素尺寸适用场景兼容性
Flex否通用,最推荐IE10+
Grid place-items否最简洁现代浏览器
绝对定位 + transform否弹窗、浮层IE9+
绝对定位 + margin:auto是已知宽高的元素IE8+
table-cell否兼容老浏览器IE8+
/* ✅ 方法1:Flex(最推荐,通用性最强) */
.parent {
  display: flex;
  justify-content: center;  /* 主轴居中 */
  align-items: center;      /* 交叉轴居中 */
}

/* ✅ 方法2:Grid(最简洁,一行搞定) */
.parent {
  display: grid;
  place-items: center;  /* align-items + justify-items 的简写 */
}

/* ✅ 方法3:绝对定位 + transform(不需要知道子元素尺寸) */
.parent { position: relative; }
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* translate 百分比参照的是自身的宽高 */
}

/* ✅ 方法4:绝对定位 + margin:auto(需要明确设置子元素宽高) */
.parent { position: relative; }
.child {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  margin: auto;
  width: 200px;
  height: 100px;
  /* 原理:四方向为 0 + margin:auto,浏览器自动计算等分 margin */
}

/* ✅ 方法5:table-cell(兼容老浏览器) */
.parent {
  display: table-cell;
  text-align: center;       /* 水平居中(行内/行内块子元素) */
  vertical-align: middle;   /* 垂直居中 */
}
.child { display: inline-block; }

/* ✅ 方法6:Flex + margin:auto(也很简洁) */
.parent { display: flex; }
.child { margin: auto; }

💡 面试加分点: transform: translate(-50%, -50%) 不会引起回流(Reflow),因为 transform 在合成层(Composite Layer)中完成,性能优于修改 top/left。实际开发中弹窗组件通常用方法3。


7. 清除浮动的方法有哪些?

浮动元素会脱离文档流,导致父元素高度塌陷(父元素的高度变为 0),影响后续布局。清除浮动就是让父元素正确包裹浮动子元素。

方法原理推荐度
clearfix 伪元素在父元素末尾添加清除浮动的伪元素⭐⭐⭐⭐⭐
display: flow-root创建 BFC⭐⭐⭐⭐⭐
overflow: hidden创建 BFC⭐⭐⭐
在末尾添加空标签<div style="clear:both">❌ 不推荐
/* ✅ 方法1:clearfix 伪元素(经典方案,兼容性最好) */
.clearfix::after {
  content: '';
  display: block;    /* 伪元素默认是 inline,clear 只对 block 有效 */
  clear: both;       /* 清除左右两侧浮动 */
}
/* 兼容 IE6/7 的完整版 */
.clearfix::after {
  content: '';
  display: table;    /* 同时防止 margin 折叠 */
  clear: both;
}
.clearfix { *zoom: 1; }  /* IE6/7 触发 hasLayout */

/* ✅ 方法2:display: flow-root(现代方案,最推荐) */
.parent {
  display: flow-root;
  /* 专门为创建 BFC 设计,无副作用 */
}

/* ✅ 方法3:overflow: hidden 触发 BFC */
.parent {
  overflow: hidden;
  /* 注意:可能裁剪溢出内容(如下拉菜单、阴影) */
}

/* ❌ 方法4:末尾空标签(污染 HTML 结构,不推荐) */
/* <div class="parent">
     <div class="child" style="float:left">内容</div>
     <div style="clear:both"></div>
   </div> */

💡 面试加分点: 在现代项目中,由于 Flex 和 Grid 的普及,浮动布局已经很少使用。但理解清除浮动仍然重要,因为维护老项目时经常遇到。display: flow-root 是目前最干净的解决方案。


8. position 定位有哪些值?区别是什么?

值说明是否脱离文档流参照物创建层叠上下文
static默认值,正常文档流否无(top/left 无效)否
relative相对自身偏移,原位置保留否自身原位置是(配合 z-index)
absolute绝对定位,脱离文档流是最近的 非 static 祖先是
fixed固定定位,脱离文档流是视口(viewport)是
sticky粘性定位,到达阈值前正常流,到达后固定否滚动容器是
/* ✅ relative:常用作定位参照物 */
.parent {
  position: relative;  /* 为子元素的 absolute 定位提供参照 */
}

/* ✅ absolute:常用于弹窗、下拉菜单、气泡提示 */
.dropdown {
  position: absolute;
  top: 100%;          /* 紧贴父元素底部 */
  left: 0;
  z-index: 100;
}

/* ✅ fixed:常用于固定导航、回到顶部按钮 */
.back-to-top {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 999;
}

/* ✅ sticky:常用于吸顶导航、表头固定 */
.sticky-header {
  position: sticky;
  top: 0;            /* 必须指定 top/bottom/left/right 至少一个 */
  z-index: 100;
  background: white;
}
/* ⚠️ sticky 的注意事项:
   1. 父元素不能设置 overflow: hidden/auto/scroll(会导致 sticky 失效)
   2. 必须指定 top/bottom/left/right 中至少一个方向
   3. 粘性定位的范围不超过父元素的边界(父元素滚出视口后 sticky 也消失)
*/

/* ✅ fixed 的特殊情况:transform 会影响 fixed */
.parent {
  transform: scale(1); /* 任何非 none 的 transform */
}
.child {
  position: fixed;
  /* ⚠️ 此时 fixed 不再相对于视口,而是相对于有 transform 的祖先! */
}

💡 面试加分点: 当祖先元素设置了 transform、filter、perspective、will-change 等属性时,fixed 定位的参照物会从视口变为该祖先元素。这是一个常见的"坑",调试时需注意。sticky 在移动端表格场景(固定表头)中特别实用。


9. CSS 动画:transition 和 animation 的区别?

特性transitionanimation
触发方式需要状态变化触发(:hover、class 切换等)可自动播放,无需触发
关键帧只有起始和结束两个状态可定义任意多个关键帧
循环播放❌ 不支持✅ infinite
暂停控制❌ 不支持✅ animation-play-state: paused
JS 事件transitionendanimationstart / animationend / animationiteration
适用场景简单交互(hover、显隐)复杂动画(加载、引导)
/* ========== transition(过渡) ========== */
.button {
  background: #007bff;
  transform: scale(1);
  /* transition: 属性 时长 缓动函数 延迟 */
  transition: background 0.3s ease, transform 0.2s ease-out;
}
.button:hover {
  background: #0056b3;
  transform: scale(1.05);
}

/* 常用缓动函数(timing function) */
/* ease      → 先快后慢(默认)       */
/* linear    → 匀速                   */
/* ease-in   → 先慢后快(加速)       */
/* ease-out  → 先快后慢(减速)       */
/* ease-in-out → 先慢后快再慢         */
/* cubic-bezier(0.4, 0, 0.2, 1) → 自定义(Material Design 标准) */

/* ========== animation(动画) ========== */
/* 定义关键帧 */
@keyframes fadeSlideIn {
  0%   { opacity: 0; transform: translateY(-20px); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0); }
}

.modal {
  /* animation: 名称 时长 缓动 延迟 次数 方向 填充模式 */
  animation: fadeSlideIn 0.3s ease forwards;
  /* forwards:动画结束后保持最后一帧的状态 */
}

/* 加载旋转动画 */
@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.loading {
  animation: spin 1s linear infinite;
}

/* ✅ 性能优化:只动画 transform 和 opacity */
/* 这两个属性不会触发回流和重绘,由 GPU 合成层完成 */
.performant {
  /* ✅ 好:GPU 加速 */
  transition: transform 0.3s, opacity 0.3s;

  /* ❌ 差:触发回流 */
  /* transition: width 0.3s, height 0.3s, top 0.3s; */
}

/* ✅ will-change 提示浏览器优化 */
.animated-element {
  will-change: transform, opacity;
  /* 提前告诉浏览器该元素会变化,浏览器会提前创建合成层 */
  /* ⚠️ 不要滥用,用完后移除,否则会占用额外内存 */
}

/* ✅ prefers-reduced-motion:尊重用户的动画偏好 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

💡 面试加分点: 浏览器中只有 transform 和 opacity 的变化可以在**合成层(Compositor Layer)**中完成,不触发回流和重绘,性能最佳。其他属性(如 width、height、top、left)的动画会触发布局计算,应尽量避免。


10. CSS 变量(自定义属性)怎么使用?

CSS 变量(Custom Properties) 以 -- 开头声明,通过 var() 函数使用。与 Sass 变量不同,CSS 变量是运行时的,可以通过 JS 动态修改,也能被继承和覆盖。

特性CSS 变量Sass/Less 变量
作用域遵循 CSS 继承和层叠编译时确定
运行时修改✅ JS 可动态修改❌ 编译后是静态值
媒体查询中使用✅ 可以在不同断点重定义❌
浏览器支持现代浏览器(IE 不支持)需要编译工具
/* 声明变量(通常在 :root 中定义全局变量) */
:root {
  --primary-color: #007bff;
  --font-size-base: 16px;
  --border-radius: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* 使用变量 */
.button {
  background-color: var(--primary-color);
  font-size: var(--font-size-base);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
}

/* ✅ 带默认值(变量未定义时使用默认值) */
.text { color: var(--text-color, #333); }

/* ✅ 变量嵌套使用 */
.card { padding: var(--spacing-md, var(--spacing-sm, 8px)); }

/* ✅ 主题切换(通过切换 data-theme 属性实现) */
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
}
[data-theme="dark"] {
  --bg-color: #1a1a1a;
  --text-color: #e0e0e0;
  --primary-color: #4dabf7;
}
body {
  background: var(--bg-color);
  color: var(--text-color);
}

/* ✅ 局部作用域(组件级变量) */
.alert {
  --alert-bg: #f8d7da;
  --alert-color: #842029;
  background: var(--alert-bg);
  color: var(--alert-color);
}
.alert.success {
  --alert-bg: #d1e7dd;
  --alert-color: #0f5132;
}

/* ✅ 响应式变量 */
:root { --columns: 4; }
@media (max-width: 768px) { :root { --columns: 2; } }
.grid {
  grid-template-columns: repeat(var(--columns), 1fr);
}
// ✅ JS 动态修改 CSS 变量
document.documentElement.style.setProperty('--primary-color', '#ff6b6b')

// ✅ JS 读取 CSS 变量
const primaryColor = getComputedStyle(document.documentElement)
  .getPropertyValue('--primary-color').trim()

💡 面试加分点: CSS 变量的作用域遵循 DOM 继承。在组件内定义的变量只对该组件及其后代生效。配合 JS 动态修改,可以轻松实现主题切换、用户自定义颜色等功能,这是 Sass 变量做不到的。

Sass 变量(SCSS 语法):

Sass 是最流行的 CSS 预处理器,变量以 $ 开头。Sass 变量在编译时解析,最终输出的 CSS 中不包含变量,全部替换为具体值。

// ========== 变量定义 ==========
$primary-color: #007bff;
$font-size-base: 16px;
$border-radius: 4px;
$spacing: (sm: 8px, md: 16px, lg: 24px);  // Map 类型

// ========== 基本使用 ==========
.button {
  background-color: $primary-color;
  font-size: $font-size-base;
  border-radius: $border-radius;
  padding: map-get($spacing, sm) map-get($spacing, md);
}

// ========== 变量作用域 ==========
$color: red;         // 全局变量

.container {
  $color: blue;      // 局部变量(仅在此选择器内生效)
  color: $color;     // blue
}
.other {
  color: $color;     // red(不受局部变量影响)
}

// 使用 !global 将局部变量提升为全局
.container {
  $color: blue !global;  // 修改全局变量
}
.other {
  color: $color;     // blue(全局已被修改)
}

// ========== !default 默认值(常用于组件库/主题定制) ==========
// 用户可以在 @use 之前覆盖这些变量
$primary-color: #007bff !default;   // 仅在变量未定义时才赋值
$border-radius: 4px !default;

// ========== 变量插值 #{} ==========
$property: margin;
$direction: top;
$breakpoint: 768px;

.box {
  #{$property}-#{$direction}: 10px;  // 编译为:margin-top: 10px;
}

// 用于选择器和媒体查询
$component: 'alert';
.#{$component} { display: block; }   // 编译为:.alert { display: block; }

@media (min-width: #{$breakpoint}) {
  .container { max-width: 1200px; }
}

// ========== 变量类型 ==========
$number: 42px;                         // 数字(可带单位)
$string: 'hello';                      // 字符串
$color: #ff6b6b;                       // 颜色
$boolean: true;                        // 布尔值
$list: 10px 20px 30px;                 // 列表(空格分隔)
$list2: Helvetica, Arial, sans-serif;  // 列表(逗号分隔)
$map: (sm: 576px, md: 768px, lg: 1024px);  // Map
$null-val: null;                       // 空值

// ========== 配合 Mixin 和函数使用 ==========
// 定义可复用的样式块
@mixin responsive($breakpoint) {
  @media (min-width: map-get($map, $breakpoint)) {
    @content;  // 接收传入的内容块
  }
}

.container {
  width: 100%;
  @include responsive('md') {
    width: 750px;
  }
  @include responsive('lg') {
    width: 1000px;
  }
}

// 自定义函数
@function px-to-rem($px) {
  @return calc($px / $font-size-base) * 1rem;
}

.title {
  font-size: px-to-rem(24px);  // 编译为:font-size: 1.5rem;
}

// ========== 模块化(@use 代替 @import) ==========
// _variables.scss(以 _ 开头的文件不会被单独编译)
$primary: #007bff;
$secondary: #6c757d;

// main.scss
@use 'variables' as vars;    // 推荐使用 @use(有命名空间)
.btn { color: vars.$primary; }

@use 'variables' as *;       // 去掉命名空间(直接使用)
.btn { color: $primary; }

Less 变量:

Less 变量以 @ 开头(注意和 CSS 的 @media、@keyframes 等不冲突)。Less 也是编译时处理,语法比 Sass 更接近原生 CSS。

// ========== 变量定义 ==========
@primary-color: #007bff;
@font-size-base: 16px;
@border-radius: 4px;
@spacing-sm: 8px;
@spacing-md: 16px;

// ========== 基本使用 ==========
.button {
  background-color: @primary-color;
  font-size: @font-size-base;
  border-radius: @border-radius;
  padding: @spacing-sm @spacing-md;
}

// ========== 变量作用域(类似 JS 块级作用域) ==========
@color: red;

.container {
  @color: blue;     // 局部变量
  color: @color;    // blue
}
.other {
  color: @color;    // red(Less 变量作用域是块级的)
}

// ========== 变量插值 @{} ==========
@property: margin;
@direction: top;
@selector: banner;

.@{selector} {                      // 编译为:.banner
  @{property}-@{direction}: 10px;   // 编译为:margin-top: 10px;
}

// URL 插值
@images-path: '../images';
.logo {
  background: url('@{images-path}/logo.png');
}

// ========== 变量运算 ==========
@base: 16px;
@large: @base * 1.5;     // 24px
@half: @base / 2;        // 8px
@dark-primary: darken(@primary-color, 10%);   // 颜色函数

.box {
  width: 100% - 20px;    // Less 支持混合单位运算
  font-size: @large;
  border-color: @dark-primary;
}

// ========== 变量作为属性值的延迟加载(Lazy Evaluation) ==========
// Less 变量在同一作用域内可以"先使用后定义"
.box {
  color: @text-color;    // ✅ 可以正常工作
}
@text-color: #333;       // 定义在使用之后

// ========== Maps(Less 中用 Ruleset 模拟) ==========
#colors() {              // 定义一组变量
  primary: #007bff;
  secondary: #6c757d;
  success: #28a745;
}

.btn-primary {
  color: #colors[primary];    // 使用
}
.btn-success {
  color: #colors[success];
}

// ========== Mixin(混入) ==========
.border-radius(@radius: 4px) {   // 带默认参数
  border-radius: @radius;
  -webkit-border-radius: @radius;
}

.card {
  .border-radius(8px);   // 调用 Mixin
}

// ========== 模块化 ==========
// variables.less
@primary: #007bff;

// main.less
@import 'variables';     // 引入变量文件
.btn { color: @primary; }

三者对比总结:

特性CSS 变量Sass(SCSS)Less
声明语法--name: value$name: value@name: value
使用语法var(--name)$name@name
作用域DOM 继承(可在子元素覆盖)块级作用域块级作用域
运行时✅ 浏览器原生支持❌ 编译为静态 CSS❌ 编译为静态 CSS
JS 操控✅ setProperty❌❌
条件逻辑❌ 不支持✅ @if/@else/@for✅ when/each
函数❌ 仅 calc()等✅ @function 自定义✅ 内置函数丰富
插值❌#{}@{}
模块化❌@use/@forward@import
适用场景主题切换、动态样式大型项目、组件库中小项目、快速开发
// ========== 实际项目中的最佳实践:CSS 变量 + Sass 结合使用 ==========

// _theme.scss — 用 Sass 管理设计令牌(Design Tokens)
$themes: (
  light: (bg: #ffffff, text: #333333, primary: #007bff),
  dark:  (bg: #1a1a1a, text: #e0e0e0, primary: #4dabf7),
);

// 通过 Sass 循环生成 CSS 变量
@each $theme-name, $theme-map in $themes {
  [data-theme='#{$theme-name}'] {
    @each $key, $value in $theme-map {
      --color-#{$key}: #{$value};
    }
  }
}

// 编译输出:
// [data-theme='light'] { --color-bg: #ffffff; --color-text: #333333; --color-primary: #007bff; }
// [data-theme='dark']  { --color-bg: #1a1a1a; --color-text: #e0e0e0; --color-primary: #4dabf7; }

// 使用时用 CSS 变量(支持运行时切换)
.card {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-primary);
}

11. 响应式布局的实现方式有哪些?

响应式布局的核心目标:一套代码适配不同屏幕尺寸(手机、平板、桌面)。

方案原理适用场景
媒体查询 @media根据屏幕宽度应用不同样式断点式布局变化
rem + 根字体所有尺寸相对于根字体缩放移动端等比缩放
vw/vh 视口单位百分比相对于视口尺寸全屏布局、流式文字
clamp()在最小值和最大值之间自动响应流体字体、流体间距
Grid/Flex 自适应容器自动调整子项排列卡片网格、弹性布局
容器查询 @container根据父容器宽度(而非视口)响应组件级响应式
/* ✅ 1. 媒体查询(断点式响应) */
/* 移动优先策略(Mobile First):先写小屏样式,再用 min-width 扩展 */
.container { flex-direction: column; }

@media (min-width: 768px) {   /* 平板 */
  .container { flex-direction: row; }
}
@media (min-width: 1024px) {  /* 桌面 */
  .container { max-width: 1200px; margin: 0 auto; }
}

/* 常用断点参考(Tailwind CSS 标准):
   sm: 640px  | md: 768px  | lg: 1024px  | xl: 1280px  | 2xl: 1536px */

/* ✅ 2. rem + 根字体大小(移动端等比缩放) */
html { font-size: 16px; }
@media (max-width: 375px) { html { font-size: 14px; } }
.title { font-size: 1.5rem; }  /* 24px / 21px */
.card { padding: 1rem; }       /* 16px / 14px */

/* ✅ 3. vw/vh 视口单位 */
.hero {
  width: 100vw;
  height: 100vh;       /* vh 在移动端有地址栏问题 */
  height: 100dvh;      /* ✅ dvh = dynamic viewport height,推荐 */
}

/* ✅ 4. clamp() 流体排版(最推荐的响应式字体方案) */
.title {
  /* clamp(最小值, 首选值, 最大值) */
  font-size: clamp(1rem, 2.5vw + 0.5rem, 2.5rem);
  /* 屏幕小时 = 1rem,屏幕大时 = 2.5rem,中间自动过渡 */
}
.container {
  padding: clamp(1rem, 3vw, 3rem);
  /* 间距也可以用 clamp 实现流体效果 */
}

/* ✅ 5. Grid 自适应(无需媒体查询的响应式网格) */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
  /* 卡片宽度至少 280px,自动填充列数 */
}

/* ✅ 6. 容器查询(CSS 新特性,组件级响应式) */
.card-container {
  container-type: inline-size;  /* 声明为查询容器 */
  container-name: card;
}
@container card (min-width: 400px) {
  .card { flex-direction: row; }  /* 容器宽度≥400px 时横排 */
}
@container card (max-width: 399px) {
  .card { flex-direction: column; }  /* 容器宽度<400px 时竖排 */
}

💡 面试加分点: @container 容器查询是响应式设计的里程碑。之前只能根据视口宽度响应,现在可以根据组件容器宽度响应,使组件真正可复用。移动端建议使用 dvh(动态视口高度)替代 vh,避免移动端地址栏收起/展开导致的布局跳动。


12. CSS 伪类和伪元素的区别?

核心区别: 伪类选择已有元素的特定状态;伪元素创建不在 DOM 中的虚拟元素。

对比伪类(Pseudo-class)伪元素(Pseudo-element)
语法单冒号 :双冒号 ::
作用选择元素的状态创建虚拟元素
DOM 中选择的是已存在的元素创建不存在的元素
数量可以链式使用多个一个选择器最多一个
/* ========== 伪类:选择元素的状态 ========== */

/* 用户行为伪类 */
a:hover   { color: red; }        /* 鼠标悬停 */
input:focus { outline: 2px solid blue; }  /* 获得焦点 */
button:active { transform: scale(0.98); } /* 按下状态 */
a:visited { color: purple; }     /* 已访问链接 */
input:focus-visible { outline: 2px solid blue; }  /* ✅ 键盘聚焦时才显示 */

/* 结构伪类(选择特定位置的元素) */
li:first-child { font-weight: bold; }    /* 第一个子元素 */
li:last-child { border-bottom: none; }   /* 最后一个子元素 */
li:nth-child(odd) { background: #f0f0f0; }   /* 奇数行 */
li:nth-child(3n) { color: red; }         /* 每隔3个 */
li:nth-child(n+4) { opacity: 0.5; }      /* 第4个及之后 */
p:first-of-type { font-size: 1.2em; }    /* 同类型第一个 */
div:empty { display: none; }             /* 没有子元素的元素 */

/* 否定与匹配伪类 */
p:not(.special) { color: gray; }         /* 排除 */
:is(h1, h2, h3) { color: navy; }         /* 匹配任一(简化写法) */
:where(h1, h2, h3) { margin: 0; }        /* 同 :is,但权重为 0 */
:has(.icon) { padding-left: 2em; }       /* ✅ 父元素选择器(革命性!) */

/* 表单状态伪类 */
input:disabled { opacity: 0.5; }
input:checked + label { font-weight: bold; }
input:valid { border-color: green; }
input:invalid { border-color: red; }
input:placeholder-shown { border-style: dashed; }  /* 显示占位符时 */

/* ========== 伪元素:创建虚拟元素 ========== */

/* ::before 和 ::after(最常用) */
.quote::before { content: '\201C'; color: #999; font-size: 2em; }  /* 左引号 " */
.quote::after  { content: '\201D'; color: #999; font-size: 2em; }  /* 右引号 " */

/* 清除浮动经典方案 */
.clearfix::after { content: ''; display: block; clear: both; }

/* ✅ 装饰性内容(不污染 HTML) */
.required::after { content: ' *'; color: red; }
.external-link::after { content: ' ↗'; font-size: 0.8em; }

/* 其他伪元素 */
li::marker { color: #007bff; }                  /* 列表标记 */
::selection { background: #007bff; color: white; } /* 文字选中样式 */
input::placeholder { color: #999; }              /* 占位符样式 */
::first-line { font-weight: bold; }              /* 首行 */
::first-letter { font-size: 2em; float: left; }  /* 首字母下沉 */

💡 面试加分点: :has() 是 CSS 中期待已久的"父元素选择器",可以根据子元素的状态来选择父元素,例如 form:has(input:invalid) { border-color: red; }。:focus-visible 只在键盘聚焦时显示轮廓,鼠标点击时不显示,比 :focus 的用户体验更好。


13. CSS 中 display 有哪些常用值?

display 属性决定了元素的显示类型,影响元素如何参与布局。

值外部表现内部表现特点
block独占一行流式布局可设置宽高,默认宽度 100%
inline与其他元素同行流式布局❌ 不可设置宽高,宽度由内容决定
inline-block与其他元素同行块级布局✅ 可设置宽高,不独占一行
flex独占一行弹性布局一维布局,子项默认横排
inline-flex与其他元素同行弹性布局行内级别的 flex 容器
grid独占一行网格布局二维布局
none不渲染-完全移除,不占空间
contents自身不渲染子元素正常"拆箱",去掉容器保留子元素
flow-root独占一行BFC创建 BFC,无副作用
/* ✅ block:块级元素(div、p、h1-h6、section 默认值) */
.block { display: block; width: 100px; height: 50px; } /* 独占一行 */

/* ✅ inline:行内元素(span、a、strong 默认值) */
.inline { display: inline; }
/* ⚠️ 设置 width/height 无效,上下 margin 无效,上下 padding 不推开其他元素 */

/* ✅ inline-block:行内块(img、input 默认值) */
.tag {
  display: inline-block;
  padding: 4px 12px;
  border: 1px solid #007bff;
  border-radius: 4px;
  /* 可设置宽高,同时不独占一行 → 适合标签、按钮 */
}
/* ⚠️ inline-block 元素之间会有约 4px 的空白间隙(由 HTML 中的换行/空格引起) */
/* 解决:父元素设置 font-size: 0;或使用 flex 替代 */

/* ✅ flex / grid → 见第 3、4 题 */

/* ✅ none:完全隐藏(不占空间,不可交互) */
.hidden { display: none; }

/* ✅ contents:"去掉盒子,保留内容" */
.wrapper { display: contents; }
/* 场景:想让子元素直接参与祖父的 flex/grid 布局,而不受中间容器影响 */
/* <div class="grid">
     <div class="wrapper" style="display:contents">
       <div>直接成为 grid 子项</div>
       <div>直接成为 grid 子项</div>
     </div>
   </div> */

/* ✅ flow-root:创建 BFC(推荐替代 overflow:hidden) */
.bfc-container { display: flow-root; }

💡 面试加分点: CSS Display Module Level 3 规范引入了"双值语法":display: block flex 表示外部 block、内部 flex。display: contents 在 CSS Grid 嵌套场景中特别有用,可以让中间的包裹元素"消失",子元素直接参与外层 Grid 布局。


14. visibility:hidden 和 display:none 和 opacity:0 的区别?

这三种方式都可以让元素"消失",但在 空间占用、事件响应、渲染性能 上差异很大:

特性display: nonevisibility: hiddenopacity: 0
占据空间❌ 不占✅ 占据✅ 占据
触发回流✅ 切换时触发❌ 不触发❌ 不触发
子元素覆盖不可(子元素一起消失)✅ 子元素可设 visible 覆盖❌ 不可覆盖
事件响应❌ 不可点击❌ 不可点击✅ 仍可点击!
transition 过渡❌ 不支持✅ 支持✅ 支持
屏幕阅读器不可见不可见可见(会朗读)
适用场景条件渲染(v-if)占位隐藏淡入淡出动画
/* ❌ display: none → 完全移除,切换会触发回流 */
.hidden { display: none; }

/* ⚠️ visibility: hidden → 占位但不可见、不可交互 */
.invisible { visibility: hidden; }

/* ⚠️ opacity: 0 → 占位、不可见,但仍然可以交互(点击事件仍触发!) */
.transparent { opacity: 0; }
/* 如果不想响应事件,需配合 pointer-events: none */
.transparent-no-events { opacity: 0; pointer-events: none; }

/* ✅ 最佳实践:淡入淡出动画(opacity + visibility 配合) */
.modal {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
  /* visibility 确保隐藏时不可交互 */
  /* opacity 负责视觉过渡效果 */
}
.modal.show {
  opacity: 1;
  visibility: visible;
}

/* ✅ 更多隐藏元素的方式 */
/* 移出视口(屏幕阅读器仍可读取,常用于无障碍) */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* clip-path 裁剪隐藏 */
.clip-hidden { clip-path: inset(50%); }

/* 缩放为 0 */
.scale-hidden { transform: scale(0); }

💡 面试加分点: 做淡入淡出动画时,不能只用 opacity(因为 opacity: 0 的元素仍然可以被点击),也不能只用 display: none(因为不支持 transition)。正确做法是 opacity + visibility 配合使用。.sr-only 类是无障碍开发的标准方案,让内容只对屏幕阅读器可见。


15. CSS 中如何实现文字溢出省略号?

方案适用场景兼容性
单行省略标题、列表项所有浏览器
-webkit-line-clamp多行文本截断现代浏览器(包括 Firefox)
JS 方案需要精确控制所有浏览器
/* ✅ 单行省略(三件套,缺一不可) */
.ellipsis {
  white-space: nowrap;        /* 1. 强制不换行 */
  overflow: hidden;           /* 2. 超出部分隐藏 */
  text-overflow: ellipsis;    /* 3. 超出部分显示省略号 */
  /* ⚠️ 必须有明确的宽度限制(width 或 max-width 或 flex:1) */
}

/* ✅ 多行省略(-webkit-line-clamp) */
.multi-ellipsis {
  display: -webkit-box;          /* 1. 弹性伸缩盒子 */
  -webkit-box-orient: vertical;  /* 2. 垂直排列 */
  -webkit-line-clamp: 3;         /* 3. 最多显示 3 行 */
  line-clamp: 3;                 /* 标准属性(未来) */
  overflow: hidden;              /* 4. 超出隐藏 */
  /* ⚠️ 不能设置 padding-bottom,否则可能导致省略号不显示 */
}

/* ✅ 在 Flex 布局中使用单行省略 */
.flex-ellipsis {
  display: flex;
  align-items: center;
}
.flex-ellipsis .text {
  flex: 1;
  min-width: 0;              /* ⚠️ 关键!Flex 子项默认 min-width: auto */
  white-space: nowrap;        /* 没有 min-width: 0,省略号不会生效 */
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ✅ Grid 布局中使用省略号 */
.grid-ellipsis {
  display: grid;
  grid-template-columns: 1fr auto;
}
.grid-ellipsis .text {
  min-width: 0;              /* Grid 子项也需要 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ✅ 配合 title 属性显示完整内容 */
/* <p class="ellipsis" title="这是一段很长的文字...">这是一段很长的文字...</p> */

💡 面试加分点: 在 Flex 和 Grid 布局中使用文字省略时,必须在子项上设置 min-width: 0。因为 Flex/Grid 子项的默认 min-width 是 auto(内容的最小宽度),会阻止元素收缩到比内容更小,导致 text-overflow: ellipsis 失效。


16. CSS 中 z-index 的工作原理?

z-index 控制元素在z 轴(垂直于屏幕方向)上的叠放顺序。但它并不是简单的"数值越大越靠前",而是受**层叠上下文(Stacking Context)**约束。

核心规则:

  1. z-index 只对定位元素(position ≠ static)和 flex/grid 子项有效
  2. z-index 的比较只在同一个层叠上下文内进行
  3. 子元素的 z-index 无法超越父元素所在层叠上下文的限制

创建新层叠上下文的条件(任一即可):

属性条件
position + z-indexposition: relative/absolute/fixed/sticky 且 z-index ≠ auto
opacity值小于 1
transform不为 none
filter不为 none
isolationisolate
will-change指定了以上任一属性
mix-blend-mode不为 normal
containlayout 或 paint
/* ❌ 经典"坑":子元素 z-index 再大也超不过父元素的兄弟 */
.parent-a {
  position: relative;
  z-index: 1;  /* 创建了层叠上下文 */
}
.child-of-a {
  position: absolute;
  z-index: 99999;  /* 只在 parent-a 内部有效! */
}

.parent-b {
  position: relative;
  z-index: 2;  /* parent-b 整体在 parent-a 之上 */
}
/* 结果:parent-b 的内容会覆盖 child-of-a,尽管 child-of-a 的 z-index 是 99999 */

/* ✅ 同一层叠上下文内的叠放顺序(从下到上):
   1. 背景和边框
   2. z-index < 0 的子元素
   3. 标准流中的块级元素
   4. 浮动元素
   5. 标准流中的行内元素
   6. z-index: 0 / auto 的定位元素
   7. z-index > 0 的定位元素
*/

/* ✅ 使用 isolation: isolate 精确控制层叠上下文 */
.component {
  isolation: isolate;
  /* 创建新的层叠上下文,内部 z-index 不会泄漏到外部 */
  /* 比 position + z-index 更语义化 */
}

/* ✅ 项目中的 z-index 管理规范(使用 CSS 变量统一管理) */
:root {
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-fixed: 300;
  --z-modal-backdrop: 400;
  --z-modal: 500;
  --z-popover: 600;
  --z-tooltip: 700;
  --z-toast: 800;
}

.modal-backdrop { z-index: var(--z-modal-backdrop); }
.modal { z-index: var(--z-modal); }
.tooltip { z-index: var(--z-tooltip); }

💡 面试加分点: isolation: isolate 是创建层叠上下文最"干净"的方式——不会影响元素的定位和布局,只创建新的层叠上下文。在组件库开发中,给每个组件根元素加 isolation: isolate 可以防止 z-index 污染。


17. CSS 预处理器(Sass/Less)的常用特性?

CSS 预处理器通过扩展 CSS 语法,提供变量、嵌套、混入、函数等编程能力,让样式代码更易维护和复用。

特性Sass/SCSSLess原生 CSS 替代
变量$color: red@color: red--color: red ✅
嵌套✅✅✅(CSS Nesting)
Mixin@mixin / @include.mixin()❌
继承@extend:extend()❌
函数@function有限支持❌
循环@for @each @whileeach()❌
模块化@use @forward@import@import / @layer
// ========== Sass(SCSS 语法)示例 ==========

// 1. 变量
$primary: #007bff;
$font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
$breakpoint-md: 768px;

// 2. 嵌套 + BEM 命名(& 代表父选择器)
.nav {
  display: flex;
  background: $primary;

  &__item {                    // 编译为 .nav__item
    padding: 10px 16px;

    &:hover {                  // 编译为 .nav__item:hover
      background: darken($primary, 10%);
    }

    &--active {                // 编译为 .nav__item--active
      font-weight: bold;
    }
  }
}

// 3. Mixin(可传参的样式片段)
@mixin flex-center($direction: row) {
  display: flex;
  flex-direction: $direction;
  justify-content: center;
  align-items: center;
}
@mixin responsive($breakpoint) {
  @media (min-width: $breakpoint) { @content; }
}

.card {
  @include flex-center(column);
  @include responsive($breakpoint-md) {
    flex-direction: row;     // 768px 以上横排
  }
}

// 4. 函数(返回一个值)
@function rem($px, $base: 16px) {
  @return calc($px / $base * 1rem);
}
.title { font-size: rem(24px); }  // 1.5rem

// 5. 继承(%placeholder 不会单独编译输出)
%button-base {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}
.btn-primary { @extend %button-base; background: $primary; color: white; }
.btn-danger  { @extend %button-base; background: #dc3545; color: white; }

// 6. 循环
@for $i from 1 through 12 {
  .col-#{$i} { width: percentage(calc($i / 12)); }
}

// 7. 条件判断
@mixin theme-color($theme) {
  @if $theme == 'dark' {
    background: #1a1a1a; color: #fff;
  } @else {
    background: #fff; color: #333;
  }
}
/* ✅ 原生 CSS 嵌套(2023 年起主流浏览器支持) */
.nav {
  display: flex;
  & .item { padding: 10px; }
  & .item:hover { color: red; }
  @media (max-width: 768px) {
    flex-direction: column;
  }
}

💡 面试加分点: 随着 CSS 原生支持了变量(Custom Properties)、嵌套(CSS Nesting)、@layer(层叠层)等特性,CSS 预处理器的优势在缩小。但 Mixin、函数、循环等编程能力仍然是原生 CSS 做不到的。Sass 中推荐使用 @use 替代 @import,避免全局污染。


18. 什么是 CSS Modules?

CSS Modules 是一种 CSS 模块化方案,通过构建工具(Webpack/Vite)自动将类名编译为唯一的哈希值,实现样式的局部作用域,彻底解决类名冲突问题。

CSS 方案隔离方式运行时开销适用场景
CSS Modules编译时生成唯一类名无React/Vue 项目
CSS-in-JS (styled-components)运行时生成唯一类名有React 项目
Scoped CSS (Vue)添加 data-v-xxx 属性选择器无Vue 单文件组件
BEM 命名规范人工约定无所有项目
Tailwind CSS原子类,不存在冲突无所有项目
/* Button.module.css */
.button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.primary { background: #007bff; color: white; }
.danger { background: #dc3545; color: white; }

/* 组合类名 */
.button.primary { }

/* 全局样式(不被哈希化) */
:global(.clearfix)::after { content: ''; display: block; clear: both; }

/* 继承其他模块的类名 */
.submit { composes: button primary; font-weight: bold; }
// Button.jsx(React 中使用)
import styles from './Button.module.css'

const Button = ({ variant = 'primary', children }) => (
  <button className={`${styles.button} ${styles[variant]}`}>
    {children}
  </button>
)
// ✅ 编译后的类名:Button_button__a1b2c3 Button_primary__d4e5f6
// 完全不会与其他组件的类名冲突
<!-- Vue 中的 Scoped CSS(类似效果) -->
<template>
  <button class="button primary">按钮</button>
</template>
<style scoped>
/* scoped 会自动添加属性选择器 [data-v-7ba5bd90] */
.button { padding: 8px 16px; }
.primary { background: #007bff; }
/* 编译后:.button[data-v-7ba5bd90] { padding: 8px 16px; } */
</style>

<!-- ⚠️ Vue scoped 的深度选择器(修改子组件样式) -->
<style scoped>
:deep(.child-class) { color: red; }
/* 编译后:[data-v-7ba5bd90] .child-class { color: red; } */
</style>

💡 面试加分点: Vue 的 scoped 样式底层通过给元素添加 data-v-xxx 属性实现隔离,但它不是真正的隔离——子组件的根元素也会带有父组件的 scope 属性。如果需要在 scoped 中修改子组件内部样式,需要使用 :deep() 深度选择器。


19. Tailwind CSS 的核心理念是什么?

Tailwind CSS 是一个原子化(Utility-First)CSS 框架。核心理念:每个 class 只做一件事(如 p-4 = padding: 1rem),通过组合多个原子类构建 UI,而不是编写自定义 CSS。

原子化 CSS 的优缺点:

优点缺点
不用起类名,开发效率高HTML 类名较长
天然避免 CSS 冲突需要学习类名映射
CSS 体积小(按需生成)自定义复杂动画仍需写 CSS
设计令牌(Design Token)统一不适合非组件化项目
<!-- ❌ 传统 CSS 方式 -->
<div class="card">
  <h2 class="card-title">标题</h2>
  <p class="card-description">描述内容</p>
</div>
<style>
  .card { padding: 16px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
  .card-title { font-size: 20px; font-weight: bold; color: #333; }
  .card-description { color: #666; margin-top: 8px; }
</style>

<!-- ✅ Tailwind CSS 方式(无需写 CSS 文件) -->
<div class="p-4 rounded-lg shadow-md">
  <h2 class="text-xl font-bold text-gray-800">标题</h2>
  <p class="text-gray-600 mt-2">描述内容</p>
</div>

<!-- ✅ 响应式(前缀:sm/md/lg/xl/2xl) -->
<div class="w-full md:w-1/2 lg:w-1/3">
  <!-- 手机全宽 → 平板半宽 → 桌面三分之一宽 -->
</div>

<!-- ✅ 状态变体(hover/focus/active/disabled 等) -->
<button class="bg-blue-500 hover:bg-blue-700 focus:ring-2 focus:ring-blue-300
               active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed
               transition-all duration-200">
  按钮
</button>

<!-- ✅ 暗色模式 -->
<div class="bg-white dark:bg-gray-900 text-black dark:text-white">
  自动适应暗色模式
</div>

<!-- ✅ 分组变体(group-hover) -->
<div class="group cursor-pointer">
  <h3 class="group-hover:text-blue-500 transition-colors">标题</h3>
  <p class="group-hover:text-gray-600">描述</p>
</div>

<!-- ✅ 任意值(方括号语法) -->
<div class="w-[calc(100%-2rem)] bg-[#1da1f2] grid-cols-[200px_1fr]">
  自定义值
</div>
// tailwind.config.js - 自定义配置
export default {
  content: ['./src/**/*.{html,js,jsx,tsx,vue}'],  // 扫描路径
  theme: {
    extend: {
      colors: {
        brand: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a5f' },
      },
      spacing: { '18': '4.5rem' },
    },
  },
  plugins: [],
}

💡 面试加分点: Tailwind 在生产环境中通过 PurgeCSS(现在内置为 JIT 引擎)只保留项目中实际使用的类,最终 CSS 通常只有 10-20KB(gzip 后)。与 CSS-in-JS 相比,Tailwind 没有运行时开销。


20. 如何实现一个三角形?

CSS 中实现三角形有多种方式,原理各不相同:

方法原理优点缺点
border边框交汇处形成三角形兼容性最好只能做实心三角
clip-path裁剪路径可做任意形状IE 不支持
linear-gradient渐变背景灵活代码不直观
/* ✅ 方法1:border 技巧(最经典,面试常考) */
/* 原理:当元素宽高为 0 时,四个 border 形成四个三角形 */
.triangle-up {
  width: 0;
  height: 0;
  border-left: 20px solid transparent;    /* 左边透明 */
  border-right: 20px solid transparent;   /* 右边透明 */
  border-bottom: 40px solid #007bff;      /* 底边有色 → 向上的三角 */
}

.triangle-right {
  width: 0;
  height: 0;
  border-top: 20px solid transparent;
  border-bottom: 20px solid transparent;
  border-left: 40px solid #007bff;        /* 左边有色 → 向右的三角 */
}

.triangle-down {
  width: 0;
  height: 0;
  border-left: 20px solid transparent;
  border-right: 20px solid transparent;
  border-top: 40px solid #007bff;         /* 顶边有色 → 向下的三角 */
}

/* ✅ 方法2:clip-path(推荐,可做任意多边形) */
.triangle-clip {
  width: 100px;
  height: 100px;
  background: #007bff;
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);  /* 等腰三角形 */
}

/* ✅ 更多 clip-path 形状 */
.arrow-right { clip-path: polygon(0 0, 80% 50%, 0 100%); }
.pentagon { clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%); }
.hexagon { clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); }

/* ✅ 方法3:实际应用 - Tooltip 气泡箭头 */
.tooltip {
  position: relative;
  background: #333;
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
}
.tooltip::after {
  content: '';
  position: absolute;
  top: 100%;         /* 箭头在底部 */
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #333;  /* 向下箭头 */
}

/* ✅ 方法4:实际应用 - 下拉菜单箭头 */
.dropdown-arrow::after {
  content: '';
  display: inline-block;
  width: 8px;
  height: 8px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);    /* 旋转 45° 变成向下的箭头 */
  margin-left: 6px;
  vertical-align: middle;
}

💡 面试加分点: 实际项目中三角形最常见的场景是 Tooltip 气泡箭头和下拉菜单指示器。clip-path 可以用在线工具 Clippy 快速生成路径。


21. 什么是回流(Reflow)和重绘(Repaint)?如何优化?

回流(Reflow/Layout): 当元素的几何属性(位置、大小)发生变化时,浏览器需要重新计算布局。 重绘(Repaint): 当元素的外观属性(颜色、背景、阴影)发生变化,但不影响布局时,浏览器只需重新绘制。

回流一定引起重绘,重绘不一定引起回流。回流的性能代价远大于重绘。

操作是否触发回流是否触发重绘
修改 width/height/margin/padding✅✅
修改 color/background/box-shadow❌✅
修改 transform/opacity❌❌(合成层完成)
读取 offsetWidth/scrollTop/getComputedStyle✅(强制同步布局)-
添加/删除 DOM 节点✅✅
窗口 resize✅✅
// ❌ 错误:多次读写交替,触发多次强制同步布局
for (let i = 0; i < 100; i++) {
  el.style.width = el.offsetWidth + 10 + 'px'  // 每次都触发回流!
}

// ✅ 正确:先批量读取,再批量写入
const width = el.offsetWidth          // 只读取一次
for (let i = 0; i < 100; i++) {
  el.style.width = width + 10 * i + 'px'
}
/* ✅ 优化方案 */
/* 1. 使用 transform 代替 top/left 做动画 */
.animate { transform: translateX(100px); }  /* ✅ GPU 加速 */
/* .animate { left: 100px; } */             /* ❌ 触发回流 */

/* 2. 使用 will-change 提示浏览器提前优化 */
.will-animate { will-change: transform, opacity; }

/* 3. 使用 contain 属性限制回流范围 */
.card { contain: layout style paint; }
/* 告诉浏览器:这个元素内部的变化不影响外部,可以局部回流 */

💡 面试加分点: contain: content 是性能优化的利器,它告诉浏览器该元素是一个独立的渲染边界,内部变化不会影响外部布局。Chrome DevTools 的 Performance 面板可以看到每次 Layout(回流)和 Paint(重绘)的耗时。


22. CSS 中的 margin 折叠(合并)是什么?

Margin 折叠(Margin Collapsing) 是指在垂直方向上,两个相邻块级元素的 margin 会合并为一个,取其中的较大值(而非相加)。

发生 margin 折叠的三种情况:

  1. 相邻兄弟元素:上元素的 margin-bottom 和下元素的 margin-top
  2. 父元素与第一个/最后一个子元素:父元素的 margin-top 和第一个子元素的 margin-top
  3. 空的块级元素:自身的 margin-top 和 margin-bottom
/* ❌ 场景1:相邻兄弟元素 margin 折叠 */
.box-a { margin-bottom: 30px; }
.box-b { margin-top: 20px; }
/* 结果:两者间距是 30px(取较大值),不是 50px */

/* ❌ 场景2:父子 margin 折叠(父没有 border/padding 时) */
.parent { margin-top: 0; }
.child { margin-top: 20px; }
/* 子元素的 margin-top 会"穿透"父元素,表现为父元素的 margin-top */

/* ✅ 解决父子 margin 折叠的方法 */
/* 方法1:父元素设置 overflow(触发 BFC) */
.parent { overflow: hidden; }

/* 方法2:父元素设置 border 或 padding(隔断 margin) */
.parent { padding-top: 1px; }
.parent { border-top: 1px solid transparent; }

/* 方法3:父元素 display: flow-root */
.parent { display: flow-root; }

/* 方法4:使用 padding 替代子元素的 margin */
.parent { padding-top: 20px; }  /* 替代 .child { margin-top: 20px; } */

不会发生 margin 折叠的情况:

  • 水平方向的 margin 永远不折叠
  • 浮动元素、绝对定位元素不折叠
  • display: inline-block 的元素不折叠
  • BFC 内外不折叠(BFC 本身就是隔离的)
  • Flex/Grid 子项不折叠

💡 面试加分点: 负 margin 的折叠规则——如果一个正一个负,结果 = 正值 + 负值(即相减);如果两个都是负值,结果 = 较大绝对值的负值。Flex 和 Grid 子项之间不存在 margin 折叠,这也是现代布局的优势之一。


23. CSS 中 em、rem、%、vw/vh 等单位的区别?

单位相对于适用场景
px绝对单位(CSS 像素)边框、阴影等固定尺寸
em父元素的 font-size组件内的间距、行高
rem根元素(html)的 font-size全局字体、间距
%父元素的对应属性宽度自适应
vw / vh视口的宽度 / 高度的 1%全屏布局、流体排版
dvh / svh / lvh动态 / 最小 / 最大视口高度移动端全屏
ch字符 0 的宽度等宽字体排版
lh当前元素的 line-height与行高相关的间距
cqw / cqh容器查询的宽度 / 高度的 1%组件级响应式
/* ✅ em:相对于父元素 font-size(会层层嵌套累积) */
.parent { font-size: 16px; }
.child { font-size: 1.5em; }    /* 24px = 16 × 1.5 */
.grandchild { font-size: 1.5em; } /* 36px = 24 × 1.5(累积!) */
/* ⚠️ em 的坑:嵌套层级多时字体大小会不断放大/缩小 */

/* ✅ rem:相对于根元素 font-size(不会累积,更可控) */
html { font-size: 16px; }
.title { font-size: 1.5rem; }    /* 24px,始终基于 html 的 16px */
.subtitle { font-size: 1.25rem; } /* 20px */
.body { font-size: 1rem; }        /* 16px */

/* ✅ %:相对于父元素的对应属性 */
.child {
  width: 50%;         /* 父元素宽度的 50% */
  padding: 5%;        /* ⚠️ 百分比 padding 是相对于父元素的宽度! */
  margin-top: 10%;    /* ⚠️ 百分比 margin 也是相对于父元素的宽度! */
  /* 上面这点经常是面试考点 */
}

/* ✅ vw/vh:视口单位 */
.full-screen { width: 100vw; height: 100vh; }
.fluid-text { font-size: 4vw; }  /* 字体随视口缩放 */

/* ✅ dvh(动态视口高度):推荐移动端使用 */
.mobile-full {
  height: 100dvh;
  /* dvh 会考虑移动端地址栏的展开/收起,vh 不会 */
}

/* ✅ ch 单位:限制文本宽度(无障碍推荐) */
.article { max-width: 65ch; } /* 每行约 65 个字符,最佳阅读宽度 */

💡 面试加分点: padding 和 margin 的百分比值始终相对于包含块的宽度计算(即使是 padding-top 和 margin-top)。这个特性可以用来实现固定宽高比的元素(如 padding-top: 56.25% 实现 16:9 的容器),现在更推荐使用 aspect-ratio: 16/9。


24. 什么是 CSS 层叠层(@layer)?

@layer 是 CSS 层叠层(Cascade Layers),用于控制样式的优先级层级。它在选择器优先级之上增加了一层"层级优先级"管理,让样式的覆盖关系更可控。

解决的问题: 以往覆盖第三方库(如 Bootstrap、Ant Design)的样式需要写更高优先级的选择器或 !important,有了 @layer 后可以优雅地管理。

/* ✅ 定义层叠顺序(后声明的层优先级更高) */
@layer reset, base, components, utilities;

/* reset 层:最低优先级 */
@layer reset {
  * { margin: 0; padding: 0; box-sizing: border-box; }
  a { text-decoration: none; color: inherit; }
}

/* base 层 */
@layer base {
  body { font-family: sans-serif; line-height: 1.6; }
  h1 { font-size: 2rem; }
}

/* components 层 */
@layer components {
  .btn { padding: 8px 16px; border-radius: 4px; }
  .card { padding: 16px; border: 1px solid #eee; }
}

/* utilities 层:最高优先级 */
@layer utilities {
  .hidden { display: none !important; }
  .text-center { text-align: center; }
}

/* ✅ 未分层的样式优先级高于所有 @layer 中的样式 */
.my-custom-style { color: red; }  /* 这条会覆盖所有层中的 color */

/* ✅ 导入第三方库到指定层(降低其优先级) */
@import url('bootstrap.css') layer(framework);
@layer framework, custom;
/* 现在 custom 层的样式会自然覆盖 framework 层 */

@layer custom {
  .btn { background: #007bff; }  /* 轻松覆盖 Bootstrap 的按钮样式 */
}

层叠优先级顺序(从低到高):

  1. 浏览器默认样式
  2. 有 @layer 声明的样式(按声明顺序,后面的更高)
  3. 没有 @layer 的普通样式
  4. 内联样式
  5. !important(反转上述顺序)

💡 面试加分点: @layer 配合 @import 可以把第三方 CSS 库放在低优先级层中,无需 !important 就能轻松覆盖。Tailwind CSS v4 已经默认使用 @layer 来管理样式优先级。


25. 什么是 CSS 逻辑属性?

CSS 逻辑属性(Logical Properties) 使用 inline(行内方向)和 block(块方向)替代传统的 top/right/bottom/left,使样式能自动适配不同的书写模式(如从右到左的阿拉伯语)。

物理属性逻辑属性含义
widthinline-size行内方向的尺寸
heightblock-size块方向的尺寸
margin-leftmargin-inline-start行内方向起点的 margin
margin-rightmargin-inline-end行内方向终点的 margin
margin-topmargin-block-start块方向起点的 margin
padding-left / padding-rightpadding-inline行内方向的 padding(简写)
text-align: lefttext-align: start行内起点对齐
border-topborder-block-start块方向起点的 border
top / bottominset-block-start / end块方向的偏移
left / rightinset-inline-start / end行内方向的偏移
/* ✅ 使用逻辑属性(国际化友好) */
.card {
  margin-block: 16px;          /* = margin-top + margin-bottom */
  padding-inline: 24px;        /* = padding-left + padding-right */
  border-block-end: 1px solid #eee;  /* = border-bottom */
  inline-size: 300px;          /* = width */
  max-inline-size: 100%;       /* = max-width */
}

/* ✅ 简写属性 */
.element {
  margin-inline: 20px 10px;   /* start: 20px, end: 10px */
  padding-block: 16px;        /* start 和 end 都是 16px */
  inset: 0;                   /* = top: 0; right: 0; bottom: 0; left: 0; */
}

/* ✅ 国际化场景:RTL 布局自动适配 */
.sidebar {
  margin-inline-start: 0;
  margin-inline-end: 20px;
  /* LTR(英语):左 0,右 20px */
  /* RTL(阿拉伯语):右 0,左 20px → 自动翻转! */
}

💡 面试加分点: inset: 0 是 top: 0; right: 0; bottom: 0; left: 0 的简写,配合 position: absolute 和 margin: auto 可以实现居中。现代 CSS 推荐优先使用逻辑属性,尤其是需要支持国际化的项目。


26. 如何实现 1px 边框问题(移动端)?

在 Retina 屏幕(devicePixelRatio >= 2)上,CSS 的 1px 会被渲染成 2 个或 3 个物理像素,看起来比设计稿粗。

方案原理推荐度
transform: scale(0.5)伪元素 + 缩放⭐⭐⭐⭐⭐
border-image + SVGSVG 图片做 1px 边框⭐⭐⭐
box-shadow利用阴影模拟边框⭐⭐
0.5px直接写 0.5px⭐⭐(iOS 8+ 支持)
/* ✅ 方案1:transform + 伪元素(最佳方案) */
.border-1px {
  position: relative;
}
.border-1px::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 200%;
  height: 200%;
  border: 1px solid #e5e5e5;
  border-radius: inherit;
  transform: scale(0.5);
  transform-origin: 0 0;
  pointer-events: none;
  box-sizing: border-box;
}

/* 只要底部边框 */
.border-bottom-1px::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: #e5e5e5;
  transform: scaleY(0.5);
  transform-origin: 0 100%;
}

/* ✅ 方案2:使用 SVG(清晰度最好) */
.border-svg {
  border: 1px solid transparent;
  border-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='none' stroke='%23e5e5e5'/%3E%3C/svg%3E") 1;
}

/* ✅ 方案3:直接 0.5px(简单但兼容性有限) */
@media (-webkit-min-device-pixel-ratio: 2) {
  .border-half { border-width: 0.5px; }
}

💡 面试加分点: 这是移动端开发的经典问题。transform: scale(0.5) 方案最通用,原理是先画 2 倍大小的 1px 边框,再缩放到 50%,在 2x 屏幕上刚好是 1 个物理像素。


27. 什么是 CSS 滚动吸附(Scroll Snap)?

CSS Scroll Snap 允许在滚动结束时自动将内容吸附对齐到指定位置,常用于轮播图、全屏滚动页面、横向滑动列表等。

/* ✅ 横向滑动卡片(移动端常见) */
.scroll-container {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;  /* x 方向强制吸附 */
  gap: 16px;
  padding: 16px;
  /* 隐藏滚动条(可选) */
  scrollbar-width: none;          /* Firefox */
}
.scroll-container::-webkit-scrollbar { display: none; } /* Chrome/Safari */

.scroll-item {
  flex: 0 0 80%;                  /* 每张卡片占 80% 宽度 */
  scroll-snap-align: center;      /* 吸附到中心位置 */
  border-radius: 12px;
}

/* ✅ 全屏滚动(每次滚动一整屏) */
.fullpage {
  height: 100vh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
}
.section {
  height: 100vh;
  scroll-snap-align: start;       /* 吸附到顶部 */
}

/* scroll-snap-type 的值 */
/* mandatory:必须吸附到最近的吸附点(强制) */
/* proximity:靠近吸附点时才吸附(宽松) */

/* scroll-snap-align 的值 */
/* start | center | end:对齐到容器的起点/中心/终点 */

/* ✅ 额外控制 */
.scroll-container {
  scroll-padding: 20px;           /* 吸附时的内边距偏移 */
}
.scroll-item {
  scroll-snap-stop: always;       /* 不允许跳过,每个元素都要经过 */
  scroll-margin: 10px;            /* 单个元素的吸附偏移 */
}

💡 面试加分点: Scroll Snap 可以替代很多 JS 轮播库(如 Swiper),实现纯 CSS 的滑动效果,性能更好。配合 scroll-behavior: smooth 可以让滚动有平滑过渡效果。


28. CSS 中如何实现等比例宽高比(Aspect Ratio)?

保持元素的宽高比在响应式布局中非常重要,常见于视频播放器、图片容器、嵌入内容等。

/* ✅ 方法1:aspect-ratio 属性(现代方案,强烈推荐) */
.video-container {
  width: 100%;
  aspect-ratio: 16 / 9;  /* 自动计算高度 = 宽度 × 9/16 */
}

.square { aspect-ratio: 1; }      /* 正方形 */
.card { aspect-ratio: 3 / 4; }    /* 竖版卡片 */

/* ✅ 配合 object-fit 控制图片填充方式 */
.image-container {
  aspect-ratio: 1;
  overflow: hidden;
}
.image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;    /* 裁剪填满(保持比例) */
  /* object-fit: contain → 完整显示(可能有留白) */
  /* object-fit: fill → 拉伸填满(可能变形) */
}

/* ✅ 方法2:padding-top hack(兼容老浏览器) */
.ratio-16-9 {
  position: relative;
  width: 100%;
  padding-top: 56.25%;  /* 9 / 16 × 100% = 56.25% */
  /* 因为 padding 百分比是相对于宽度计算的 */
}
.ratio-16-9 > * {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}

/* ✅ 防止布局偏移(CLS 优化) */
img {
  aspect-ratio: attr(width) / attr(height);  /* 根据 HTML 属性计算 */
  width: 100%;
  height: auto;
}
/* 或直接在 HTML 上设置宽高属性 */
/* <img src="photo.jpg" width="800" height="600" /> */

💡 面试加分点: aspect-ratio 属性极大简化了等比例容器的实现。配合 <img> 标签的 width 和 height HTML 属性,浏览器可以在图片加载前预留空间,避免布局偏移(CLS),提升 Core Web Vitals 评分。


29. 什么是 CSS 的 contain 属性?

contain 属性告诉浏览器某个元素的内部变化不会影响外部,使浏览器可以进行局部优化(跳过不必要的回流/重绘),显著提升渲染性能。

值含义效果
layout内部布局变化不影响外部回流范围局限在元素内部
style计数器和引号不影响外部样式计算优化
paint内部内容不会绘制到元素边界外跳过屏幕外的绘制
size元素大小不依赖子元素跳过子元素的尺寸计算
content= layout + style + paint常用简写
strict= layout + style + paint + size最严格
/* ✅ 卡片组件:内部变化不影响外部 */
.card {
  contain: content;
  /* 等于 contain: layout style paint */
  /* 浏览器知道:card 内部的 DOM 变化不需要重新计算外部布局 */
}

/* ✅ 长列表优化:每一项都是独立的渲染区域 */
.list-item {
  contain: layout style paint;
  /* 修改某一项不会触发其他项的回流 */
}

/* ✅ content-visibility:自动跳过屏幕外元素的渲染(更强大) */
.long-list .item {
  content-visibility: auto;
  contain-intrinsic-size: 0 200px;  /* 预估高度,避免滚动条跳动 */
  /* 屏幕外的元素不会被渲染,进入视口时才渲染 */
  /* 类似虚拟滚动的效果,但无需 JS! */
}

💡 面试加分点: content-visibility: auto 是近年来最强大的 CSS 性能优化属性之一,可以让浏览器自动跳过屏幕外元素的渲染,在长页面场景下可以将首次渲染时间降低 50% 以上。需要配合 contain-intrinsic-size 使用,否则滚动条会抖动。


30. CSS 有哪些新特性值得关注?

以下是近几年 CSS 的重要新特性,面试中展现对新技术的了解是加分项:

/* ✅ 1. 容器查询 @container(2023) */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
  .card { flex-direction: row; }
}

/* ✅ 2. :has() 父元素选择器(2023) */
form:has(input:invalid) { border-color: red; }
li:has(> a:hover) { background: #f0f0f0; }

/* ✅ 3. CSS 嵌套 Nesting(2023) */
.nav {
  background: white;
  & .item { padding: 10px; }
  &:hover { background: #f0f0f0; }
}

/* ✅ 4. 层叠层 @layer(2022) */
@layer base, components, utilities;

/* ✅ 5. 子网格 subgrid(2023) */
.parent { display: grid; grid-template-columns: repeat(3, 1fr); }
.child { display: grid; grid-template-columns: subgrid; }
/* 子元素继承父元素的网格轨道定义 */

/* ✅ 6. color-mix() 颜色混合(2023) */
.button {
  background: oklch(0.7 0.15 200);        /* 新的颜色空间 */
}
.button:hover {
  background: color-mix(in oklch, var(--primary) 80%, black);
  /* 将主色与黑色混合,实现悬停加深效果 */
}

/* ✅ 7. View Transitions API(2024) */
::view-transition-old(root) { animation: fade-out 0.3s; }
::view-transition-new(root) { animation: fade-in 0.3s; }

/* ✅ 8. Popover API(2024) */
/* <button popovertarget="menu">打开</button>
   <div id="menu" popover>弹出内容</div> */
[popover] {
  &:popover-open { opacity: 1; }
}

/* ✅ 9. anchor positioning 锚点定位(2024) */
.tooltip {
  position: absolute;
  position-anchor: --trigger;
  top: anchor(bottom);
  left: anchor(center);
}

/* ✅ 10. @scope 作用域(2024) */
@scope (.card) to (.card-footer) {
  p { color: #333; }  /* 只在 .card 到 .card-footer 之间生效 */
}

💡 面试加分点: 面试时能提到 :has()、@container、@layer、CSS Nesting 等新特性,说明你持续关注前端发展。View Transitions API 让页面切换动画变得极其简单,Popover API 是原生弹窗方案,减少对 JS 弹窗库的依赖。


31. CSS 有哪些样式引入方式?各有什么优缺点?

CSS 样式引入页面主要有 四种方式:外部样式表、内部样式表、行内样式和 @import 导入。

引入方式语法优先级适用场景
外部样式表(External)<link rel="stylesheet" href="xxx.css">由选择器权重决定大型项目、多页面共享样式
内部样式表(Internal)<style>...</style> 写在 <head> 中由选择器权重决定单页面特有样式、关键 CSS 内联
行内样式(Inline)style="..." 写在标签上⚡ 最高(仅次于 !important)动态样式、JS 操控样式
@import 导入@import url('xxx.css') 写在 CSS 中由选择器权重决定CSS 文件模块化(不推荐)

详细对比

① 外部样式表(推荐 ✅)

<!-- 在 <head> 中引入 -->
<link rel="stylesheet" href="styles/main.css" />
<link rel="stylesheet" href="styles/responsive.css" media="(max-width: 768px)" />

✅ 优点:

  • 结构与样式分离,HTML 更干净
  • 浏览器缓存,多个页面共用同一份 CSS,减少重复加载
  • 便于维护和复用,一处修改全局生效
  • 支持 media 属性按条件加载

❌ 缺点:

  • 额外的 HTTP 请求(可通过合并/内联关键 CSS 优化)
  • CSS 加载完成前页面会出现无样式闪烁(FOUC)

② 内部样式表

<head>
  <style>
    /* 关键首屏样式(Critical CSS)——直接内联减少请求 */
    body { margin: 0; font-family: system-ui, sans-serif; }
    .hero { min-height: 100vh; display: flex; align-items: center; }
    .hero h1 { font-size: 3rem; color: #333; }
  </style>
</head>

✅ 优点: 无额外 HTTP 请求,页面加载更快(适合 Critical CSS) ❌ 缺点: 不能跨页面复用,HTML 体积增大,无法被浏览器单独缓存

③ 行内样式

<!-- 通常由 JS 动态设置 -->
<div style="color: red; font-size: 16px; margin-top: 10px;">行内样式</div>

<!-- React/Vue 中动态绑定 -->
<!-- <div :style="{ color: isActive ? 'red' : 'gray' }">动态样式</div> -->

✅ 优点: 优先级高,适合 JS 动态控制样式 ❌ 缺点: 完全违背"结构与样式分离"原则,无法复用,不能使用伪元素/伪类/媒体查询

④ @import 导入(不推荐 ❌)

/* 在 CSS 文件或 <style> 标签中使用 */
@import url('reset.css');
@import url('variables.css');
@import url('components.css');

/* 也支持媒体查询条件 */
@import url('mobile.css') screen and (max-width: 768px);

✅ 优点: CSS 文件内部模块化组织 ❌ 缺点: 串行加载(详见 Q32),增加页面渲染延迟

优先级规则(权重从高到低)

!important > 行内样式 > ID 选择器 > 类选择器 > 标签选择器 > 继承

同权重下,后声明的覆盖先声明的(就近原则)。外部样式表和内部样式表的优先级取决于它们在 HTML 中的书写顺序,后出现的优先级更高。

💡 面试加分点: 实际项目中的最佳实践是——关键首屏 CSS 用 <style> 内联(减少首屏渲染阻塞),其余样式用 <link> 外部引入(利用缓存),避免使用 @import(串行加载问题),行内样式仅用于 JS 动态设置。现代构建工具(如 Vite、Webpack)会自动提取 Critical CSS 并内联。


32. <link> 和 @import 引入 CSS 有什么区别?

这是一道经典面试题,考察你对 CSS 加载机制的理解。

对比项<link>@import
从属关系HTML 标签CSS 语法
加载时机与 HTML 并行加载等宿主 CSS 加载完后串行加载
兼容性所有浏览器CSS 2.1+(IE5+ 支持)
DOM 可控性可通过 JS 动态创建 <link>无法通过 JS 动态插入
功能扩展支持 rel、media、preload 等属性仅支持 url() 和媒体查询
放置位置<head> 中CSS 文件顶部(@charset 之后,其他规则之前)

核心差异:加载行为

🔵 <link> 并行加载(推荐):
──────────────────────────────────────
HTML 解析:  ████████████████████████████
main.css:        ██████████
theme.css:       ████████            ← 同时加载!
渲染:                       ████████
──────────────────────────────────────
总耗时短 ✅


🔴 @import 串行加载(不推荐):
──────────────────────────────────────
HTML 解析:  ████████████████████████████████████
main.css:        ██████████
theme.css:                  ████████  ← 等 main.css 加载完才开始!
渲染:                                ████████
──────────────────────────────────────
总耗时长 ❌(多了一次往返延迟)

示例代码

<!-- ✅ 推荐:<link> 引入 -->
<head>
  <link rel="stylesheet" href="reset.css" />
  <link rel="stylesheet" href="main.css" />
  <link rel="stylesheet" href="responsive.css" media="(max-width: 768px)" />

  <!-- 还可以预加载关键 CSS -->
  <link rel="preload" href="above-fold.css" as="style" />
</head>
/* ❌ 不推荐:@import 引入(会导致串行加载) */
/* main.css */
@import url('reset.css');      /* 等 main.css 下载完才开始下载 reset.css */
@import url('variables.css');  /* 等 reset.css 下载完才开始下载 variables.css */
@import url('components.css'); /* 继续串行... */

/* 嵌套 @import 更严重:a.css → @import b.css → @import c.css → 逐级串行 */
// <link> 支持 JS 动态创建
function loadCSS(href) {
  const link = document.createElement('link')
  link.rel = 'stylesheet'
  link.href = href
  document.head.appendChild(link)
}

// 按需加载主题样式
loadCSS('themes/dark.css')

// @import 无法通过 JS 动态插入

为什么 @import 会造成串行加载?

浏览器必须先下载并解析包含 @import 的 CSS 文件,才能发现其中的 @import 声明并发起新的请求。这就形成了一个请求瀑布流(Request Waterfall),每一层 @import 都要多等待一个网络往返时间(RTT)。

💡 面试加分点: 在现代前端工程中,@import 基本被淘汰了。Sass/Less 中的 @import(以及 Sass 新推荐的 @use)在编译时就会合并为一个 CSS 文件,不存在运行时串行加载问题。CSS 原生的 @import 是运行时串行加载,性能差异巨大。如果面试官追问"有没有情况适合用 @import"——CSS 的 @layer 配合 @import 可以控制样式优先级层叠顺序,这是 2024 年的新用法。


33. 如何让 Chrome 支持小于 12px 的文字?

Chrome 浏览器(及大多数基于 Chromium 的浏览器)在中文语言环境下有一个最小字号限制:默认不允许文字小于 12px。即使你设置 font-size: 10px,实际渲染出来的也是 12px。

原因: Chrome 认为小于 12px 的中文字体可读性太差,为了保护用户阅读体验而设置了最小字号。这个限制仅在中文等 CJK 语言环境下生效,英文环境没有此限制。

解决方案

方案原理推荐度
transform: scale()CSS 缩放,视觉缩小⭐⭐⭐⭐⭐ 推荐
-webkit-text-size-adjust: none禁用字号调整⭐⭐ 新版 Chrome 已失效
使用 SVG/Canvas用图形绘制文字⭐⭐ 特殊场景
使用图片替代将小文字做成图片⭐ 不推荐

方案一:transform: scale()(最佳方案 ✅)

<span class="small-text">版权所有 © 2026</span>
<span class="mini-label">辅助说明文字</span>
/* 目标:显示 10px 大小的文字 */
.small-text {
  font-size: 12px;               /* 先设置 Chrome 允许的最小值 */
  transform: scale(0.833);        /* 12 × 0.833 ≈ 10px */
  transform-origin: left top;     /* 设置缩放基点,避免位移偏差 */
  display: inline-block;          /* transform 对行内元素无效,需要转换 */
}

/* 目标:显示 8px 大小的文字 */
.mini-label {
  font-size: 12px;
  transform: scale(0.667);        /* 12 × 0.667 ≈ 8px */
  transform-origin: left top;
  display: inline-block;
}

注意事项:

  • transform: scale() 只是视觉缩放,元素在文档流中仍然占据 12px 的空间
  • 需要手动调整 margin 或用 width/height 修正占位偏差
  • transform 对纯 inline 元素无效,需要设置 display: inline-block 或 block
/* 完善方案:修正占位偏差 */
.scale-text-wrapper {
  font-size: 12px;
  display: inline-block;
  transform: scale(0.833);
  transform-origin: left top;
  /* 修正缩放后多余的占位空间 */
  /* 原始高度 × (1 - 缩放比例) 的负 margin */
  margin-right: -17%;  /* 大约修正水平方向多占的空间 */
}

方案二:-webkit-text-size-adjust(已失效 ⚠️)

/* 早期方案,新版 Chrome 已不支持 */
html {
  -webkit-text-size-adjust: none;  /* ❌ Chrome 27+ 已移除对桌面端的支持 */
}

/* 该属性现在仅在移动端 Safari 生效,用于防止横屏时自动放大文字 */
html {
  -webkit-text-size-adjust: 100%;  /* 移动端防止文字缩放 */
  text-size-adjust: 100%;
}

方案三:SVG 文字(特殊场景)

<!-- 用 SVG 渲染小文字,不受 Chrome 字号限制 -->
<svg width="200" height="20">
  <text x="0" y="12" font-size="10" fill="#999">
    这段文字是真正的 10px 大小
  </text>
</svg>

实际开发建议

/* 封装一个通用的小字号工具类 */
.text-xxs {
  font-size: 12px;
  transform: scale(0.833);       /* 等效 10px */
  transform-origin: left center;
  display: inline-block;
}

.text-xxxs {
  font-size: 12px;
  transform: scale(0.667);       /* 等效 8px */
  transform-origin: left center;
  display: inline-block;
}

/* 在 Tailwind CSS 中可以自定义插件处理 */

💡 面试加分点: 这道题考察的不仅是解决方案,更重要的是你是否知道这个限制存在以及它的原因。回答时可以提到:① Chrome 的这个限制是语言相关的(中文环境 12px,英文环境无限制);② 用户可以在 chrome://settings/fonts 中手动修改最小字号设置;③ transform: scale() 是唯一可靠的纯 CSS 方案,但要注意它不改变元素的实际占位,需要配合布局调整。实际开发中,如果设计稿出现小于 12px 的文字,优先和设计师沟通是否可以调整为 12px。