很全面的前端面试题——CSS篇(下)

679 阅读54分钟

前言

《很全面的前端面试题——CSS篇(下)》没有《很全面的前端面试题——CSS篇(上)》重要(直言不讳)

鹦鹉兄弟摇饮料.gif

而且重要程度会按照序号越来越低(真)

各位能看到哪里就看到哪里吧(这是考验的一部分)

十八、你了解link和@import吗,两者有什么区别

link和@import在CSS中都是引入CSS样式的方法,但是两者的区别在我看来还是特别大的
下面我从六个方面横向比较link和@import的各个方面

1. 语法与位置

  • link:这是 HTML 标签,要放在 HTML 文档的<head>部分。

    <link href="styles.css" rel="stylesheet">
    
  • @import:它属于 CSS 规则,要写在 CSS 文件里,而且必须放在所有其他 CSS 规则之前。

    @import url("styles.css");
    

其实link更倾向于将CSS样式嵌入其他文件(比如HTML文件),而@import更倾向于CSS样式之间的‘内联’,比如CSS文件太大了,给他分开

2. 加载顺序

  • link:HTML 解析器在解析到这个标签时,会并行加载 CSS 资源,不会阻塞页面其他资源的加载,能加快页面的显示速度。
  • @import:CSS 引擎在处理到这条规则时才会去加载外部资源,这会导致页面加载延迟,尤其是在嵌套使用@import时,这种延迟会更加明显。

其实这个加载顺序还是很影响性能的,除此之外我们也可以看到其他的内容——处理link的是HTML解析器,但是解析@import的确是CSS 引擎,原因是两者的作用不同,就如前文所说“link更倾向于将CSS样式嵌入其他文件(比如HTML文件),而@import更倾向于CSS样式之间的‘内联’”

3. 兼容性

  • link:所有主流浏览器都支持这种方式,兼容性良好。
  • @import:虽然大多数现代浏览器都支持,但一些老旧浏览器(如 IE 5.5 及更早版本)可能会有兼容问题。

4. DOM 操作

  • link:可以通过 JavaScript 动态操作link标签,比如在运行时切换不同的样式表。

    const link = document.createElement('link');
    link.href = 'dark-mode.css';
    link.rel = 'stylesheet';
    document.head.appendChild(link);
    

@import:无法在运行时动态加载,因为它是 CSS 规则,要在 CSS 解析阶段就确定。

5. 性能表现

  • link:支持浏览器的预加载机制(如 HTTP/2 的多路复用),能更高效地加载资源。
  • @import:会增加 HTTP 请求的串行化,降低资源加载的效率,特别是在引入多个资源时,性能问题会更突出。

6. 媒体查询支持

  • link:可以直接在标签中通过media属性指定适用的媒体类型。

    <link href="print.css" rel="stylesheet" media="print">
    
  • @import:虽然也能结合媒体查询使用,但语法比较繁琐。

    @import url("print.css") print;
    

看到这里,大家心中可能会有一个疑问:“@import有啥用,怎么觉得不论哪一方面都不如link”

1.在 CSS 早期(CSS 1.0 时代),@import 是引入外部样式表的唯一方式。对于一些遗留项目或需要兼容极古老浏览器的场景,它可能是必要的选择。
如果我们弃用了@import,那么很多的老项目就会出现问题,这也是一种向下兼容

2.在其他的一些特定情况下@import在写法上也很简便,比如我们从网上下载了一些CSS文件,我们要选择性的使用时,我们便不需要在大量的CSS代码中去截取我们需要的代码,我们可以在我们的CSS文件中使用@import('...')去选择的使用

当然还有其他的原因,我就不在这里继续举例了,毕竟这不是重点,而且吸取《很全面的前端面试题——CSS篇(上)》的教训,我们要在10w字符内完成这篇文章

十九、伪类和伪元素的区别和作用

首先我们要认识一下什么是伪类和伪元素

  • 伪类通过单冒号(:)选择处于特定状态(如hover)或位置(如first-child)的已有 DOM 元素,用于动态样式控制。
  • 伪元素通过双冒号(::)创建或选择元素的特定部分(如first-letter)或虚拟内容(如after),用于扩展 DOM 的视觉表现。

举个例子方便大家理解

伪类示例(:hover

button:hover {
  background-color: red; /* 鼠标悬停时按钮变红 */
}

伪元素示例(::first-line

p::first-line {
  font-weight: bold; /* 段落首行文字加粗 */
}

伪类和伪元素最直观的区别
通过上述的定义和例子,大家应该能看到伪类和伪元素的最直观的区别便是语法[一个是:一个是::(伪元素也有:的写法,但是不建议用)]和选中元素的不同(一个选中元素,一个选中元素的一部分)

下面详细的去阐述两者的区别

  1. 语法形式

    • 伪类:使用单冒号 :,直接附加在选择器后(如 a:hover)。
    • 伪元素:使用双冒号 ::,表示对元素特定部分的操作(如 p::first-line)。
    • 兼容性补充:CSS3 引入双冒号以区分伪类,但单冒号写法(如 :before)仍被支持,建议统一使用 :: 提高可读性。
  2. 选择目标

    • 伪类:选中文档中已存在的真实 DOM 元素,基于其状态(如悬停、焦点)或位置(如首个子元素)。
    • 伪元素:创建虚拟的 DOM 片段或选择元素的特定部分(如文本首字母、元素的前置内容),这些内容不存在于 HTML 结构中,仅在渲染时出现。
  3. ★★★作用范围

    • 伪类:主要用于实现动态交互效果结构性样式,例如:

      button:active { transform: scale(0.95); } /* 点击时缩小 */
      input:required { border-color: red; }    /* 必填字段边框变红 */
      
    • 伪元素:专注于视觉装饰内容增强,例如:

      article::after { content: "© 2023"; }    /* 在文章末尾添加版权信息 */
      p::first-letter { font-size: 2em; }      /* 首字母放大 */
      
  4. 使用限制

    • 伪类:可在同一选择器中叠加多个(如 a:hover:focus),按顺序生效。
    • 伪元素:每个选择器最多使用一个(如 p::first-line),若需组合操作需通过嵌套选择器实现(如 p:first-child::first-letter)。
  5. DOM 影响

    • 伪类:仅改变元素的渲染样式,不修改 DOM 结构。
    • 伪元素:通过 content 属性动态生成内容,可视为 DOM 的 “虚拟扩展”,但无法通过 JavaScript 直接操作。

这里的难点是对伪类和伪元素在DOM层的差异,那么我们应该如何去向面试官表述呢?

1.伪类操作的是真实的DOM,伪元素是通过创建虚拟DOM来操作样式和内容
2.伪类不会改变DOM的结构,仅通过状态变化触发样式更新;伪元素则相当于添加了“视觉DOM”(通过CSS实现了DOM效果,好像增加了DOM节点一样,所以叫做‘视觉DOM’)
3.伪类可以被js直接操作,伪元素却不可以

对于第三点,补充一个例子

JS可以直接操作伪类选中的元素

<a href="#" class="link">示例链接</a>
.link:hover { color: red; }
const hoveredLink = document.querySelector('.link:hover');
const link = document.querySelector('.link');
link.addEventListener('mouseenter', () => {
  const color = getComputedStyle(link).color; 
  console.log(color); // 输出"rgb(255, 0, 0)"(红色)
});

JS只可间接操作伪元素选中的元素部分

// 示例:通过修改类切换::before的内容
document.querySelector('.btn').classList.add('hide-icon');

.btn::before { content: "👉"; }
.btn.hide-icon::before { content: ""; }

二十、CSS中的可继承与不可继承属性

可继承属性会自动应用到元素的子元素,而不可继承属性仅作用于元素本身。

一、可继承属性(默认传递给子元素)

1. 文本相关属性

  • color(文本颜色)
  • font-family(字体族)
  • font-size(字体大小)
  • font-style(字体样式,如斜体)
  • font-weight(字体粗细)
  • line-height(行高)
  • text-align(文本对齐方式)
  • text-indent(首行缩进)
  • text-transform(文本大小写转换)
  • letter-spacing(字符间距)
  • word-spacing(单词间距)

2. 列表相关属性

  • list-style(列表样式)
  • list-style-type(列表项标记类型)
  • list-style-position(列表项标记位置)

3. 表格相关属性(部分)

  • border-collapse(边框合并模式)
  • caption-side(表格标题位置)

4. 其他

  • visibility(元素可见性)
  • cursor(鼠标指针样式)

二、不可继承属性(默认仅作用于元素本身)

1. 盒模型相关属性

  • width/height(宽度 / 高度)
  • margin(外边距)
  • padding(内边距)
  • border(边框)
  • box-sizing(盒模型计算方式)
  • display(显示模式)
  • overflow(溢出处理)

2. 定位与布局

  • position(定位方式)
  • top/right/bottom/left(定位偏移量)
  • float(浮动)
  • clear(清除浮动)
  • flex(Flexbox 属性)
  • grid(Grid 布局属性)
  • z-index(层叠顺序)

3. 背景与视觉效果

  • background(背景)
  • background-color(背景颜色)
  • background-image(背景图像)
  • opacity(透明度)
  • transform(变换)
  • animation(动画)
  • transition(过渡效果)

4. 盒子装饰

  • border-radius(圆角)
  • box-shadow(阴影)
  • outline(轮廓)

5. 交互属性

  • pointer-events(指针事件)
  • user-select(文本选择)
  • touch-action(触摸行为)

凡事总有意外,其实还有一种操作继承的方法

强制继承:使用 inherit 关键字可让不可继承属性强制继承。

.child {
  border: inherit; /* 强制继承父元素的边框 */
}

那么问题来了,这么多的属性怎么呢记得住呢?面试官问的时候我们只能一字不差的背下来吗?

我们其实可以总结以下的规律

  • 可继承属性多与文本内容相关(如字体、颜色、对齐方式)。
  • 不可继承属性多与元素布局视觉表现相关(如盒模型、定位、背景)。

二十一、常见的图片格式及使用场景

这一个面试题,说实话,很烦人,要记得一大堆,这里以表格的形式展示给大家

其中,最重要的是JPG、PNG、WebP、SVG、AVIF,至于其他的能记就记一下,记不了就算了

局限性典型使用场景
JPG位图有损压缩,24 位真彩色,无透明 / 动画体积小,色彩丰富,兼容性极佳压缩损失细节,不支持透明 / 动画照片、产品图、网页横幅等色彩丰富且对细节要求适中的场景
PNG位图无损压缩,分 PNG-8(256 色,1 位透明)和 PNG-24(真彩色,8 位透明),无动画无损画质,支持透明(半透明渐变)体积较大,不支持动画图标、Logo、截图、需要透明背景的图形(如按钮、图表)
GIF位图8 位色(256 色以内),支持简单动画和 1 位透明(全透明 / 不透明),无损压缩支持动画,兼容性强色彩少,半透明效果差,复杂图体积大简单动画(加载动效、表情包)、色彩简单的图标
WebP位图支持有损 / 无损压缩,比 JPG 小 25%-35%、比 PNG 小 50%,支持透明和动画高压缩效率,兼顾画质与体积老旧浏览器(如 IE)不支持网页优化(替代 JPG/PNG)、透明图像、简短动画
AVIF位图新一代格式,压缩效率超 WebP(小 20%-30%),支持 HDR、透明和动画画质最佳,体积最小,支持 HDR兼容性有限,编码速度慢高清照片、图像密集型网站、对加载速度和画质要求极高的场景
BMP位图无压缩或简单压缩,体积极大,保留原始像素数据无损高精度体积过大,不适合网络传输早期 Windows 系统图像、印刷原始素材(不考虑体积时)
SVG矢量图基于 XML 的文本格式,由路径描述图形,支持缩放、动画、交互无限放大不失真,体积小,支持响应式不适合复杂写实图像(代码冗长)图标、Logo、图表、地图、响应式设计元素(需多尺寸适配的图形)
AI矢量图Adobe Illustrator 原生格式,支持复杂矢量图形和图层专业设计源文件,可导出多种格式不可直接用于网页展示专业设计环节(Logo、插画创作),作为源文件保存
TIFF位图无损压缩,支持高色彩深度(如 CMYK),体积大高精度,适合印刷兼容性差,体积过大印刷行业原始素材(杂志、海报)、专业摄影后期(保留编辑细节)
ICO位图专门用于图标,支持多尺寸和透明适配图标场景,兼容性强用途单一网站 favicon(浏览器标签页图标)、软件快捷方式图标

二十二、对CSSsprites 的理解

什么是CSS sprites

CSS Sprites 的本质:把网页中多个小图片(如图标、按钮)“打包” 成一张大图,然后通过 CSS 控制只显示大图中的某一部分,从而减少图片加载的次数。
打个恰当的比方
想象你是一家餐厅的服务员,顾客点了 3 道菜:可乐、汉堡、薯条。

  • 没用到 Sprites 的情况:你需要跑 3 趟厨房,分别拿可乐、汉堡、薯条(3 次 “请求”)。
  • 用到 Sprites 的情况:厨师把 3 道菜装在一个托盘里,你一趟就全拿过来了(1 次 “请求”)。

如何使用CSS Sprites

假设我们有 3 个小图标:
|首页图标(20x20 像素)|
|搜索图标(20x20 像素)|
|用户图标(20x20 像素)|

我们把三个图标合成一个大图标

|首页图标|搜索图标|用户图标|

接下来,用 CSS 的background-position属性 “定位” 到每个图标:

  • 要显示 “首页图标”:直接显示大图的最左边(background-position: 0 0)。

  • 要显示 “搜索图标”:把大图向左移动 20px(刚好露出第二个图标,background-position: -20px 0)。

  • 要显示 “用户图标”:把大图向左移动 40px(露出第三个图标,background-position: -40px 0)。

代码举例(直观对应):

/* 共用的基础样式:引用大图,固定大小 */
.icon {
  background-image: url("all-icons.png"); /* 这是合并后的大图 */
  background-repeat: no-repeat; /* 不重复显示 */
  width: 20px; /* 每个图标的宽 */
  height: 20px; /* 每个图标的高 */
  display: inline-block; /* 让图标能并排显示 */
}

/* 显示首页图标:定位到最左边 */
.icon-home {
  background-position: 0 0; /* 第一个数值是左右位置(0=不偏移),第二个是上下位置 */
}

/* 显示搜索图标:向左移20px(刚好跳过第一个图标) */
.icon-search {
  background-position: -20px 0; /* 负号表示向左移 */
}

/* 显示用户图标:向左移40px(跳过前两个图标) */
.icon-user {
  background-position: -40px 0;
}

在 HTML 中使用:

<!-- 只显示首页图标 -->
<div class="icon icon-home"></div>
<!-- 只显示搜索图标 -->
<div class="icon icon-search"></div>

也就是我们有一张大图,我们通过控制图中的局部位置,展示不同的小图

下面给大家做个演示

GIF.gif

当我们分析一项技术,必不可少的就是该技术的优缺点,CSS Sprites也不例外

核心优势

  1. 减少加载时间,提升网页速度
    浏览器加载图片时,每加载一张就要发一次 “请求”,而浏览器同时能处理的请求数量有限(比如 HTTP/1.1 时代最多同时处理 6 个)。

    • 10 个小图标 = 10 次请求,可能需要排队加载。
    • 1 张包含 10 个图标的大图 = 1 次请求,一次性加载完成。
      举例:手机端网页加载速度慢,用 Sprites 能明显减少等待时间。
  2. 减少图片 “闪烁”
    单独加载小图标时,可能出现 “先显示 A 图标,过一会儿 B 图标才出来” 的情况(因为加载有先后)。用 Sprites 时,大图一次性加载完,图标会同时显示,更流畅。

  3. 方便管理图标状态
    比如一个按钮有 “正常”“ hover(鼠标悬停)”“点击” 3 种状态,把这 3 种状态的图片合并成一张,用background-position切换,无需额外加载新图。
    例:

    .btn {
      background-image: url("btn-states.png");
      width: 100px;
      height: 40px;
    }
    .btn:hover {
      background-position: 0 -40px; /* 悬停时显示第二行的图 */
    }
    .btn:active {
      background-position: 0 -80px; /* 点击时显示第三行的图 */
    }
    

缺点

  1. 修改麻烦
    若要新增或删除一个图标,需要重新制作整张大图,还要重新计算每个图标的位置(background-position的值)。 (不过现在有工具能自动生成位置,比如在线 Sprite 生成器,输入图标后自动输出大图和 CSS 代码)。
  2. 大图可能 “浪费” 空间
    ** 图标之间需要留一点空隙(避免显示时 “露边”),这些空隙会让大图体积比单独小图的总和略大**(但通常影响很小,压缩后可忽略)。
  3. 不适合大图片或动态图标
    Sprites 只适合小图标(如 20x20px、50x50px),大图片合并后体积太大;动态变化的图标(如需要放大 10 倍)也不适合,因为大图放大后会模糊。

二十三、CSS预处理器/后处理器是什么?为什么要使用它们

CSS预处理器和CSS后处理器并不是同一个概念,下面我们分别来讲解两者

一、CSS 预处理器(Preprocessor)

定义是一种 “增强版 CSS 语言”,需要通过编译工具转换成原生 CSS 才能被浏览器识别。它在原生 CSS 语法基础上,新增了变量、嵌套、混入(Mixin)、继承、函数等特性,让样式代码更简洁、逻辑更清晰。

常见工具

  • Sass/SCSS:最流行的预处理器之一,Sass 有两种语法(缩进式 .sass 和类 CSS 的 .scss,后者更常用)。
  • Less:语法接近 CSS,学习成本低,支持实时编译。
  • Stylus:语法灵活,可省略括号、分号,适合追求极简风格的开发。

核心特性举例(以 SCSS 为例)

  1. 变量
    可定义重复使用的值(如颜色、尺寸),便于全局修改:

    $primary-color: #3498db; // 定义变量
    .button {
      background: $primary-color; // 引用变量
    }
    
  2. 嵌套
    按 HTML 结构嵌套 CSS 选择器,避免重复书写父级选择器:

    .nav {
      ul {
        margin: 0;
        li {
          float: left;
          a {
            color: #333;
          }
        }
      }
    }
    

    编译后生成:

    .nav ul { margin: 0; }
    .nav ul li { float: left; }
    .nav ul li a { color: #333; }
    
  3. 混入
    封装可复用的样式片段(类似函数),支持参数传递:

    // 定义一个带参数的混入(用于圆角)
    @mixin rounded($radius: 4px) {
      border-radius: $radius;
    }
    .btn {
      @include rounded(6px); // 引用混入,传递参数
    }
    

    编译后生成:

    .btn { border-radius: 6px; }
    
  4. 继承
    复用其他选择器的样式,减少代码冗余:

    .base-btn {
      padding: 8px 16px;
      border: none;
    }
    .primary-btn {
      @extend .base-btn; // 继承基础按钮样式
      background: blue;
    }
    

二、CSS 后处理器(Postprocessor)

定义是对已编写的原生 CSS(或预处理器编译后的 CSS) 进行二次处理的工具。它的作用是优化 CSS 代码(如自动补全前缀、压缩代码、语法校验等),让代码更兼容、更高效。

常见工具

  • PostCSS:最流行的后处理器,通过插件机制实现各种功能(如 autoprefixer 自动补前缀)。
  • cssnano:专注于压缩 CSS 代码,移除冗余空格、注释等。
  • Stylelint:CSS 语法检查工具,规范代码风格。

核心功能举例

  1. 自动添加浏览器前缀(Autoprefixer)
    不同浏览器对 CSS 属性的前缀要求不同(如 -webkit--moz-),后处理器可根据配置自动补全:
    输入(原生 CSS):

    .box {
      display: flex;
    }
    

    处理后(兼容多浏览器):

    .box {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
    }
    
  2. 代码压缩与优化
    移除注释、空格,合并重复样式,减小文件体积:
    输入:

    /* 这是注释 */
    .nav {
      margin: 0;
      padding: 0;
    }
    

    处理后:

    .nav{margin:0;padding:0}
    

★★★三、为什么要使用预处理器 / 后处理器?

  1. 提升开发效率

    • 预处理器的变量、嵌套等特性减少重复代码,让样式结构更清晰(尤其适合大型项目)。
    • 后处理器自动处理兼容性、压缩等机械工作,减少人工操作。
  2. 增强代码可维护性

    • 预处理器的模块化(如 Sass 的 @import)可拆分样式文件(如 _header.scss_footer.scss),便于团队协作。
    • 变量集中管理,修改全局样式(如主题色)只需改一处。
  3. 解决原生 CSS 局限性

    • 原生 CSS 无逻辑控制(如条件判断、循环),预处理器通过 @if@for 等语法弥补这一缺陷。
    • 后处理器解决浏览器兼容性问题,无需手动写前缀。
  4. 规范代码风格
    预处理器的语法约束和后处理器的 lint 工具(如 Stylelint)可统一团队代码风格,减少冲突。

  • 预处理器:在编写阶段增强 CSS 功能,让代码更 “可编程”(需编译成原生 CSS)。
  • 后处理器:在编译后优化 CSS 代码,解决兼容性、性能等问题。

两者通常配合使用(如用 Sass 编写 → 编译成 CSS → 用 PostCSS 补前缀 → 用 cssnano 压缩),形成完整的 CSS 工作流,尤其在中大型项目中能显著提升开发体验和项目质量。

二十四、对line-height 的理解

line-height 指的是两行文本基线之间的垂直距离(而非行与行之间的空白距离)。

  • 基线(Baseline) :是文本排版中的基准线,对于字母来说,是小写字母(如 x)底部对齐的线(类似手写时的横线)。
  • 直观来看,line-height 决定了一行文本所占据的垂直空间高度(包括文本本身高度 + 上下空白区域)。

下面是一个演示,方便大家直观的理解line-height

GIF.gif

那么我们如何去赋值line-height呢?

line-height 支持四种主要赋值方式

1. 无单位数值(推荐)

p {
  line-height: 1.5; /* 基于当前字体大小的倍数 */
}

优势:继承时会基于子元素的 font-size 重新计算,避免固定值导致的问题。

2. 百分比

h1 {
  line-height: 120%; /* 基于当前字体大小的百分比 */
}
  • 劣势:计算后的值会被继承,子元素无法基于自身 font-size 动态调整。
  • 适用场景:少数需要精确控制行高的场景(如标题)。

与无单位数值相比,劣势是子元素会继承父元素的行高,如果子元素的字体大小改变,行高并不会随之改变

3. 固定长度单位

.button {
  line-height: 24px; /* 固定像素值 */
}
  • 劣势:不随字体大小变化,不利于响应式设计。
  • 适用场景:固定高度的 UI 组件(如按钮、导航项)。

4. 关键字 normal

body {
  line-height: normal; /* 浏览器默认值,通常约为1.2 */
}
  • 特性:不同浏览器和字体可能有差异,缺乏精确控制。
  • 适用场景:临时性样式或无需精确控制的场景。

二十五、Sass、Less是什么?为什么要使用他们

该问题在CSS预处理器/后处理器是什么?为什么要使用它们中有提到,这里不再赘述

二十六、对媒体查询的理解?

媒体查询(Media Queries)是 CSS3 引入的核心响应式设计技术,通过检测设备特性(如屏幕宽度、分辨率、方向等),针对性地应用不同样式规则,实现 “同一网站在不同设备上呈现最佳体验” 的目标。

对于这部分知识的学习,建议大家联系条件查询之类的知识

★★★媒体查询核心是查询,查询的结果用作CSS的条件,去控制不同媒体下的CSS布局

一、核心语法与结构

媒体查询由 媒体类型  和 媒体特性  组成,使用 @media 规则定义:

/* 基本语法 */
@media (媒体特性: 值) {
  /* 符合条件时应用的样式 */
  selector {
    property: value;
  }
}

/* 示例:屏幕宽度≤768px时,导航栏改为垂直排列 */
@media (max-width: 768px) {
  .nav {
    flex-direction: column;
  }
}

二、关键组成部分

1. 媒体类型(可选)

限定查询适用的设备类别:

  • all(默认):所有设备
  • screen:屏幕设备(电脑、平板、手机)
  • print:打印预览模式
  • speech:屏幕阅读器等语音设备
/* 仅在打印时应用的样式 */
@media print {
  body {
    font-size: 12pt;
  }
}

2. 媒体特性(必选)

检测设备的具体特性,常用特性包括:

特性说明示例
width视口(viewport)宽度(min-width: 768px)
height视口高度(max-height: 600px)
orientation设备方向(横向 / 纵向)(orientation: landscape)
resolution设备分辨率(DPI/DPCM)(min-resolution: 300dpi)
aspect-ratio视口宽高比(aspect-ratio: 16/9)
prefers-color-scheme用户系统主题偏好(light 或 dark(prefers-color-scheme: dark)

三、逻辑操作符

组合多个条件,增强查询灵活性:

  • and:同时满足多个条件

    @media (min-width: 768px) and (max-width: 1024px) {
      /* 平板设备样式 */
    }
    
  • ,(逗号) :满足任一条件

    @media (max-width: 600px), (orientation: portrait) {
      /* 小屏幕或竖屏样式 */
    }
    
  • not:排除特定条件

    @media not all and (monochrome) {
      /* 非单色屏幕样式 */
    }
    

四、断点设计

根据常见设备尺寸定义响应式断点,确保在关键宽度下样式平滑过渡:

1. 移动端优先(Mobile First)

以小屏幕为基础,逐步添加更大屏幕的样式:

/* 基础样式(移动设备) */
.container {
  width: 100%;
}

/* 平板及以上 */
@media (min-width: 768px) {
  .container {
    width: 750px;
  }
}

/* 桌面 */
@media (min-width: 1024px) {
  .container {
    width: 970px;
  }
}

2. 桌面端优先(Desktop First)

以大屏幕为基础,逐步调整小屏幕样式:

/* 桌面样式 */
.container {
  width: 970px;
}

/* 平板 */
@media (max-width: 1023px) {
  .container {
    width: 750px;
  }
}

/* 移动设备 */
@media (max-width: 767px) {
  .container {
    width: 100%;
  }
}

3. 常用断点参考

/* 超小屏幕(手机) */
@media (max-width: 575px) { ... }

/* 小屏幕(平板竖屏) */
@media (min-width: 576px) and (max-width: 767px) { ... }

/* 中等屏幕(平板横屏) */
@media (min-width: 768px) and (max-width: 991px) { ... }

/* 大屏幕(笔记本) */
@media (min-width: 992px) and (max-width: 1199px) { ... }

/* 超大屏幕(桌面) */
@media (min-width: 1200px) { ... }

媒体查询是响应式设计的基石,通过灵活检测设备特性,实现 “一套代码,多端适配”。合理使用断点、结合现代布局技术(如 Flexbox/Grid),可以构建出既美观又易用的跨设备网站。

二十七、常见的CSS布局单位

在 CSS 中,布局单位用于定义元素的尺寸、间距、定位等属性,不同单位适用于不同的场景。常见的 CSS 布局单位可分为相对单位和绝对单位两大类

一、绝对单位

绝对单位是固定的长度单位,不随父元素或视口的变化而改变,适用于需要精确控制尺寸的场景(如打印样式)。

1.px(像素)

  • 最常用的绝对单位,1px 对应屏幕上的一个物理像素点(但现代屏幕可能存在缩放,实际物理像素可能不同)。
  • 示例:width: 200px; 表示元素宽度为 200 像素。
  • 特点:尺寸固定,不受其他元素影响,适合固定布局。

2.cm(厘米)、mm(毫米)

  • 基于物理长度的单位,常用于打印样式。
  • 示例:height: 5cm; 表示元素高度为 5 厘米。

3.in(英寸)

  • 1 英寸 = 2.54 厘米,同样多用于打印。
  • 示例:padding: 0.5in; 表示内边距为 0.5 英寸。

4.pt(点)、pc(派卡)

  • 印刷行业常用单位,1pt = 1/72 英寸,1pc = 12pt。
  • 示例:font-size: 12pt; 表示字体大小为 12 点。

二、相对单位

相对单位的尺寸依赖于其他元素(如父元素、根元素)或视口的大小,适合响应式布局,能自适应不同屏幕尺寸。

1. 相对于父元素的单位

(1)em

  • 相对于当前元素的font-size(若未设置,则继承父元素的font-size)。
  • 示例:父元素font-size: 16px,子元素width: 2em 等价于 32px
  • 注意:嵌套元素会继承父元素的em计算,可能导致尺寸叠加(如多层嵌套时)。

(2)%(百分比)

  • 相对于父元素对应属性的百分比(如宽度相对于父元素宽度,高度相对于父元素高度)。
  • 示例:width: 50%; 表示元素宽度为父元素宽度的 50%;line-height: 150%; 表示行高为当前字体大小的 1.5 倍。

2. 相对于根元素的单位

(1)rem(root em)

  • 相对于根元素(<html>)的font-size,避免了em的嵌套叠加问题。
  • 示例:根元素font-size: 16px,元素padding: 2rem 等价于 32px
  • 优势:全局统一基准(通常设置根元素font-size: 62.5%使 1rem = 10px,便于计算),适合响应式布局。

rem是实现响应式的一个重要的考点,具体的详细讲解可以看我之前的一篇文章前端必备技能 | 使用rem实现移动页面 - 掘金

工程化使用rem可以看我的另一篇文章,里面有移动端适配的部分 面试必备 | React项目的一些优化方案(持续更新......) - 掘金

3. 相对于视口的单位(视口单位)

视口(viewport)指浏览器的可见区域,视口单位常用于全屏布局或响应式组件。

(1)vw(视口宽度) :1vw = 视口宽度的 1%。

  • 示例:width: 100vw; 表示元素宽度与视口宽度一致。

(2)vh(视口高度) :1vh = 视口高度的 1%。

  • 示例:height: 50vh; 表示元素高度为视口高度的一半。

(3)vmin(视口最小值) :1vmin = min (1vw, 1vh),取视口宽高中的较小值的 1%。

  • 示例:正方形元素width: 20vmin; height: 20vmin;,在横屏时以高度为基准,竖屏时以宽度为基准。

(4)vmax(视口最大值) :1vmax = max (1vw, 1vh),取视口宽高中的较大值的 1%。

  • 示例:font-size: 5vmax; 确保字体在大屏幕上足够大。

4. 其他相对单位

(1)ch

  • 相对于当前字体中 “0”(零)字符的宽度,适合等宽字体(如代码)。
  • 示例:width: 40ch; 表示元素宽度约为 40 个 “0” 字符的宽度。

(2)ex

  • 相对于当前字体中 “x” 字符的高度(基线到 x-height 的距离),较少使用。

三、你在实际的开发中,如何挑选布局单位?

  • 响应式布局:优先使用 rem(全局统一)、vw/vh(视口适配)、%(父元素依赖)。
  • 固定尺寸:使用 px(如按钮、图标)。
  • 字体相关:em(基于当前字体)、rem(全局字体基准)。
  • 打印样式:使用 cmmmpt 等物理单位。

二十八、px、em、rem的区别及使用场景

这个问题在常见的CSS布局单位中有详细的解释,这里不再赘述

二十九、什么是margin重叠问题?如何解决?

什么是margin重叠问题

Margin 重叠(Margin Collapse)是 CSS 中一个常见的现象,当两个垂直相邻的块级元素的 margin 相遇时,它们会合并成一个 margin,其大小为两者中的较大者,而不是两者的总和。

下图是一个实示例

image.png

一般情况下,发生margin重叠有三种情况——

1. 相邻兄弟元素之间

相邻的两个块级元素的垂直 margin 会合并:

<style>
  p {
    margin-top: 20px;
    margin-bottom: 30px; /* 与下一个p的margin-top合并,最终间距为30px */
  }
</style>
<p>第一段文本</p> <!-- 底部margin: 30px -->
<p>第二段文本</p> <!-- 顶部margin: 20px -->

结果两段文本之间的间距为30px(取较大值),而非 50px(20px + 30px)。

GIF.gif

2. 父子元素之间(无内容分隔)

若父元素没有内容(如没有内边距或边框)分隔,子元素的 margin 会 “溢出” 到父元素外部:

<style>
  .parent {
    background: lightblue;
    margin-top: 20px;
  }
  .child {
    margin-top: 30px; /* 溢出到父元素,导致父元素整体下移30px */
  }
</style>
<div class="parent">
  <div class="child">子元素</div>
</div>

结果父元素的顶部 margin 被忽略,实际间距由子元素的 margin 决定(30px)。

GIF.gif

3. 空块级元素自身

没有内容、内边距或边框的空块级元素,其上下 margin 会合并:

<style>
  .empty {
    margin-top: 20px;
    margin-bottom: 30px; /* 合并为30px */
  }
</style>
<div class="empty"></div>

结果空元素的实际高度为 30px,而非 50px。

GIF.gif

那么我们如何去解决它呢?

1. 相邻兄弟元素

  • 使用 padding 代替 margin:通过设置父元素的内边距控制间距。

    .container {
      padding-top: 20px;
      padding-bottom: 20px;
    }
    .item {
      /* 移除margin,改用容器的padding */
    }
    
  • 使用 flexbox/grid 布局:Flexbox 和 Grid 中的子元素不会发生 margin 重叠。

    .container {
      display: flex;
      flex-direction: column;
      gap: 20px; /* 使用gap代替margin */
    }
    

2. 父子元素

  • 为父元素添加内边距或边框:分隔父子元素的 margin。

    .parent {
      padding-top: 1px; /* 最小分隔 */
      /* 或 border-top: 1px solid transparent; */
    }
    
  • 触发 BFC(块级格式化上下文) :父元素设置 overflow: hidden 或其他 BFC 触发条件。

    .parent {
      overflow: hidden; /* 触发BFC,隔离子元素的margin */
    }
    

3. 空块级元素

  • 为元素添加内容、内边距或边框:阻止 margin 合并。

    .empty {
      padding-top: 1px; /* 最小内容分隔 */
    }
    

何时应保留 margin 重叠?

在某些场景下,margin 重叠是符合预期的,例如:

  • 文本段落之间的间距:保持一致的行间距。
  • 列表项之间的间距:统一的视觉效果。

三十、display、float、position的关系

三十一、实现一个扇形

关于这个题,我的建议是记住有SVGCanvasCSS clip-path这三种方法,然后会手写给面试官演示一下CSS transform,关于CSS transform的原理,在前文的实现一个三角形有提到

在前端开发中,实现扇形有多种方式,常见的包括SVGCSS transformCanvasCSS clip-path

方案一:使用 SVG

SVG 是矢量图形,天然适合绘制扇形,无需考虑缩放失真问题。

<svg width="200" height="200" viewBox="0 0 200 200">
  <path d="M100,100 L100,0 A100,100 0 0,1 170,70 Z" 
        fill="#3B82F6" 
        stroke="#1E40AF" 
        stroke-width="2"/>
</svg>

使用 path 元素的 A 命令绘制圆弧参数说明:A rx,ry x-axis-rotation large-arc-flag sweep-flag x,y``large-arc-flag:0 表示小弧,1 表示大弧sweep-flag:0 表示逆时针,1 表示顺时针

方案二:使用 CSS border(适用于固定角度)

通过将元素的宽高设为 0,利用 border 的特性(每个方向的 border 会形成等腰直角三角形),当四条边的 border 宽度相等且设置 border-radius 为相同值时,四条边的拼接点会被圆角平滑过渡。此时,若仅显示一条边的颜色(其他边设为透明),则该边的三角形会被圆角切割成 90° 扇形;若显示相邻两条边的颜色,则形成 180° 扇形。由于每个方向的 border 固定对应 90°,因此这种方法只能实现特定角度(如 90°、180°)的扇形,无法动态调整任意角度。

<style>
.sector {
  width: 0;
  height: 0;
  border: 100px solid transparent;
  border-top-color: #3B82F6;
  border-radius: 100px;
}
</style>
<div class="sector"></div>

限制:只能实现 90° 或 180° 的扇形,角度不可动态调整。

方案三:使用 Canvas(动态绘制)

适合需要 JavaScript 动态生成或交互的场景。

<canvas id="sector" width="200" height="200"></canvas>
<script>
  const canvas = document.getElementById('sector');
  const ctx = canvas.getContext('2d');
  
  // 绘制扇形
  ctx.beginPath();
  ctx.moveTo(100, 100); // 圆心
  ctx.arc(100, 100, 80, 0, Math.PI * 1.5); // 圆弧
  ctx.closePath();
  
  ctx.fillStyle = '#3B82F6';
  ctx.fill();
  ctx.strokeStyle = '#1E40AF';
  ctx.lineWidth = 2;
  ctx.stroke();
</script>

可以精确控制角度(通过弧度值),支持动画和交互。

方案四:使用 CSS clip-path(现代浏览器)

通过裁剪圆形实现扇形效果。

<style>
.sector {
  width: 200px;
  height: 200px;
  background: #3B82F6;
  border-radius: 50%;
  clip-path: polygon(50% 50%, 50% 0, 100% 50%);
}
</style>
<div class="sector"></div>

clip-path 的兼容性较差(IE 不支持),且角度调整需要复杂计算。

方案一览

方案优势劣势适用场景
SVG矢量图形,无失真,支持动画需熟悉 path 语法静态或动态扇形,响应式设计
CSS border实现简单,无需额外标签角度固定,灵活性差固定角度的装饰性扇形
Canvas完全动态,支持复杂交互需 JavaScript 编程数据可视化(如饼图)
clip-path纯 CSS 实现,代码简洁兼容性差,计算复杂现代浏览器中的简单扇形

三十二、画一条0.5px的线

1.为什么会有这样的问题

在标准屏幕(DPR=1)上,CSS 最小只能渲染 1px 的线,因为浏览器会对小数像素进行四舍五入。但在高 DPI 屏幕(如 Retina)上,1CSS 像素可能对应 2 或 3 物理像素,此时可以通过特殊手段渲染 0.5px 的视觉效果。

2. 解决方案

方案一:transform 缩放(兼容性最佳)

浏览器无法直接渲染 0.5px,但可以渲染 1px。通过 transform: scaleY(0.5) 将 1px 的元素在垂直方向上缩小 50%,视觉上变为 0.5px。

.thin-line {
  height: 1px;
  background: #000;
  transform: scaleY(0.5);
  transform-origin: top;
}

transform 触发的是 合成层渲染,不会触发重排,性能优于直接修改 height,适合高频动画场景。

方案二:SVG(真正的 0.5px,无模糊)

<svg width="100%" height="0.5" xmlns="http://www.w3.org/2000/svg">
  <line x1="0" y1="0" x2="100%" y2="0" stroke="#000" />
</svg>
  • 优点:矢量图形,精确渲染 0.5px,不受屏幕分辨率影响
  • 缺点:需要额外文件或内联代码

方案三:viewport 单位(现代浏览器)

1vh 等于视口高度的 1%。在 720px 高的屏幕上,1vh = 7.2px,因此 0.069444vh ≈ 0.5px。这种方式直接将 CSS 单位映射到物理像素上。

.thin-line {
  height: 0.069444vh; /* 假设屏幕高720px,0.069444vh ≈ 0.5px */
  background: #000;
}

方案四:媒体查询 + 缩放(适配不同 DPI)

通过 device-pixel-ratio 媒体查询识别高 DPI 屏幕(如 Retina),仅在这些设备上应用缩放,避免在标准屏幕上因缩放导致的模糊问题。

.thin-line {
  height: 1px;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .thin-line {
    transform: scaleY(0.5);
  }
}

3.实际项目中你如何运用

实现 0.5px 线的核心是利用缩放、SVG 或 viewport 单位绕过浏览器限制。实际开发中,我会根据项目需求选择方案:如果是追求兼容性的 PC 端项目,用 transform;如果是移动端且需要高精度,用 SVG 或 viewport;同时会结合 JavaScript 检测设备特性做降级处理,确保在不同环境下都有最佳体验

三十三、设置小于12px的字体

这道题考察什么

这个问题的关键在于认识到浏览器对字体大小存在默认限制(如多数浏览器默认最小字体为 12px) ,直接通过font-size设置小于该值的尺寸可能无法生效,因此需要跳出 “直接设置像素值” 的思维,转而利用浏览器渲染特性(如缩放、矢量渲染、动态单位计算) 实现视觉上的小字体效果,同时还要兼顾跨平台兼容性、布局影响和用户可读性 —— 这本质上是对 “前端开发中设计需求与浏览器限制之间矛盾如何解决” 的核心认知考察。

解决方案

方案一:使用 transform 缩放(兼容性最佳)

通过 transform: scale() 缩小整个元素,间接实现小字体:

.small-font {
  font-size: 12px;  /* 先设为浏览器允许的最小值 */
  transform: scale(0.833);  /* 缩小到10px效果 (10/12≈0.833) */
  transform-origin: left center;  /* 从左侧开始缩放,避免位置偏移 */
  display: inline-block;  /* 确保transform正确应用 */
}

原理先将字体设为浏览器允许的最小值(通常 12px),再通过整体缩放实现更小的视觉效果。

  • 优点:所有现代浏览器支持,文字清晰度高。
  • 缺点:整个元素会被缩放(包括宽高),可能影响布局。

方案二:viewport 单位(现代浏览器)

使用 vw 或 rem 结合视口尺寸动态计算字体大小:

.small-font {
  font-size: 1.3vw;  /* 假设视口宽度为375px,1.3vw≈4.9px */
}

原理

1vw 等于视口宽度的 1%,通过计算实现任意小数像素。

  • 优点:真正的小字体,无需缩放。
  • 缺点:依赖视口宽度,在大屏幕上可能显得过大;兼容性较差(不支持 IE)。

方案三:SVG 文本(高精度方案)

将文本嵌入 SVG 中,利用 SVG 的矢量特性渲染小字体:

<svg width="100" height="20" xmlns="http://www.w3.org/2000/svg">
  <text x="0" y="15" font-size="8">Small text here</text>
</svg>

原理

SVG 的文本渲染不受浏览器字体限制,可精确控制大小。

  • 优点:完全精确,清晰度高,适合图标中的小字。
  • 缺点:无法继承 CSS 样式,需要额外处理交互。

方案四:rem/em 单位结合根字体(移动端适配)

通过调整根元素字体大小间接控制子元素:

html {
  font-size: 10px;  /* 设定基准值 */
}
.small-font {
  font-size: 1rem;  /* 10px */
}

原理先设置根元素 html 的字体大小,再通过相对单位 rem 计算实际大小。

  • 优点:适合移动端整体布局,可结合媒体查询动态调整。
  • 缺点:可能影响全站字体,需谨慎使用。

方案五:Canvas 绘制(极致自定义)

使用 Canvas API 直接绘制文本:

<canvas id="textCanvas" width="200" height="30"></canvas>
<script>
  const canvas = document.getElementById('textCanvas');
  const ctx = canvas.getContext('2d');
  ctx.font = '8px Arial';  /* 设置任意大小 */
  ctx.fillText('Small text rendered by Canvas', 10, 20);
</script>

原理

Canvas 绘制的文本是位图,可精确控制像素级别渲染。

  • 优点:完全自定义,适合特殊场景(如游戏、图表)。
  • 缺点:文本不可选择,SEO 不友好,维护成本高。

三十四、如何解决1px 问题?

这道题和之前的题重复了,为了不影响排版,先放在这儿吧

三十五、transition和animation的区别

transition 和 animation 是 CSS 中实现动态效果的两大核心工具,它们的区别主要体现在触发方式、控制粒度和应用场景上

transition

transition 是 “被动触发” 的过渡效果,需要依赖元素状态变化(如 :hover:active 或 JS 动态修改样式)才能启动,只能定义起始和结束两个关键状态,动画过程由浏览器自动计算,适合实现简单的状态切换(如按钮颜色变化、元素位移),且默认不会循环,结束后会停留在目标状态。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>CSS Transition 悬停按钮效果</title>
    <style>
        .demo-container {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background: #f0f2f5;
        }
        
        .transition-button {
            width: 150px;
            height: 50px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
            /* 定义过渡效果:同时应用于多个属性 */
            transition: 
                width 0.3s ease, 
                background-color 0.3s ease,
                transform 0.3s ease,
                box-shadow 0.3s ease;
        }
        
        .transition-button:hover {
            width: 200px;
            background-color: #2980b9;
            transform: translateY(-5px);
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
        }
    </style>
</head>
<body>
    <div class="demo-container">
        <button class="transition-button">
            悬停查看效果
        </button>
    </div>
</body>
</html>
    

这里是鼠标悬停触发的,录制的时候没有录到

GIF.gif

animation

animation 则是 “主动触发” 的动画,通过 @keyframes 定义任意数量的关键帧,可以直接指定动画的启动、循环次数、方向(如正向、反向、交替)和结束状态,无需依赖外部事件即可自动执行,适合实现复杂的多阶段动画(如加载图标旋转、元素连续变换),还能通过 animation-play-state 控制暂停 / 播放,灵活性更高。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>CSS Animation 加载动画</title>
    <style>
        .demo-container {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background: #f0f2f5;
            gap: 30px;
        }
        
        /* 旋转加载动画 */
        .spinner {
            width: 60px;
            height: 60px;
            border: 5px solid rgba(52, 152, 219, 0.3);
            border-radius: 50%;
            border-top-color: #3498db;
            /* 定义动画:名称、时长、速度曲线、循环次数 */
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        /* 脉动加载动画 */
        .pulse {
            width: 20px;
            height: 20px;
            background: #2ecc71;
            border-radius: 50%;
            /* 定义动画:脉动效果 */
            animation: pulse 1.5s ease-in-out infinite;
        }
        
        @keyframes pulse {
            0% { transform: scale(0.8); opacity: 0.7; }
            50% { transform: scale(1.2); opacity: 1; }
            100% { transform: scale(0.8); opacity: 0.7; }
        }
        
        /* 波浪加载动画 */
        .wave {
            display: flex;
            gap: 4px;
        }
        
        .wave span {
            width: 6px;
            height: 24px;
            background: #9b59b6;
            border-radius: 3px;
            /* 定义动画:波浪效果 */
            animation: wave 1s ease-in-out infinite;
        }
        
        .wave span:nth-child(2) { animation-delay: 0.1s; }
        .wave span:nth-child(3) { animation-delay: 0.2s; }
        .wave span:nth-child(4) { animation-delay: 0.3s; }
        .wave span:nth-child(5) { animation-delay: 0.4s; }
        
        @keyframes wave {
            0%, 100% { transform: scaleY(0.3); }
            50% { transform: scaleY(1); }
        }
    </style>
</head>
<body>
    <div class="demo-container">
        <div class="spinner"></div>
        <p>旋转加载动画(无限循环)</p>
        
        <div class="pulse"></div>
        <p>脉动加载动画(缩放+透明度变化)</p>
        
        <div class="wave">
            <span></span>
            <span></span>
            <span></span>
            <span></span>
            <span></span>
        </div>
        <p>波浪加载动画(多元素序列动画)</p>
    </div>
</body>
</html>
    

GIF.gif

简言之,transition 是 “简单过渡”,适合状态变化的平滑衔接;animation 是 “复杂动画”,适合自定义路径的连续动效,两者的核心差异在于是否需要外部触发、关键帧数量以及循环控制能力。

三十六、移动端开发时为什么要用到@3x,@2x这种图片

在移动端开发中,物理像素、逻辑像素和像素密度是理解界面适配的核心概念,直接解释了为什么需要使用 @2x@3x 这种多倍图方案

一、基础概念解析

1. 物理像素

  • 定义设备屏幕上的实际物理显示单元,是屏幕能控制的最小显示单位(例如 iPhone 14 的 2532×1170 个物理点)。
  • 特点:固定不变,出厂即确定(如 Retina 屏的物理像素更小、更密集)。

2. 逻辑像素

  • 定义:开发者在代码中使用的抽象像素单位(如 CSS 中的 width: 100px)。

  • 特点:与物理像素的对应关系由设备像素密度决定。例如:

    • 在普通屏幕(1x)上,1 逻辑像素 = 1 物理像素
    • 在 Retina 屏(2x)上,1 逻辑像素 = 2×2 物理像素(即 4 个物理点显示 1 个逻辑像素)

3. 像素密度(Pixel Density)

  • 定义单位长度(通常每英寸)内的物理像素数量,即 PPI(Pixels Per Inch)。

  • 关键指标

    • 设备像素比(Device Pixel Ratio, DPR) :物理像素与逻辑像素的比值。
      例如:DPR=2 表示 1 个逻辑像素对应 2×2=4 个物理像素。

    • 常见 DPR 值

      • 普通屏幕:1x(如 iPhone 8 标准模式)
      • Retina 屏:2x(如 iPhone 8 Plus)
      • 超 Retina 屏:3x(如 iPhone 14 Pro)

二、为什么需要 @2x/@3x 图片?

1. 屏幕精细化导致的显示问题

  • 在高 DPR 屏幕(如 2x/3x)上,如果只使用 1x 图片:

    • 图片会被放大以填充逻辑像素空间(例如 100px×100px 的 1x 图片在 2x 屏上被拉伸到 200×200 物理像素)。
    • 导致图片模糊(因每个逻辑像素需由多个物理像素显示,但 1x 图片仅提供 1 个物理像素的数据)。

2. 多倍图方案的解决方案

  • @2x 图片:尺寸是 1x 图片的 2 倍,包含 4 倍像素信息(例如 200px×200px 的 @2x 图)。

    • 在 2x 屏上:200×200 物理像素正好对应 100×100 逻辑像素,图片清晰。
  • @3x 图片:同理,尺寸为 1x 的 3 倍,适用于 3x 屏(如 iPhone 14 Pro)。

3. 浏览器的自动适配机制

  • CSS 中的 background-image 和 HTML 中的 <img> 标签可通过 srcset 或媒体查询自动选择合适的图片:
    srcset在前文有讲到

    <!-- srcset 语法 -->
    <img src="icon.png" 
         srcset="icon@2x.png 2x, icon@3x.png 3x" 
         alt="图标">
    
    • 浏览器会根据设备 DPR 自动选择对应倍率的图片,确保最佳显示效果。

三、移动端开发的适配

1. 设计资源准备

  • 要求设计师提供  @1x/@2x/@3x 三套切图,或直接设计 @3x 图(通过工具压缩生成低倍率版本)。

2. 代码适配方案

  • 图片资源

    <!-- 方案1:srcset 自动适配 -->
    <img src="logo.png" srcset="logo@2x.png 2x, logo@3x.png 3x" alt="Logo">
    
    <!-- 方案2:CSS background-image + media query -->
    .element {
      background-image: url(icon.png);
    }
    @media (-webkit-min-device-pixel-ratio: 2) {
      .element {
        background-image: url(icon@2x.png);
      }
    }
    
  • 图标资源:优先使用 SVG(矢量图形,自动适配任何 DPR)。

3. 布局单位选择

  • 使用 rem/em 或 viewport 单位(vw/vh)  进行布局,避免固定像素导致的适配问题。

    /* 基于视口宽度的字体大小 */
    body {
      font-size: 16px;
      font-size: 4vw; /* 假设设计稿为375px宽,16px ≈ 4vw */
    }
    

三十七、margin和padding的区别和使用场景

1.两者区别

margin(外边距)

作用于元素外部,控制与其他元素的距离,背景色不覆盖 margin 区域,修改时可能触发重排,影响布局性能,存在 “垂直 margin 合并” 特性(相邻元素的垂直 margin 会取最大值而非叠加)

padding(内边距)

作用于元素内部,控制内容与边框的距离,背景色会填充 padding 区域,修改时可能触发重绘或重排(视是否影响元素尺寸),不存在合并问题,永远叠加生效,

2.使用场景

两者的使用场景实现都很简单,这里使用表格的形式展示

场景分类margin 的典型使用场景padding 的典型使用场景
元素间关系列表项、卡片之间的上下 / 左右间距无(padding 不影响元素外部关系)
与父容器的关系元素与父容器边缘的留白(如页面内容距浏览器边框的距离)父容器内部内容与自身边框的距离(如卡片内文字距卡片边框的距离)
居中与对齐块级元素水平居中(margin: 0 auto单行文本垂直居中(结合固定高度,用上下 padding 平衡)
背景与视觉效果无(margin 区域不显示背景色)扩展背景色 / 背景图的覆盖范围(padding 区域会被背景色填充)
交互体验优化增大元素可点击区域(如按钮通过 padding 扩展点击范围)
布局调整技巧用负 margin 抵消父元素 padding 的挤压维持元素最小尺寸(避免内容紧贴边缘显得拥挤)
特殊布局需求分隔不同功能区块(如导航栏与主体内容的间隔)实现文字 / 图片的内缩进(如卡片内文字的左右缩进)
表单与控件表单控件之间的间距(如输入框与按钮的间隔)输入框、下拉框内文字与边框的距离

三十八、CSS优化和提高性能的方法有哪些

三十九、如何避免display:inline-block的间隙产生

为何display:inline-block会产生间隙?

当 HTML 中的 inline-block 元素之间存在空白字符(如换行、空格)时,浏览器会将这些空白解析为一个空格字符(约 4px 宽)。

下面使是示意图,方便大家理解

image.png

那么,各位,如何避免呢?

相信各位脑子里已经浮现了很多的想法,HTML不换行,负边距等,下面我给各位总结一下

1. 移除 HTML 中的空白字符

  • 方案 1:不换行

    <div class="box">1</div><div class="box">2</div>
    
  • 方案 2:注释掉空白

    <div class="box">1</div><!--
    --><div class="box">2</div>
    
  • 方案 3:使用 HTML 实体<!-- -->

    <div class="box">1</div
    ><div class="box">2</div>
    

image.png

2. 调整父元素的font-size

  • 将父元素的 font-size 设为 0,再在子元素中恢复:

    .parent {
      font-size: 0;
    }
    .box {
      font-size: 16px; /* 恢复子元素字体大小 */
      display: inline-block;
    }
    

image.png

3. 使用负margin

  • 抵消空格的宽度(通常为 4px,但不同浏览器可能有差异):

    .box {
      display: inline-block;
      margin-right: -4px; /* 抵消空格宽度 */
    }
    

image.png

4. 浮动元素替代

  • 使用 float: left 替代 inline-block,但需清除浮动:

    .box {
      float: left;
    }
    .parent::after {
      content: "";
      display: block;
      clear: both;
    }
    

5. Flexbox 或 Grid 布局

  • 使用现代布局模型完全避免间隙问题:

    .parent {
      display: flex; /* 或grid */
      gap: 10px; /* 显式控制间距 */
    }
    

image.png

这里的间隙是flex本身就有的

那么,在实际的场景应用中,我们如何避免?

推荐优先使用 Flexbox/Grid 或 父元素 font-size:0 方案,兼顾兼容性和可维护性。

四十、对CSS工程化的理解

CSS 工程化是指将 CSS 开发从传统的 “写样式” 转变为系统化、可维护、可扩展的工程实践。它通过工具链、方法论和架构设计,解决大型项目中 CSS 的可维护性差、命名冲突、冗余代码、性能低下等问题,本质是用工程思维管理 CSS 代码。

一、核心目标

  1. 可维护性:通过模块化、组件化减少代码耦合,降低修改成本。
  2. 可扩展性:设计灵活的架构,支持多人协作和持续迭代。
  3. 性能优化:通过工具链自动压缩、合并、去除冗余代码。
  4. 一致性:统一命名规范、目录结构和开发流程。

二、相关工程化技术

1. 预处理器

  • 变量与嵌套:减少重复代码,提高可维护性。

    $primary-color: #3498db;
    .button {
      background: $primary-color;
      &:hover {
        background: darken($primary-color, 10%);
      }
    }
    
  • Mixin 与函数:封装复用逻辑。

    @mixin responsive-text($size) {
      font-size: $size;
      @media (max-width: 768px) {
        font-size: $size * 0.8;
      }
    }
    

2. CSS 模块化方案

  • CSS Modules:自动生成唯一类名,避免全局污染。

    // React组件中使用
    import styles from './Button.module.css';
    <button className={styles.primary}>Click</button>
    
  • CSS-in-JS:将样式封装在 JavaScript 中,如 styled-components。

    const Button = styled.button`
      background: ${props => props.primary ? '#3498db' : '#fff'};
    `;
    

3. 命名规范与架构设计

  • BEM(Block-Element-Modifier) :结构化命名,避免嵌套过深。

    .card { /* Block */ }
    .card__title { /* Element */ }
    .card--highlighted { /* Modifier */ }
    
  • OOCSS(面向对象 CSS) :分离结构与皮肤,提高复用性。

    .u-clearfix { /* 结构类 */ }
    .btn--primary { /* 皮肤类 */ }
    

4. 工具链与自动化

  • 构建工具:Webpack、Gulp 处理 CSS 加载、压缩、分割。

  • PostCSS:通过插件实现自动前缀、CSSnext 特性。

    // postcss.config.js
    {
      plugins: [
        require('autoprefixer'),
        require('cssnano')
      ]
    }
    
  • Lint 工具:Stylelint 检查样式规范,避免错误。

5. 性能优化

  • Tree-shaking:移除未使用的 CSS(如 PurgeCSS)。
  • 代码分割:按需加载 CSS(如 Webpack 的 mini-css-extract-plugin)。
  • 雪碧图与字体优化:减少 HTTP 请求。

三、常见架构模式

  1. ITCSS(Inverted Triangle CSS) :分层组织 CSS,从通用到专用。

    Settings → Tools → Generic → Elements → Objects → Components → Utilities
    
  2. SMACSS(Scalable and Modular Architecture for CSS) :按功能分类。

    Base → Layout → Module → State → Theme
    
  3. Atomic CSS:原子化类名,如 Tailwind CSS。

    <div class="bg-white p-4 rounded shadow">...</div>
    

四、具体的工程化问题对应解决方案

挑战解决方案
全局作用域冲突使用 CSS Modules、CSS-in-JS 或 BEM 命名规范
代码冗余预处理器变量、Mixin,Tree-shaking 技术
维护成本高组件化开发、统一命名规范、文档工具(如 Storybook)
浏览器兼容性PostCSS 自动添加前缀,Can I Use 查询支持度
性能问题压缩合并、懒加载、使用 CSS 变量替代 JavaScript 操作样式

五、常见工程化组合

GIF.gif

CSS 工程化不是单一技术,而是方法论、工具链和架构设计的结合。它通过标准化和自动化,让 CSS 代码更易于维护、扩展和优化,尤其适合大型项目和团队协作。选择适合的方案时,需平衡项目规模、团队习惯和性能需求,避免过度工程化。

与面试官聊CSS工程化的时候尽量去结合自己项目中的具体实例去谈

如果大家对工程化不是很理解,可以看一下我之前的一篇文章面试必备 | React项目的一些优化方案(持续更新......) - 掘金

四十一、如何判断元素是否到达可视区域

我们只需要给面试官讲两个方法即可,至于面试官可能追问的懒加载之类的可以开我以后要发的手写题部分

核心方法:Intersection Observer API

最佳方法是使用现代浏览器提供的Intersection Observer API

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('元素进入可视区域');
      // 执行需要的操作
      observer.unobserve(entry.target); // 如果需要只触发一次
    }
  });
}, {
  threshold: 0.1 // 当元素10%可见时触发
});

// 观察目标元素
const target = document.querySelector('.target-element');
observer.observe(target);

优点:

  • 高性能,不阻塞主线程
  • 无需频繁计算,浏览器优化
  • 支持配置阈值(threshold)和根元素(root)
  • 自动处理滚动和 resize 事件

传统方法:getBoundingClientRect

如果必须支持老式浏览器,可以使用:

function isInViewport(element) {
  const rect = element.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

// 使用示例
window.addEventListener('scroll', () => {
  const element = document.querySelector('.target-element');
  if (isInViewport(element)) {
    console.log('元素进入可视区域');
  }
});
  • window.innerHeight 是浏览器可视区的高度;
  • document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离;
  • imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离);

注意事项:

  • 性能较差,需要节流(throttle)优化
  • 需要手动处理scroll/resize事件
  • 计算相对复杂

四十二、如何根据设计稿进行移动端适配

这个问题非常好,可以看我之前的一篇文章,里面的方法可谓是最优解

面试必备 | React项目的一些优化方案(持续更新......) - 掘金

里面的五、移动端适配便是最优解

四十三、响应式设计的概念与实现

什么是响应式

响应式设计是一种前端设计理念,通过一套代码使网站在不同屏幕尺寸(从手机到桌面显示器)上都能提供一致且优质的用户体验。

四十四、使用clear属性清除浮动的原理?

原理

当元素应用 clear 时,浏览器会计算浮动元素的高度,并将当前元素的顶部边缘下移,直到元素的顶部在浮动元素的底部之下

clear 属性的值可以是:

  • left:元素左侧不允许有浮动元素

屏幕截图 2025-07-15 153018.png

  • right:元素右侧不允许有浮动元素

屏幕截图 2025-07-15 153027.png

  • both:元素两侧都不允许有浮动元素

屏幕截图 2025-07-15 153033.png

  • none(默认值):不清除浮动

四十五、absolute与fixed共同点与不同点

在 CSS 中,absolute 和 fixed 是两种重要的定位方式

1.共同点

  1. 脱离文档流
    两者都会使元素脱离正常文档流,不再占据原空间,后续元素会忽略它们的存在。
  2. 可通过偏移属性定位
    都可使用 toprightbottomleft 控制位置,这些值的生效取决于包含块的设置。
  3. 层级提升
    默认层级高于普通元素,可通过 z-index 调整堆叠顺序。

2.不同点

特性absolutefixed
包含块(定位基准)最近的已定位祖先元素(即 position 为 relativeabsolutefixed 或 sticky 的元素)。若没有,则相对于初始包含块(即 <html>)。始终相对于浏览器视口(viewport),即使页面滚动,元素位置也不会改变。
滚动行为随包含块滚动而移动。若包含块是 <html>,则元素会随页面滚动。固定在视口位置,不随页面滚动而移动(如导航栏、悬浮按钮)。
应用场景实现元素相对于父容器的精确位置(如弹出菜单、定位图标)。创建固定导航栏、悬浮广告、返回顶部按钮等。
宽度默认行为宽度由内容撑开,除非显式设置 width宽度默认占满视口,需显式设置宽度。

3. 效果说明

这里便是两者包含块的确定不同

  • absolute 元素:相对于 .container 定位,若 .container 无定位,则相对于 <html>

image.png

这里的‘新品’悬浮在图片之上便是absolute的作用

  • fixed 元素:始终相对于视口底部左侧,即使页面滚动也保持不动。 GIF.gif

这里底边栏的静止效果便是fixed的作用

四、使用场景对比

场景推荐使用
元素相对于父容器定位absolute
创建固定在视口的组件fixed
需要响应式定位(如导航栏)fixed
实现元素重叠效果absolute

四十六、实现一个宽高自适应的正方形

方法1:使用 vw 单位(视窗宽度百分比)

实现原理

vw(Viewport Width)单位表示 视窗宽度的百分,20vw 即视窗宽度的 20%。由于 widthheight 都设为相同的 vw 值,因此元素始终是正方形。

代码示例

<div class="square-vw"></div>

<style>
.square-vw {
  width: 20vw;       /* 宽度 = 视窗宽度的 20% */
  height: 20vw;      /* 高度 = 宽度(保证正方形) */
  background: #f00;
}
</style>

方法2:使用 padding 百分比(经典方法)

实现原理

padding 的百分比值 基于父元素的宽度计算(即使 padding-toppadding-bottom 也是如此)。

  1. 设置 width: 20%(基于父容器宽度)。
  2. 设置 height: 0(避免内容影响高度)。
  3. 设置 padding-bottom: 20%(与 width 相同,形成正方形)。

代码示例

<div class="square-padding">
  <div class="content">内容</div>
</div>

<style>
.square-padding {
  width: 20%;
  height: 0;
  padding-bottom: 20%;  /* 关键:基于父元素宽度计算 */
  position: relative;   /* 让子元素绝对定位 */
  background: #0f0;
}
.square-padding .content {
  position: absolute;   /* 避免 padding 影响内容 */
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
</style>

方法3:使用 aspect-ratio 属性

实现原理

aspect-ratio 是 CSS 新属性,直接定义 宽高比(如 1/1 表示正方形)。只需设置 widthheight 之一,浏览器会自动计算另一个维度。

代码示例

<div class="square-aspect"></div>

<style>
.square-aspect {
  width: 20%;         /* 宽度基于父容器 */
  aspect-ratio: 1/1;  /* 关键:强制宽高比 1:1 */
  background: #00f;
}
</style>

方法4:使用 Grid 布局

实现原理

CSS Grid 可以通过 ::before 伪元素 + padding-top: 100% 实现正方形:

  1. 设置 display: grid
  2. ::before 生成一个占位元素,并用 padding-top: 100% 撑高。
  3. 内容放在 Grid 单元格内。

代码示例

<div class="square-grid">内容</div>

<style>
.square-grid {
  width: 20%;
  display: grid;
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  background: #ff0;
}
.square-grid::before {
  content: "";
  padding-top: 100%;  /* 关键:基于父元素宽度计算高度 */
  grid-area: 1/1/2/2; /* 占据第一个单元格 */
}
</style>

方法5:Flexible + Padding(移动端适配)

实现原理

类似于 方法2,但常用于移动端 REM 适配方案:

  1. 设置 width: 50%(基于父容器)。
  2. padding-bottom: 50% 撑高(与 width 相同)。

代码示例

<div class="square-flexible"></div>

<style>
.square-flexible {
  width: 50%;        /* 任意百分比 */
  height: 0;
  padding-bottom: 50%; /* 与 width 相同 */
  background: #f0f;
}
</style>

实际项目中推荐选择

  • 现代项目:直接用 aspect-ratio(最简单)。
  • 兼容旧浏览器:用 padding 百分比。
  • 全屏正方形:用 vw
  • 移动端 REM 适配:用 flexible + padding

四十七、对requestAnimationframe的理解

requestAnimationFrame(简称 rAF)是浏览器提供的一个用于优化动画渲染的 API,它让开发者能够在浏览器下一次重绘之前执行回调函数,从而实现流畅的动画效果。

requestAnimationFrame 的优点

requestAnimationFrame 最大优势在于与浏览器重绘周期同步,能自动匹配屏幕刷新率(如 60Hz),避免传统定时器(setTimeout/setInterval)因时间差导致的卡顿或过度渲染,显著提升动画流畅度。同时,它具备智能暂停机制,当页面处于后台或不可见时会自动停止执行,大幅节省 CPU 和电池资源,尤其适合复杂动画、滚动效果等场景。此外,回调函数接收的高精度时间戳便于精确控制动画进度,简化了帧率适配逻辑。

requestAnimationFrame 的缺点

requestAnimationFrame 的执行时机完全依赖浏览器重绘调度,无法手动指定固定时间间隔,对于需要严格定时的场景(如倒计时、数据轮询)不够灵活。其兼容性虽覆盖现代浏览器,但旧版 IE(如 IE9 及以下)不支持,需额外编写降级方案。另外,若回调函数中包含复杂计算或频繁 DOM 操作,可能阻塞渲染流程,反而影响动画性能,需开发者手动优化逻辑以保持轻量。

总结

OK,终于写完了。(加粗)