MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

减少冗余代码提高CSS性能的策略

2021-12-084.1k 阅读

一、CSS 冗余代码的危害

1.1 增加文件体积

CSS 冗余代码最直接的影响就是增加了文件的体积。例如,在一个项目中,多个选择器可能重复定义了相同的样式属性。假设我们有如下代码:

.header {
  color: #333;
  font-size: 16px;
}
.footer {
  color: #333;
  font-size: 16px;
}

这里 .header.footer 都定义了相同的 colorfont - size 属性,这就是一种冗余。当这种情况在一个较大的 CSS 文件中频繁出现时,文件体积会显著增大。以一个包含 100 个类似这样的冗余样式块的文件为例,每个冗余块增加 20 字节(保守估计,实际会因属性和值的不同而有所变化),那么文件体积就会额外增加 2000 字节。这对于用户来说,加载页面时就需要下载更多的数据,尤其是在移动网络环境下,会极大地影响加载速度。

1.2 降低维护性

冗余代码使得 CSS 文件变得臃肿和复杂,增加了维护的难度。想象一下,当你需要修改某个通用样式,比如把 font - size 从 16px 改为 18px。在有冗余代码的情况下,你需要在多个选择器中查找并修改这个属性。如果遗漏了某个地方,就会导致页面样式不一致。例如,在一个包含多个页面的网站项目中,可能有不同的 CSS 文件分别定义了不同页面元素的样式,但存在大量冗余。当需要对全站的链接颜色进行修改时,就需要在各个 CSS 文件中仔细排查所有相关的冗余样式定义,这不仅浪费时间,还容易出错。

1.3 影响渲染性能

浏览器在解析和渲染 CSS 时,会消耗一定的资源。冗余代码意味着浏览器需要处理更多不必要的信息。当 CSS 文件较大且包含大量冗余时,浏览器解析 CSS 的时间会变长。例如,在渲染一个复杂页面时,浏览器可能需要花费 200ms 来解析 CSS。如果冗余代码导致 CSS 文件体积增加了 50%,那么解析时间可能会延长到 300ms 甚至更多。这会导致页面渲染延迟,用户体验变差。而且,在浏览器进行重排和重绘操作时,也需要重新计算冗余样式,进一步消耗性能。

二、识别 CSS 冗余代码

2.1 重复的选择器样式定义

重复的选择器样式定义是最常见的冗余形式。比如:

.main - content {
  background - color: #f0f0f0;
  padding: 10px;
}
.sidebar {
  background - color: #f0f0f0;
  padding: 10px;
}

这里 .main - content.sidebar 具有相同的 background - colorpadding 样式。可以通过代码审查工具,如 ESLint 结合一些 CSS 插件(如 stylelint)来检测这种重复。这些工具能够扫描 CSS 文件,标记出具有相同样式定义的选择器,方便开发者进行优化。

2.2 嵌套选择器中的冗余

在嵌套选择器中也容易出现冗余。例如:

.nav {
  list - style - type: none;
  padding: 0;
}
.nav li {
  display: inline - block;
  padding: 5px;
}
.nav li a {
  color: #333;
  text - decoration: none;
}
.sidebar - nav {
  list - style - type: none;
  padding: 0;
}
.sidebar - nav li {
  display: inline - block;
  padding: 5px;
}
.sidebar - nav li a {
  color: #333;
  text - decoration: none;
}

.nav.sidebar - nav 及其内部嵌套选择器的样式基本相同,这就是一种嵌套选择器中的冗余。在开发过程中,开发者可能为了区分不同位置的导航,分别编写了两套相似的样式,但实际上可以合并。可以通过仔细分析页面结构和样式需求,找出这些相似的嵌套选择器结构,进行合并优化。

2.3 未使用的样式

未使用的样式也是一种冗余。比如在一个项目开发过程中,可能会编写一些临时的样式,后来不再使用,但却留在了 CSS 文件中。例如:

/* 这个样式可能是之前测试用的,现在没有任何元素使用它 */
.test - style {
  color: red;
  font - weight: bold;
}

可以使用工具如 PurifyCSS 来检测和删除未使用的样式。PurifyCSS 会分析 HTML 文件和 CSS 文件,找出 CSS 中哪些样式没有在 HTML 中被引用,然后可以选择将这些未使用的样式从 CSS 文件中删除,从而减小文件体积。

三、减少冗余代码的策略

3.1 使用类继承和复用

3.1.1 定义基础类

通过定义基础类,可以实现样式的复用。例如,对于文本样式,我们可以定义一个基础的 text - base 类:

.text - base {
  color: #333;
  font - size: 16px;
  line - height: 1.5;
}

然后,在需要这些基本文本样式的地方,如标题、段落等,通过继承这个基础类来复用样式:

h1 {
  @extend.text - base;
  font - weight: bold;
  font - size: 24px;
}
p {
  @extend.text - base;
}

这里使用了 Sass 中的 @extend 指令,在其他预处理器或原生 CSS 中,也可以通过将基础类名添加到元素的类列表中来实现复用。例如:

<h1 class="text - base title">标题</h1>
<p class="text - base">段落文本</p>

3.1.2 基于功能的类复用

还可以根据功能来定义类并复用。比如,我们有一个用于添加圆角的 rounded 类:

.rounded {
  border - radius: 5px;
}

这个类可以应用到按钮、图片、卡片等各种需要圆角效果的元素上:

<button class="rounded">按钮</button>
<img src="image.jpg" class="rounded" alt="图片">
<div class="card rounded">卡片内容</div>

3.2 利用 CSS 预处理器

3.2.1 变量的使用

CSS 预处理器(如 Sass、Less)允许我们使用变量。例如,在一个项目中,如果有多个地方使用相同的颜色,我们可以定义一个颜色变量:

$primary - color: #007bff;

然后在需要使用这个颜色的地方,使用变量:

a {
  color: $primary - color;
}
button {
  background - color: $primary - color;
}

这样,如果需要修改主色调,只需要在变量定义处修改一次,所有使用该变量的地方都会自动更新,避免了在多个选择器中重复修改颜色值,减少了冗余。

3.2.2 混合(Mixin)的应用

混合(Mixin)是预处理器中非常强大的功能,可以将一组样式封装起来,在需要的地方复用。例如,我们定义一个用于创建响应式字体大小的混合:

@mixin responsive - font - size($base - size, $min - size, $max - size) {
  font - size: $base - size;
  @media (min - width: 768px) {
    font - size: ($min - size + ($max - size - $min - size) * ((100vw - 768px) / (1200px - 768px)));
  }
}

然后在需要使用响应式字体大小的地方,调用这个混合:

h1 {
  @include responsive - font - size(16px, 20px, 24px);
}

这样,就不需要在每个需要响应式字体大小的元素上重复编写复杂的媒体查询和字体大小计算代码,大大减少了冗余。

3.3 优化选择器

3.3.1 避免过度嵌套

过度嵌套选择器会导致选择器变得冗长和复杂,增加冗余的可能性。例如:

.container {
  background - color: #f0f0f0;
  &.active {
    background - color: #ccc;
    .content {
      color: #000;
      &.highlight {
        font - weight: bold;
      }
    }
  }
}

这样的嵌套可能会导致选择器权重过高,并且难以维护。可以通过合理分层和使用类来简化:

.container {
  background - color: #f0f0f0;
}
.container.active {
  background - color: #ccc;
}
.container.content {
  color: #000;
}
.container.content.highlight {
  font - weight: bold;
}

这样每个选择器更加简洁明了,也更容易理解和维护。

3.3.2 使用合适的选择器类型

选择器类型的选择也很重要。例如,尽量避免使用通配符选择器 *,因为它会匹配页面上的所有元素,会导致浏览器性能下降。假设我们有如下代码:

* {
  box - sizing: border - box;
}

虽然这样设置 box - sizing 很方便,但会影响性能。可以改为使用更具体的选择器,如:

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  box - sizing: border - box;
}

这样虽然代码量看起来增加了,但更加明确,性能也更好。不过,这种方式比较繁琐,也可以使用现代 CSS 的 :where 选择器来达到类似效果且更简洁:

:where(html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video) {
  box - sizing: border - box;
}

:where 选择器具有 0 权重,不会影响选择器的特异性计算,同时能达到相对精确选择元素的目的。

3.4 模块化 CSS

3.4.1 组件化样式

将页面拆分为组件,每个组件有自己独立的 CSS 文件。例如,一个导航栏组件,有 navbar.css 文件:

.navbar {
  background - color: #333;
  color: white;
  padding: 10px;
}
.navbar ul {
  list - style - type: none;
  margin: 0;
  padding: 0;
}
.navbar li {
  display: inline - block;
  margin - right: 15px;
}
.navbar a {
  color: white;
  text - decoration: none;
}

这样每个组件的样式相互隔离,减少了不同组件之间样式冲突和冗余的可能性。在使用组件时,只需要引入对应的 CSS 文件即可:

<head>
  <link rel="stylesheet" href="navbar.css">
</head>
<body>
  <nav class="navbar">
    <ul>
      <li><a href="#">首页</a></li>
      <li><a href="#">关于</a></li>
    </ul>
  </nav>
</body>

3.4.2 命名规范

采用合适的命名规范对于模块化 CSS 很重要。例如,采用 BEM(块、元素、修饰符)命名规范。以一个按钮组件为例:

/* 块 */
.button {
  background - color: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  border - radius: 5px;
}
/* 元素 */
.button__text {
  font - size: 16px;
}
/* 修饰符 */
.button--primary {
  background - color: #0056b3;
}
.button--secondary {
  background - color: #6c757d;
}

在 HTML 中使用:

<button class="button button--primary">
  <span class="button__text">主要按钮</span>
</button>
<button class="button button--secondary">
  <span class="button__text">次要按钮</span>
</button>

这种命名规范使得样式的结构清晰,不同部分的样式容易区分和维护,减少了冗余代码的产生。

四、性能优化相关的其他方面

4.1 压缩和合并 CSS 文件

4.1.1 压缩 CSS

压缩 CSS 可以去除文件中的空白字符、注释等冗余信息,减小文件体积。例如,使用工具如 cssnano 对如下 CSS 进行压缩:

/* 原始 CSS */
body {
  background - color: #f0f0f0;
  color: #333;
  font - size: 16px;
}
/* 压缩后 */
body{background - color:#f0f0f0;color:#333;font - size:16px}

压缩后的文件体积会明显减小,从而加快浏览器的下载和解析速度。可以在构建过程中集成 cssnano,比如在 Webpack 中配置 cssnano 插件,让其自动对 CSS 文件进行压缩。

4.1.2 合并 CSS 文件

如果项目中有多个 CSS 文件,将它们合并为一个文件可以减少浏览器请求次数。例如,一个项目有 styles1.cssstyles2.cssstyles3.css,可以通过构建工具(如 Gulp 或 Webpack)将它们合并为一个 main.css。在合并过程中,要注意避免引入重复的样式,同时对合并后的文件进行压缩,以进一步优化性能。在 HTML 中,只需要引入合并后的 main.css 文件:

<head>
  <link rel="stylesheet" href="main.css">
</head>

这样可以减少浏览器请求资源的开销,提高页面加载速度。

4.2 优化 CSS 加载顺序

4.2.1 关键 CSS 优先加载

关键 CSS 是指页面首次渲染所必需的 CSS。例如,对于一个包含导航栏、主体内容和页脚的页面,导航栏和主体内容的基本样式就是关键 CSS。可以将这部分关键 CSS 提取出来,放在 HTML 的 <head> 标签内直接嵌入,这样浏览器可以在解析 HTML 的同时尽快渲染关键部分的样式,提高首屏渲染速度。例如:

<head>
  <style>
    .navbar {
      background - color: #333;
      color: white;
      padding: 10px;
    }
    .main - content {
      background - color: #f0f0f0;
      padding: 20px;
    }
  </style>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <nav class="navbar">导航栏</nav>
  <div class="main - content">主体内容</div>
  <footer>页脚</footer>
</body>

这里导航栏和主体内容的样式直接嵌入 <head> 中,而其他非关键样式通过外部 CSS 文件加载。

4.2.2 异步加载非关键 CSS

对于非关键 CSS,可以采用异步加载的方式,避免阻塞页面渲染。例如,使用 media 属性来加载特定设备或屏幕尺寸下的 CSS 文件:

<head>
  <link rel="stylesheet" href="styles.css">
  <link rel="stylesheet" href="print.css" media="print">
  <link rel="stylesheet" href="responsive.css" media="(min - width: 768px)">
</head>

这样,print.css 只有在打印时才会加载,responsive.css 只有在屏幕宽度大于等于 768px 时才会加载,不会影响页面的初始渲染性能。还可以使用 JavaScript 动态加载非关键 CSS,例如:

<head>
  <link rel="stylesheet" href="styles.css">
  <script>
    function loadNonCriticalCSS() {
      var link = document.createElement('link');
      link.rel ='stylesheet';
      link.href = 'non - critical.css';
      document.head.appendChild(link);
    }
    window.addEventListener('load', loadNonCriticalCSS);
  </script>
</head>

这里在页面加载完成后,再动态加载 non - critical.css,不会阻塞页面的初始渲染。

4.3 避免使用昂贵的 CSS 属性

4.3.1 理解 CSS 属性的性能影响

有些 CSS 属性在计算和渲染时会消耗更多的性能,比如 box - shadowborder - radiustransform 等。例如,box - shadow 会增加浏览器的计算量,尤其是当设置了复杂的阴影效果时。假设我们有如下代码:

.box {
  box - shadow: 0 0 10px rgba(0, 0, 0, 0.5), 0 0 20px rgba(0, 0, 0, 0.3);
}

这种多层复杂的阴影效果会比简单的 box - shadow: 0 0 5px rgba(0, 0, 0, 0.2) 消耗更多性能。在使用这些属性时,要权衡性能和视觉效果。如果对性能要求较高,可以简化这些属性的设置,比如减少阴影层数、降低透明度等。

4.3.2 替代方案

对于一些昂贵的 CSS 属性,可以寻找替代方案。例如,对于 border - radius,如果需要在某些旧浏览器中实现类似效果,可以使用图片来模拟圆角。虽然这会增加图片资源的加载,但在一些性能受限的环境中可能是一种可行的办法。又如,对于 transform,如果只是简单的位移效果,可以使用 marginpadding 来替代,不过需要注意的是,transform 不会影响文档流,而 marginpadding 会,所以要根据具体需求来选择。在动画效果方面,如果使用 transform 实现动画导致性能问题,可以尝试使用 CSS 的 transition 结合更简单的属性变化来实现类似效果,因为 transition 在一些浏览器中可能有更好的性能表现。

五、性能监测与持续优化

5.1 使用性能监测工具

5.1.1 Chrome DevTools

Chrome DevTools 是前端开发中常用的性能监测工具。在 DevTools 的 Performance 面板中,可以录制页面加载和交互过程中的性能数据。例如,通过录制可以查看 CSS 解析、样式计算以及重排重绘等操作所花费的时间。假设在录制过程中发现某个 CSS 样式的计算时间较长,可能是因为选择器过于复杂或者使用了昂贵的 CSS 属性。可以根据这些数据定位到具体的 CSS 代码,进行针对性优化。还可以使用 DevTools 的 Coverage 面板来查看 CSS 的使用情况,找出未使用的样式,从而删除冗余代码。

5.1.2 Lighthouse

Lighthouse 是 Google 开发的一款开源自动化工具,用于改善网页的质量。它可以对页面进行全面的性能评估,包括性能、可访问性、最佳实践等方面。在性能评估中,Lighthouse 会检测 CSS 文件的大小、加载时间、是否存在未使用的样式等问题,并给出相应的优化建议。例如,如果 Lighthouse 检测到 CSS 文件体积过大,可能会建议进行压缩和合并;如果发现有未使用的样式,会提示删除这些冗余代码。可以将 Lighthouse 集成到 CI/CD 流程中,每次代码部署前运行 Lighthouse 检测,确保页面性能符合要求。

5.2 持续优化流程

5.2.1 代码审查

在开发过程中,定期进行代码审查是减少冗余代码和优化性能的重要环节。团队成员可以互相审查代码,检查是否存在重复的样式定义、过度嵌套的选择器、未使用的样式等问题。例如,在代码合并请求(Pull Request)阶段,审查人员仔细检查提交的 CSS 代码,提出优化建议。如果发现有多个组件使用了相同的样式,可以建议提取为公共类进行复用;如果发现有复杂且不必要的选择器嵌套,建议简化选择器结构。通过代码审查,可以在开发过程中及时发现并解决冗余代码和性能问题。

5.2.2 性能测试与对比

在项目的不同阶段,进行性能测试并对比结果。例如,在完成一次 CSS 优化后,使用性能监测工具记录优化前后页面的加载时间、CSS 解析时间等关键指标。假设优化前页面加载时间为 3 秒,优化后缩短到 2.5 秒,说明优化措施取得了一定效果。如果性能没有提升甚至下降,需要分析原因,可能是优化过程中引入了新的问题,比如压缩 CSS 文件时破坏了某些样式,或者合并 CSS 文件导致了选择器冲突。通过持续的性能测试与对比,可以不断调整优化策略,确保项目性能持续提升。

5.2.3 关注行业动态和新技术

前端技术不断发展,新的 CSS 特性和优化方法不断涌现。关注行业动态和新技术,及时将其应用到项目中。例如,CSS 的 contain 属性可以让浏览器对元素进行更高效的渲染,通过设置 contain: layout paint,可以告诉浏览器该元素及其子元素的布局和绘制不会影响到外部元素,从而减少重排和重绘的范围,提高性能。又如,新的 CSS 布局模型如 CSS Grid 和 Flexbox 相比传统的浮动布局,在性能和布局灵活性上都有很大提升。及时学习和采用这些新技术,可以更好地优化项目的 CSS 性能,避免因使用过时技术而产生冗余代码和性能问题。