CSS原生嵌套功能完全指南:告别SCSS与Less

更新日期: 2026-04-12 阅读: 25 标签: 功能

你还在用SCSS或Less只是为了写嵌套CSS吗?浏览器现在已经原生支持了


为什么我们需要CSS嵌套

过去写CSS非常痛苦,选择器重复率很高。看下面这个例子:

/* 老写法:重复写.card 5次 */
.card { background: white; }
.card .title { font-size: 18px; }
.card .title:hover { color: blue; }
.card .body { padding: 16px; }
.card .body p { line-height: 1.6; }

为了解决这个问题,我们引入了SCSS:

/* SCSS嵌套写法 */
.card {
  background: white;
  .title {
    font-size: 18px;
    &:hover { color: blue; }
  }
  .body {
    padding: 16px;
    p { line-height: 1.6; }
  }
}

现在原生CSS也支持嵌套了,不需要任何构建工具,直接在浏览器里写。


浏览器兼容性

浏览器支持版本发布时间
Chrome112+2023年4月
Firefox117+2023年8月
Safari16.5+2023年5月
Edge112+2023年4月

2024年起可以放心在生产环境使用,全球覆盖率超过90%。


基础语法

原生CSS嵌套与SCSS基本一致,有两种写法:

/* 写法1:直接嵌套,推荐使用,Chrome 120+ 不需要写&符号 */
.card {
  background: white;
  padding: 16px;

  .title {
    font-size: 18px;
    color: #333;
  }
}

/* 写法2:用&表示父选择器,Chrome 112+ 就支持 */
.btn {
  background: blue;
  color: white;

  &:hover {
    background: darkblue;
  }

  &.active {
    background: green;
  }
}

注意:Chrome 112到119版本必须使用&符号前缀,Chrome 120以上版本子元素可以省略&符号。


实战1:组件样式封装

告别选择器重复,把一个组件的所有样式写在一起:

/* 传统写法:样式散乱 */
.nav { display: flex; }
.nav ul { list-style: none; }
.nav ul li { padding: 8px 16px; }
.nav ul li a { text-decoration: none; color: #333; }
.nav ul li a:hover { color: blue; }
.nav ul li.active a { color: blue; font-weight: bold; }

/* 原生嵌套:清晰聚合 */
.nav {
  display: flex;

  ul {
    list-style: none;
    margin: 0;
    padding: 0;

    li {
      padding: 8px 16px;

      a {
        text-decoration: none;
        color: #333;

        &:hover {
          color: blue;
        }
      }

      &.active a {
        color: blue;
        font-weight: bold;
      }
    }
  }
}

效果:代码行数相同,但结构清晰很多。


实战2:状态变体管理

用&符号管理同一组件的所有状态:

.btn {
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;

  /* 颜色变体 */
  &.primary {
    background: #007bff;
    color: white;
  }

  &.danger {
    background: #dc3545;
    color: white;
  }

  &.outline {
    background: transparent;
    border: 2px solid #007bff;
    color: #007bff;
  }

  /* 尺寸变体 */
  &.sm {
    padding: 4px 8px;
    font-size: 12px;
  }

  &.lg {
    padding: 12px 24px;
    font-size: 18px;
  }

  /* 状态 */
  &:hover {
    opacity: 0.85;
    transform: translateY(-1px);
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
  }
}


实战3:响应式嵌套

可以把媒体查询直接嵌套在选择器里面,这个功能非常实用。

/* 旧写法:媒体查询散落各处 */
.card { width: 100%; }
@media (min-width: 768px) {
  .card { width: 50%; }
}
@media (min-width: 1024px) {
  .card { width: 33.33%; }
}

/* 新写法:响应式就在元素旁边 */
.card {
  width: 100%;
  padding: 12px;
  font-size: 14px;

  @media (min-width: 768px) {
    width: 50%;
    padding: 16px;
    font-size: 15px;
  }

  @media (min-width: 1024px) {
    width: 33.33%;
    padding: 20px;
    font-size: 16px;
  }

  .title {
    font-size: 16px;

    @media (min-width: 768px) {
      font-size: 20px;
    }
  }
}

媒体查询不再满文件乱飞,直接跟着元素走。


实战4:与is()或where()组合使用

嵌套加伪类的组合写法,可以让代码更精简:

/* 用is()合并多个选择器 */
.article {
  h1, h2, h3 {
    font-weight: bold;
    line-height: 1.3;
  }

  /* 等价但更简洁的写法 */
  :is(h1, h2, h3) {
    font-weight: bold;
    line-height: 1.3;
  }

  /* 暗色模式嵌套 */
  @media (prefers-color-scheme: dark) {
    background: #1a1a1a;
    color: #e0e0e0;

    a {
      color: #64b5f6;
    }
  }
}

/* 表单验证加has()加嵌套 */
.form-group {
  label { color: #333; }

  input {
    border: 1px solid #ccc;

    &:focus { border-color: #007bff; outline: none; }
    &:invalid { border-color: #dc3545; }
    &:valid { border-color: #28a745; }
  }

  /* has()联动父元素,Chrome 105以上版本支持 */
  &:has(input:invalid) {
    label { color: #dc3545; }
    .error-msg { display: block; }
  }
}


完整可运行Demo

直接复制下面的代码到HTML文件里就可以运行:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS原生嵌套功能演示</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: sans-serif; background: #f0f2f5; padding: 20px; }

  /* CSS原生嵌套完整示例 */
  .card-list {
    display: grid;
    gap: 16px;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));

    .card {
      background: white;
      border-radius: 12px;
      overflow: hidden;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
      transition: transform 0.2s, box-shadow 0.2s;
      position: relative;

      &:hover {
        transform: translateY(-4px);
        box-shadow: 0 8px 24px rgba(0,0,0,0.15);
      }

      .card-header {
        padding: 20px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;

        h3 {
          font-size: 18px;
          margin-bottom: 4px;
        }

        span {
          font-size: 12px;
          opacity: 0.8;
        }
      }

      .card-body {
        padding: 16px 20px;

        p {
          color: #666;
          line-height: 1.6;
          font-size: 14px;
        }
      }

      .card-footer {
        padding: 12px 20px;
        border-top: 1px solid #f0f0f0;
        display: flex;
        gap: 8px;

        .btn {
          padding: 6px 14px;
          border-radius: 6px;
          border: none;
          cursor: pointer;
          font-size: 13px;
          transition: opacity 0.2s;

          &:hover { opacity: 0.85; }

          &.primary {
            background: #667eea;
            color: white;
          }

          &.outline {
            background: transparent;
            border: 1px solid #667eea;
            color: #667eea;
          }
        }
      }

      /* 标记为热门的卡片特殊样式 */
      &.hot {
        .card-header {
          background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
        }

        &::after {
          content: '🔥 热门';
          position: absolute;
          top: 12px;
          right: 12px;
          background: rgba(255,255,255,0.9);
          padding: 2px 8px;
          border-radius: 10px;
          font-size: 11px;
          color: #f5576c;
        }
      }
    }
  }

  /* 响应式嵌套 */
  @media (max-width: 480px) {
    .card-list {
      grid-template-columns: 1fr;
    }
  }
</style>
</head>
<body>
  <div class="card-list">
    <div class="card">
      <div class="card-header">
        <h3>CSS Nesting</h3>
        <span>Chrome 112以上版本,无需构建工具</span>
      </div>
      <div class="card-body">
        <p>原生CSS终于支持嵌套了,告别SCSS,直接在浏览器里写嵌套选择器。</p>
      </div>
      <div class="card-footer">
        <button class="btn primary">立即尝试</button>
        <button class="btn outline">查看文档</button>
      </div>
    </div>
    <div class="card hot">
      <div class="card-header">
        <h3>CSS has()</h3>
        <span>Chrome 105以上版本,父元素选择器</span>
      </div>
      <div class="card-body">
        <p>CSS史上最强父选择器,可根据子元素状态选中父元素,不需要JavaScript。</p>
      </div>
      <div class="card-footer">
        <button class="btn primary">立即尝试</button>
        <button class="btn outline">查看文档</button>
      </div>
    </div>
    <div class="card">
      <div class="card-header">
        <h3>CSS @layer</h3>
        <span>Chrome 99以上版本,层叠层管理</span>
      </div>
      <div class="card-body">
        <p>彻底解决第三方CSS优先级冲突问题,组件化CSS的终极方案。</p>
      </div>
      <div class="card-footer">
        <button class="btn primary">立即尝试</button>
        <button class="btn outline">查看文档</button>
      </div>
    </div>
  </div>
</body>
</html>


与SCSS的区别

特性CSS NestingSCSS Nesting
需要构建工具不需要需要
使用&引用父选择器支持支持
省略&写子元素Chrome 120以上版本支持一直支持
嵌套@media原生支持支持
变量或mixin不支持,用CSS变量代替支持
运行时性能浏览器原生解析需要编译为普通CSS

对于新项目或轻量项目,可以直接用原生CSS嵌套,零依赖。对于大型项目,SCSS的mixin和函数仍有价值,可以混合使用。


降级处理

/* 检测浏览器是否支持CSS嵌套 */
@supports (selector(&)) {
  /* 支持嵌套的浏览器 */
  .card {
    .title { color: blue; }
  }
}

/* 不支持时的降级写法 */
@supports not (selector(&)) {
  .card .title { color: blue; }
}

也可以使用postcss-nesting自动编译降级。


总结

改变说明
无需SCSS小项目不再需要Sass构建步骤
组件化样式和组件结构保持一致,易于维护
响应式就近媒体查询嵌套在元素内,不再满文件乱飞
状态聚合hover、focus、disabled等状态全在一处
兼容性Chrome 112以上,Firefox 117以上,Safari 16.5以上

CSS嵌套是CSS近年来最重要的生产力提升之一,现在就可以在新项目里用起来。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13599

相关推荐

js简体繁体转换

繁体词库封装好的,直接就对应简体转换了, 本js用于客户在网站页面选择繁体中文或简体中文显示,默认是正常显示,即简繁体同时显示,本程序只在UTF8编码下测试过,不保证其他编码有效

使用 css/less 动态更换主题色(换肤功能)

说起换肤功能,前端应该是非常熟悉了?一般来说换肤的需求分为几种:1. 纯前端,直接在页面前端通过点击自由切换限定的几种主题色,切换之后主题色变量存到本地浏览器

vue实现打印功能的两种方法

通过npm 安装插件,安装 npm install vue-print-nb --save.如需通过链接地址打印:window.location.href = airway_bill; airway_bill为链接地址。如果内容打印不全,在打印操作时点击更多设置,然后设置缩放。

在react中实现打印功能

我们知道,window.print()可以调起打印功能,但是直接用window.print()如果直接打印的话,没有样式,而且默认打印的是整个网页的内容。解决的方法可以用iframe方式引入需要打印的区域,并把样式添加进去

js剪切板_Clipboard.js 使用

clipboard.js是一个用来设置剪切板的库,小巧无依赖,但用法有点诡异,必须依赖一个DOM元素,并且需要用户的点击操作才能实现功能

JS将货币小写转换为大写

JS将货币小写转换为大写,/** 数字金额大写转换(可以处理整数,小数,负数) */ [零, 壹,贰,叁,肆,伍,陆,柒,捌,玖]

javascript如何实现计时功能?

JavaScript中可以使用setInterval()方法实现计时功能,setInterval()方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。

5个CSS新功能

在浏览器开始实现它们之前,CSS 新的功能通常需要经过长时间讨论之后,才在W3联盟的规范中定义。 有许多值得一提的 CSS 新功能,但是在本文中,我们重点介绍可以浏览器的稳定版中进行测试的五个功能:

如何较为优雅地实现新手引导功能?

早期的项目中晓衡遇到游戏终于要完成了,辛苦了一阵满以为可以稍微放松一下了,但策划、运营要求,增加一个他们认为非常“简单”且重要的功能: 新手引导 。

7个好用的TypeScript 新功能

在下面的例子中,要访问 address ,你必须遍历 data.customer.address,而且 data 或 customer 有可能是 undefined,所以通常使用 && 运算符或类似例子中的技巧遍历检查每个层次的定义。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!