CSS 热门面试题
使用说明
本篇共 60 道题。
CSS 面试重点通常不是背属性,而是能解释布局规则、层叠关系、渲染代价和方案取舍。下面的答案按口头回答组织,复杂题再补场景。
Q1: 标准盒模型和 IE 盒模型有什么区别?
答案:
区别在 width 和 height 是否包含内边距与边框。
content-box中设置的是内容区尺寸,最终占用还要加padding和border。border-box中设置的是边框盒尺寸,内容区会自动扣除内边距和边框。- 工程里通常全局使用
box-sizing: border-box,组件尺寸更容易推算。
Q2: 什么是 BFC?它能解决什么问题?
答案:
| 问题 | BFC 解决原理 |
|---|---|
| 浮动子元素导致父元素高度坍塌 | BFC 会包含内部浮动元素 |
| 垂直方向 margin 合并 | 不同 BFC 之间不会发生 margin 合并 |
| 浮动元素覆盖相邻元素 | BFC 不与浮动元素重叠 |
| 父子元素 margin 穿透 | BFC 创建独立容器,margin 不会穿透 |
Q3: Flex 布局中 flex: 1 通常表示什么?
答案:
常见浏览器行为下它等价于让项目可增长、可收缩,并把基础尺寸设为零附近,也就是按剩余空间分配,而不是按内容原始宽度分配。
还要注意 Flex 项目默认 min-width: auto,长文本可能不肯缩小;需要截断时常配 min-width: 0。
Q4: Flex 和 Grid 怎么选?
答案:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 导航栏项目排列 | Flex | 一维、内容驱动 |
| 卡片网格 | Grid | 二维、需要对齐行列 |
| 表单布局 | Grid | 标签和输入框需要对齐 |
| 等高列 | 都可以 | Grid 更直接 |
| 页面整体结构 | Grid | header/sidebar/main/footer |
| 按钮组 | Flex | 简单一维排列 |
| 仪表盘 | Grid | 复杂二维 + 合并区域 |
经验法则:从内向外用 Flex,从外向内用 Grid。
Q5: CSS 选择器优先级怎么计算?
答案:
可以按内联样式、ID、类/属性/伪类、元素/伪元素四级理解,同级再比较出现顺序。
!important 会改变普通层叠优先关系,但不应作为日常覆盖工具。更可维护的方式是控制作用域、选择器复杂度和 Cascade Layers,而不是不断叠加权重。
Q6: position: absolute 相对谁定位?
答案:
绝对定位元素会相对它的包含块定位。常见情况下,包含块由最近一个 position 不为 static 的祖先元素建立,定位边界通常是该祖先的 padding box;如果没有这样的祖先,则相对初始包含块定位。
但面试时最好补充:包含块不只由 position 建立,某些 transform、filter、contain 等属性也可能为后代建立包含块。因此排查定位偏差时,要沿祖先链检查这些属性,而不是只找 position: relative。
绝对定位会脱离正常文档流,但它的尺寸仍可能由 left/right/top/bottom 和内容共同决定。
Q7: position: sticky 为什么会失效?
答案:
常见原因是没有设置 top 等偏移、祖先滚动容器不是预期元素、祖先的 overflow 改变了滚动上下文,或可滚动空间不足。
排查时先确认它相对哪个容器粘住,再检查祖先尺寸、overflow 和自身所在布局,不要先用更高 z-index 猜问题。
Q8: 水平垂直居中有哪些可靠方案?
答案:
现代布局首选 Flex 或 Grid:父容器用 display: flex; align-items: center; justify-content: center,或者 Grid 的 place-items: center。
绝对定位加 transform 适合覆盖层;文本单行居中可用行高。回答时应先确认是否已知尺寸、是否脱离文档流和是否要兼容多行内容。
Q9: 什么是 margin 折叠?
答案:
垂直方向上相邻的 margin 会发生合并,取较大值而非叠加。有三种场景:
- 相邻兄弟元素的上下 margin
- 父元素与第一个/最后一个子元素的 margin
- 空块元素自身的上下 margin
解决方式:
- 创建 BFC(
overflow: hidden/display: flow-root) - 设置
border或padding隔开 - 使用 Flex/Grid 布局
- 改为
inline-block
Q10: display: none、visibility: hidden 和 opacity: 0 有什么区别?
答案:
| 特性 | display: none | visibility: hidden | opacity: 0 |
|---|---|---|---|
| 是否占据布局空间 | 否 | 是 | 是 |
| 是否参与绘制 | 否 | 不绘制内容 | 会参与合成,只是完全透明 |
| 是否响应指针事件 | 否 | 通常否 | 默认仍会 |
| 是否可聚焦 | 否 | 否 | 可能仍可 |
| 是否适合透明度过渡 | 否 | 可与其他属性组合 | 是 |
因此“看不见”不等于“不可交互”。用 opacity: 0 隐藏控件时,通常还要处理 pointer-events、焦点和可访问性;如果内容应完全退出页面结构,使用 hidden 或 display: none 更合适。
Q11: 什么是层叠上下文?为什么 z-index 很大也没用?
答案:
层叠上下文是独立的绘制层级单元,子元素的 z-index 只能在自己所在上下文内比较,无法越过父级与外部兄弟竞争。
定位元素、opacity、小于 1、transform、isolation 等都可能创建上下文。排查时看祖先上下文,而不是不断把数值加大。
Q12: 回流和重绘有什么区别?
答案:
回流是几何信息变化后重新计算布局,通常会连带重绘;重绘是外观变化但布局不变,例如颜色改变。
批量读取/写入布局属性、避免循环中交替访问 offsetWidth 和修改样式,可减少强制同步布局。动画优先用 transform 和 opacity,但仍要关注合成层数量。
Q13: 为什么 transform 动画通常更流畅?
答案:
transform 和 opacity 往往可以在合成阶段更新,避免主线程反复布局和大面积绘制,因此更适合位移和淡入淡出。
这不是绝对“走 GPU 就免费”。大图层会占显存,滤镜、阴影和复杂绘制仍可能昂贵,will-change 也只应短期、精确使用。
Q14: 响应式设计的核心思路是什么?
答案:
让布局根据可用空间和内容能力逐步适配,而不是为每个设备型号写一套页面。
- 使用弹性布局、相对单位、响应式图片和少量内容驱动断点。
- 优先移动端基础样式,再通过媒体查询增强大屏体验。
- 组件级适配可考虑 Container Queries,避免完全依赖页面视口。
Q15: px、rem、em、vw 怎么选?
答案:
px 适合明确边框和细粒度尺寸;rem 相对根字体,适合全局尺度和可访问缩放;em 相对当前元素字体,适合组件内部比例;vw 相对视口,适合流体尺寸但要设置上下限。
实际项目通常混合使用,并用 clamp() 控制流体值,避免超宽或超窄屏失控。
Q16: CSS 变量相比预处理器变量有什么不同?
答案:
CSS 自定义属性存在于运行时,参与层叠和继承,可以被媒体查询、主题容器和 JavaScript 动态修改;Sass 等变量在构建时展开,运行时不存在。
主题 Token 更适合 CSS 变量,复杂生成逻辑可由预处理器完成,二者可以配合。
Q17: 伪类和伪元素有什么区别?
答案:
| 特性 | 伪类(Pseudo-class) | 伪元素(Pseudo-element) |
|---|---|---|
| 语法 | :hover(单冒号) | ::before(双冒号) |
| 作用 | 选择元素的状态 | 创建虚拟元素 |
| 是否创建 DOM | 否 | 是(虚拟 DOM 节点) |
| 数量 | 可以叠加多个 | 同类型只能一个 |
| 示例 | :hover、:nth-child | ::before、::after |
Q18: CSS Modules、CSS-in-JS 和原子化 CSS 怎么选?
答案:
| 特性 | CSS Modules | CSS-in-JS |
|---|---|---|
| 文件 | 独立 .module.css | 写在 JS/TS 中 |
| 运行时 | 无 | 有(Runtime 方案) |
| 动态样式 | 通过 className 切换 | 通过 props 动态生成 |
| 学习成本 | 低(就是 CSS) | 中(新的 API) |
| 适用框架 | 通用 | 主要 React |
CSS Modules 更轻量、无运行时,适合不需要高度动态样式的项目。
Q19: 如何降低 CSS 对首屏性能的影响?
答案:
控制关键 CSS 体积和加载顺序,删除无用样式,避免巨大的全局选择器与重复框架样式。
按路由或组件拆分非关键样式,压缩产物,谨慎内联关键 CSS。同时监控样式引起的布局偏移和长时间样式计算,而不只看文件大小。
- 加载阶段:抽取并尽早加载首屏 Critical CSS,其余样式按路由或组件拆分。
- 体积阶段:清理未使用规则、压缩产物,避免重复引入组件库主题。
- 运行阶段:复杂选择器通常不是首要问题,更应关注会阻塞渲染的样式体积和动态布局成本。
Q20: contain 和 content-visibility 有什么价值?
答案:
contain 告诉浏览器某个子树在布局、绘制等方面相对独立,缩小失效影响;content-visibility: auto 可以跳过屏外内容的布局和绘制。
它们适合长页面和独立组件,但要提供合理的占位尺寸,并验证查找、焦点跳转和可访问性行为。
Q21: inherit、initial、unset、revert 有什么区别?
答案:
inherit:强制取父元素的计算值,不管该属性默认是否继承。initial:恢复为 CSS 规范定义的初始值,不等于浏览器默认样式。unset:可继承属性按inherit处理,不可继承属性按initial处理。revert:回退当前来源的声明,让更低优先级来源或用户代理样式重新生效。revert-layer:只回退当前 Cascade Layer,保留其他层的结果。
组件库做样式重置时要先明确希望“继承父级”“回到规范初值”还是“恢复浏览器默认表现”,不能把这些值混用。
Q22: Flex 项目为什么经常需要设置 min-width: 0?
答案:
Flex 项目的默认最小尺寸通常是 min-width: auto,它会参考内容的最小固有宽度。长单词、表格或设置了 white-space: nowrap 的文本可能因此拒绝收缩,把容器撑破。
.item {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
min-width: 0 允许项目缩小到内容固有宽度以下,省略号才有机会生效。纵向 Flex 布局出现类似溢出时,对应检查 min-height: 0。
Q23: Flex 容器的属性有哪些?
答案:
6 个容器属性 + 1 个间距属性:
| 属性 | 作用 | 默认值 |
|---|---|---|
flex-direction | 主轴方向 | row |
flex-wrap | 是否换行 | nowrap |
flex-flow | direction + wrap 简写 | row nowrap |
justify-content | 主轴对齐方式 | flex-start |
align-items | 交叉轴对齐(单行) | stretch |
align-content | 交叉轴对齐(多行) | stretch |
gap | 项目间距 | 0 |
Q24: Grid 布局的核心概念有哪些?
答案:
核心概念包括:
- Grid Container / Item:容器和直接子元素
- Grid Line:网格线,用数字或命名引用
- Grid Track:一行或一列
- Grid Cell:最小单元格
- Grid Area:一个或多个 cell 组成的矩形区域
- fr 单位:可用空间的比例份额
- gap:行列间距
Q25: min-content、max-content 和 fit-content 怎么理解?
答案:
它们描述元素的固有尺寸:
min-content:在允许换行的前提下尽可能窄,通常接近最长不可断片段。max-content:内容完全不换行时所需的宽度。fit-content:在可用空间、最小内容尺寸和最大内容尺寸之间取一个合适结果。- Grid 的
fit-content(<length>)还可以给轨道设置上限。
这组值适合自适应标签、侧栏和内容驱动的 Grid 轨道。使用时要测试超长单词、国际化文本和可替换元素,不能只用短中文样例判断。
Q26: position 有哪些值?分别什么含义?
答案:
5 个值:static(默认,正常流)、relative(相对自身偏移)、absolute(相对非 static 祖先)、fixed(相对视口)、sticky(滚动吸附)。
其中 absolute 和 fixed 脱离文档流,relative 和 sticky 不脱离。
Q27: :is() 和 :where() 有什么区别?
答案:
二者都能把一组选项写进一个选择器,主要差异是优先级:
:is()的优先级取参数列表中最高的那一个。:where()自身及其参数的优先级始终为零。- 两者都会按“宽容选择器列表”处理,不支持的分支通常不会让整个列表失效。
组件库的基础样式常用 :where() 保持易覆盖;业务条件选择可用 :is()。如果把 ID 放进 :is(),即使当前匹配的是另一分支,也可能显著抬高整个选择器的优先级。
Q28: 百分比高度为什么经常不生效?
答案:
百分比高度需要从包含块得到可确定的高度。如果父元素的 height 仍是由内容撑开的 auto,子元素写 height: 100% 往往没有可计算的基准,所以不会得到预期的满高效果。
常见处理方式:
- 页面级满高可为
html、body和应用根节点建立明确高度,或直接使用min-height: 100dvh。 - Flex/Grid 布局通常让容器确定可用空间,再通过
flex: 1或网格轨道分配。 - 只要求“至少占满”时用
min-height,避免固定高度导致内容溢出。 - 调试时沿包含块逐层检查高度是否可确定,同时留意绝对定位元素的包含块规则。
Q29: 为什么浮动会导致父元素高度塌陷?
答案:
浮动元素脱离了正常文档流,父元素在计算高度时不会将浮动子元素纳入计算,因此如果所有子元素都浮动,父元素的高度就会变为 0。
这不是 bug 而是设计如此——float 的本意是文字环绕,浮动元素不影响块级容器的高度,但行内内容会围绕它排列。
Q30: transition 和 animation 的区别?
答案:
核心区别:
transition需要触发条件(:hover、JS 改变属性),只有起始和结束两个状态animation自动播放,可通过@keyframes定义多个中间状态,支持循环、暂停
简单交互(悬停变色、展开菜单)用 transition;
复杂动画(加载动画、入场动画)用 animation。
Q31: 原生 CSS Nesting 和 Sass 嵌套有什么边界差异?
答案:
原生 CSS Nesting 在浏览器运行时直接参与 CSS 解析和层叠,不需要预处理;Sass 嵌套在构建阶段展开,还支持变量、Mixin、循环等编程能力。
实践中两者都要避免过深嵌套,因为展开后会形成高耦合、难覆盖的长选择器。使用 & 时应关注它代表父选择器整体,而不是字符串拼接工具;某些 Sass 的拼接写法不能直接迁移到原生 Nesting。
迁移时应以目标浏览器和构建链为准,通过 Browserslist/PostCSS 做兼容,而不是同时启用两套嵌套转换导致重复展开。
Q32: 移动端和多尺寸页面应该怎样做适配?
答案:
没有适用于所有页面的“唯一推荐单位”。更稳妥的做法是组合使用:
- 用 Flex/Grid 和内容驱动断点建立弹性布局。
- 页面级变化使用媒体查询,组件级变化优先考虑容器查询。
- 字体与间距可用
rem、百分比和clamp()做有上下限的流体缩放。 - 图片使用
srcset/sizes,同时处理安全区域、横竖屏和输入方式。 - 通过真实设备与极端内容验证,不根据某个设计稿宽度全局等比缩放。
vw 或 rem 转换可以是特定业务条件下的工程方案,但要限制最小/最大尺寸,并保证用户字体缩放和可访问性不被破坏。
Q33: Sass 和 Less 有什么区别?
答案:
核心区别:
- 变量:Sass 用
$,Less 用@ - 编程能力:Sass 更强(完善的条件、循环、模块系统)
- 编译:Sass 用 Dart Sass,Less 用 JavaScript
- 生态:Sass 更主流(Bootstrap、Tailwind),Less 在 Ant Design 中使用
Q34: display: contents 有什么作用和风险?
答案:
display: contents 会让元素自身不生成布局盒,但它的子元素仍像直接子项一样参与父级布局。它可用于让包装层不干扰 Grid/Flex 排布。
风险在于“没有盒”会影响背景、边框、定位和伪元素;不同浏览器与辅助技术历史上对其可访问性映射也存在过兼容问题。不要仅为了少写一层 CSS 就把有明确语义或交互职责的元素设为 contents。
使用前应验证键盘导航、可访问性树和目标浏览器,尤其不要让按钮、表格结构等关键语义依赖它。
Q35: CSS-in-JS 有什么优缺点?
答案:
优点:
- 样式与组件共存,无全局命名冲突
- 可使用 JS 的变量、函数、条件逻辑
- 自动添加浏览器前缀
- 删除组件时样式自动清理,无死代码
缺点:
- Runtime 方案有性能开销(序列化、注入)
- 增加包体积(需要运行时库)
- SSR 需要额外配置
- DevTools 中类名不易阅读
- React Server Components 不兼容 Runtime CSS-in-JS
Q36: CSS :has() 有什么用?能举例吗?
答案:
:has() 是父选择器,根据子元素或后续兄弟的存在/状态选择当前元素:
/* 表单验证 */
.input-group:has(input:invalid) { border-color: red; }
.input-group:has(input:valid) { border-color: green; }
/* 空状态 */
.list:not(:has(li)) { display: none; } /* 没有 li 时隐藏列表 */
.list:not(:has(li)) + .empty-state { display: block; }
本质上弥补了 CSS 只能"向下"选择的缺陷。
Q37: 哪些 CSS 属性会触发重排?如何避免?
答案:
改变元素几何属性的操作会触发重排:width、height、margin、padding、position、display、font-size 等。
避免方法:
- 动画用
transform代替top/left/width - 批量修改 DOM,避免逐条操作
- 使用
contain限制重排范围 - 使用
documentFragment或离屏 DOM
Q38: 圣杯布局和双飞翼布局的区别?
答案:
| 特性 | 圣杯布局 | 双飞翼布局 |
|---|---|---|
| HTML 结构 | 3 个并列元素 | main 需要额外 wrapper |
| 占位方式 | 父容器 padding | main 内部 margin |
| 定位方式 | 负 margin + position: relative | 只需负 margin |
| 主内容优先 | ✅ DOM 最先 | ✅ DOM 最先 |
| 复杂度 | 较高 | 稍低 |
两者目的相同:中间列 DOM 优先加载,左右固定宽度。现代方案直接用 Flex/Grid。
Q39: 容器查询和媒体查询应该怎样分工?
答案:
媒体查询根据视口或设备环境决定页面级布局,适合导航形态、整页栏数和用户偏好;容器查询根据组件容器的可用尺寸或样式状态调整组件,适合可复用卡片、侧栏模块和嵌入式组件。
使用尺寸容器查询时,先在容器上声明 container-type:
.card-list {
container-type: inline-size;
}
@container (min-width: 32rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}
不要把所有断点机械迁移成容器查询。页面外壳仍由媒体查询控制,内部组件再根据自身空间响应,职责更清晰。
Q40: overflow: hidden 和 overflow: clip 有什么区别?
答案:
两者都能裁剪超出 padding box 的内容,但 hidden 会建立可滚动容器,只是没有可见滚动条,内容仍可能通过脚本或焦点滚动;clip 不提供滚动机制,也不能通过 scrollTop 正常滚到被裁剪区域。
overflow: hidden 还常被用于建立 BFC,而 clip 本身不应被当作同样的 BFC 技巧;需要隔离布局时可配合 display: flow-root。
选择时先判断是“需要可编程滚动但隐藏滚动条”,还是“内容永久裁掉”。焦点可能进入不可见区域时要特别谨慎。
Q41: Flex 布局中的 gap 和子元素 margin 有什么区别?
答案:
gap 表示 Flex/Grid 项目之间的间距,只在相邻项目或轨道之间出现,不会自动给容器外边缘增加一圈间距;margin 属于元素自身,会参与外边距、自动 margin 和方向性布局计算。
实际选择时:
- 统一的行列间距优先使用
gap,不需要为首尾元素清零,也更适合换行布局。 - 某个项目需要单独偏移或用
margin-inline-start: auto占据剩余空间时使用 margin。 gap不会发生 margin collapse,也不能替代容器 padding。- 兼容旧环境时要确认 Flex gap 支持情况,而不是只看 Grid gap。
Q42: fr 单位是什么?和百分比有什么区别?
答案:
fr 代表 fraction(分数),表示剩余空间的比例:
| 特性 | fr | % |
|---|---|---|
| 参考基准 | 剩余可用空间 | 容器总宽度 |
| 是否考虑 gap | 是(自动扣除) | 否(可能溢出) |
| 溢出风险 | 低 | 高(如 3 × 33.33% + gap 会溢出) |
/* 使用百分比:加上 gap 后会溢出 */
grid-template-columns: 33.33% 33.33% 33.33%; /* + gap = 超出 100% */
/* 使用 fr:自动扣除 gap 后分配 */
grid-template-columns: 1fr 1fr 1fr; /* 完美 */
Q43: aspect-ratio 如何减少布局偏移?
答案:
aspect-ratio 在资源内容尚未加载时就为元素建立宽高比例,浏览器可以提前预留空间,减少图片、视频或异步卡片加载造成的 CLS。
.video {
width: 100%;
aspect-ratio: 16 / 9;
}
对图片优先写真实的 width 和 height 属性,浏览器会推导固有比例;CSS 的 aspect-ratio 更适合响应式容器和非图片元素。若同时给出明确宽高,比例只在存在自动尺寸的轴上参与计算。
Q44: 为什么设置 transform 后,后代的 position: fixed 可能不再相对视口?
答案:
某些会建立包含块的祖先属性,例如非 none 的 transform、perspective、部分 filter 或 contain,可能让后代 fixed 元素改为相对该祖先定位。
这会导致弹窗、吸顶层随着局部容器滚动或被其裁剪。排查步骤是沿祖先链检查包含块、overflow、层叠上下文和 Top Layer。
全局模态框通常放到应用根节点或使用 Portal;能用原生 dialog/Popover 的场景还可以利用 Top Layer 避开普通层叠上下文。
Q45: 多个 !important 声明冲突时怎么比较?
答案:
!important 不是“无条件最大”。两个重要声明冲突时,仍要按来源与层、作用域接近度、选择器优先级、源码顺序继续比较。
需要注意两点:
- 用户的重要样式可以覆盖作者的重要样式,这是可访问性设计的一部分。
- Cascade Layers 在重要声明下的层顺序与普通声明相反,较早声明的层更有保护能力。
工程上应先通过 Layer、作用域和组件 API 管理覆盖关系,只在确实需要保护的约束上使用 !important,不要靠不断增加选择器权重解决架构问题。
Q46: 移动端 1px 边框问题怎么解决?
答案:
高 DPR 设备上 1px CSS 像素可能显示为 2-3 物理像素,看起来过粗。解决方案:
/* 方案1: transform 缩放(推荐) */
.border-1px::after {
content: '';
position: absolute;
left: 0; top: 0;
width: 200%; height: 200%;
border: 1px solid #ccc;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
}
/* 方案2: 0.5px(仅 iOS Safari 支持) */
.border { border-width: 0.5px; }
/* 方案3: box-shadow */
.border { box-shadow: 0 0 0 0.5px #ccc; }
Q47: object-fit 和 object-position 应该怎样配合使用?
答案:
object-fit 决定图片或视频怎样放进元素的内容盒,object-position 决定裁剪或留白发生时内容对齐到哪里。
cover:填满盒子并保持比例,可能裁剪,常用于封面图。contain:完整展示并保持比例,可能留白,常用于商品图或预览。fill:直接拉伸到盒子,可能变形。none/scale-down:保留固有尺寸,或在none与contain中选择更小结果。
使用 cover 时可写 object-position: 50% 20% 保留人物脸部等视觉焦点。前提是替换元素本身有明确尺寸;这两个属性不会替你决定容器宽高,也不能直接作用于普通背景图。
Q48: 哪些 CSS 属性更适合做动画?
答案:
通常优先选择 transform 和 opacity,因为在满足分层条件时,它们可以只更新合成结果,避免每帧重新布局和大面积绘制。
但这不是绝对保证:
- 元素可能仍需首次绘制或重新栅格化。
- 大图层会增加显存和合成成本。
filter、阴影和大面积透明内容可能很昂贵。will-change只应在动画前短期、精确使用。
判断性能要录制 Performance/Rendering 数据,看是否发生 Layout、Paint、图层膨胀和掉帧,而不是只背“GPU 加速”。
Q49: 如何用纯 CSS 实现暗色模式?
答案:
:root {
color-scheme: light dark;
--bg: #fff;
--text: #333;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #e0e0e0;
}
}
body {
background: var(--bg);
color: var(--text);
}
加上 color-scheme: light dark 让表单元素、滚动条等自动适配。
Q50: 什么是设备像素比(DPR)?
答案:
DPR = 设备物理像素 / CSS 逻辑像素。iPhone 14 Pro 的 DPR 是 3,意味着 1 个 CSS 像素对应 3×3 = 9 个物理像素。
获取方式:window.devicePixelRatio。
DPR 影响:图片需要提供 2x/3x 版本才能在高 DPR 设备上清晰显示。更多参见图片优化。
Q51: 为什么需要 CSS 预处理器?
答案:
CSS 本身缺少编程能力,预处理器解决了:
- 代码复用:变量、Mixin 避免重复
- 可维护性:嵌套、模块化组织代码
- 计算能力:函数、运算减少硬编码
- 代码组织:
@use/@import拆分文件
但随着 CSS 原生支持了变量(var())、嵌套(&)和 calc(),预处理器的必要性正在降低。
Q52: 如何只对屏幕阅读器显示内容?
答案:
使用 .sr-only(screen reader only)模式:
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
典型用途:为图标按钮提供文字说明、为装饰性图片提供替代文本。
Q53: Tailwind CSS 的优缺点?
答案:
优点:
- 开发效率高,不用起类名
- 产物体积小(只包含使用的类)
- 一致的设计系统(间距、颜色)
- 响应式、暗色模式原生支持
- 无 CSS 文件增长问题
缺点:
- 类名冗长,HTML 可读性差
- 学习成本(需要记忆类名)
- 复杂样式需要
@apply或自定义 - 高度定制的设计较难实现
Q54: @layer 解决什么问题?
答案:
解决第三方库样式难以覆盖的问题。传统方式要用更高优先级的选择器或 !important;@layer 让你定义样式层级,不管选择器优先级如何,层级顺序决定最终胜出:
@layer third-party, custom;
/* custom 层的样式永远覆盖 third-party 层 */
Tailwind CSS v4 就使用 @layer 组织样式层级。
Q55: CSS Scroll-driven Animations 适合什么场景?
答案:
Scroll-driven Animations 用滚动进度驱动 CSS Animation,可以把“进入视口后的揭示”“阅读进度条”和“滚动容器内的时间线”交给浏览器时间轴处理,减少手写高频 scroll 监听。
核心是区分:
scroll():使用滚动容器的整体滚动进度。view():使用元素进入和离开滚动视口的可见进度。animation-timeline:把动画绑定到对应时间轴。
它适合视觉增强,不应让关键内容必须依赖动画才能理解。上线前检查目标浏览器,并通过 @supports 和 prefers-reduced-motion 提供渐进增强与减弱动画方案。
Q56: 如何实现粘性页脚?
答案:
最简单的方案:
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main { flex: 1; }
main 的 flex: 1 会撑满剩余空间,footer 自然被推到底部。
Q57: CSS 逻辑属性为什么适合国际化布局?
答案:
逻辑属性按书写方向描述布局,而不是固定使用上下左右。例如:
margin-inline对应行内方向两侧。padding-block对应块方向两侧。inset-inline-start表示行内起始位置。border-start-start-radius描述逻辑角。
同一组件切换 LTR、RTL 或竖排书写时,可以随 direction 和 writing-mode 自动映射,减少成套覆盖。涉及物理方向的图标、拖拽坐标和动画仍需单独判断,不能机械替换所有 left/right。
Q58: CSS Subgrid 解决什么布局问题?
答案:
普通嵌套 Grid 的轨道彼此独立,多个卡片内部的标题、正文和按钮难以跨卡片对齐。Subgrid 允许子 Grid 复用父 Grid 的列轨或行轨:
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
}
它适合表单标签、卡片内容和复杂页面区域的跨层对齐。使用时要明确子项在父网格中跨越的轨道范围,并检查降级布局,不要为了简单一维排列引入 Subgrid。
Q59: flex-shrink 的缩小比例怎么计算?
答案:
缩小量与 flex-shrink × flex-basis 的乘积成正比。
溢出量 = 所有项目总宽度 - 容器宽度
项目缩小量 = 溢出量 × (该项目的 shrink × basis) / (所有项目的 shrink × basis 之和)
例如容器 400px,三个项目 A(200px, shrink:1)、B(200px, shrink:2)、C(200px, shrink:1):
- 溢出 = 600 - 400 = 200px
- A 权重 = 200×1 = 200,B 权重 = 200×2 = 400,C 权重 = 200×1 = 200
- A 缩小 = 200 × 200/800 = 50px → 最终 150px
- B 缩小 = 200 × 400/800 = 100px → 最终 100px
- C 缩小 = 200 × 200/800 = 50px → 最终 150px
Q60: auto-fill 和 auto-fit 的区别?
答案:
两者在项目数量少于可填充列数时表现不同:
auto-fill:创建尽可能多的列轨道,多余轨道留空auto-fit:创建列轨道后,多余轨道折叠为 0,已有项目拉伸填满
项目数量恰好填满或大于列数时,两者表现一致。