CSS3 最新特性 - CSS :has() 伪类

章节标题

CSS :has() 伪类选择器详解

核心知识点讲解

1. :has() 伪类的基本概念

:has() 伪类是 CSS Selectors Level 4 规范中的一个重要特性,它允许开发者根据元素是否包含特定后代元素来选择元素。这是 CSS 历史上第一个能够选择父元素或祖先元素的选择器,填补了 CSS 选择器的重要空白。

基本语法

selector:has(relative-selector-list) {
  /* 样式规则 */
}

工作原理

  • selector 是要应用样式的元素
  • :has() 内部包含一个选择器列表,用于匹配后代元素
  • 只有当 selector 包含至少一个匹配 relative-selector-list 的后代元素时,样式才会应用

2. :has() 伪类的语法和用法

基本用法示例

/* 选择包含 <a> 元素的 <div> */
div:has(a) {
  border: 1px solid blue;
}

/* 选择包含 <img> 元素的 <p> */
p:has(img) {
  margin-bottom: 20px;
}

/* 选择直接包含 <span> 元素的 <div> */
div:has(> span) {
  padding: 10px;
}

/* 选择后面紧跟着 <p> 元素的 <h2> */
h2:has(+ p) {
  margin-bottom: 5px;
}

组合使用

:has() 伪类可以与其他选择器组合使用,创建更复杂的选择逻辑:

/* 选择包含 <a> 且包含 <span> 的 <div> */
div:has(a):has(span) {
  background-color: #f0f0f0;
}

/* 选择包含类名为 active 的 <li> 的 <ul> */
ul:has(li.active) {
  border-left: 3px solid red;
}

/* 选择包含 <input> 且该 input 具有 required 属性的 <form> */
form:has(input[required]) {
  padding: 15px;
}

3. :has() 伪类的高级特性

否定逻辑

使用 :not() 伪类与 :has() 结合,可以实现否定逻辑:

/* 选择不包含 <a> 元素的 <div> */
div:not(:has(a)) {
  background-color: #eee;
}

/* 选择不包含任何子元素的 <div> */
div:not(:has(*)) {
  height: 50px;
}

复杂选择器

:has() 内部可以使用复杂的选择器,包括后代选择器、相邻兄弟选择器等:

/* 选择包含 <ul> 且 <ul> 包含 <li> 的 <nav> */
nav:has(ul li) {
  background-color: #f8f9fa;
}

/* 选择包含 <input> 且该 input 后面紧跟着 <button> 的 <div> */
div:has(input + button) {
  display: flex;
  gap: 10px;
}

嵌套使用

:has() 伪类可以嵌套使用,实现更复杂的选择逻辑:

/* 选择包含 <div> 且该 <div> 包含 <p> 的 <section> */
section:has(div:has(p)) {
  border: 1px solid #ddd;
  padding: 20px;
}

4. :has() 伪类的浏览器兼容性

支持情况

  • Chrome 105+ (自 2022 年 8 月起)
  • Safari 15.4+ (自 2022 年 3 月起)
  • Firefox 121+ (自 2023 年 11 月起)
  • Edge 105+ (与 Chrome 同版本)

兼容性处理

对于不支持 :has() 的浏览器,可以使用 JavaScript 作为降级方案:

// 检测浏览器是否支持 :has()
const supportsHas = CSS.supports('selector(:has(*))');

if (!supportsHas) {
  // 为包含 <a> 的 <div> 添加边框
  document.querySelectorAll('div').forEach(div => {
    if (div.querySelector('a')) {
      div.style.border = '1px solid blue';
    }
  });
}

实用案例分析

案例一:导航菜单样式控制

需求:当导航菜单包含下拉菜单时,给菜单项添加特定样式。

HTML 结构

<nav>
  <ul>
    <li><a href="#">首页</a></li>
    <li>
      <a href="#">产品</a>
      <ul class="dropdown">
        <li><a href="#">产品1</a></li>
        <li><a href="#">产品2</a></li>
      </ul>
    </li>
    <li><a href="#">关于我们</a></li>
    <li>
      <a href="#">服务</a>
      <ul class="dropdown">
        <li><a href="#">服务1</a></li>
        <li><a href="#">服务2</a></li>
      </ul>
    </li>
  </ul>
</nav>

CSS 样式

/* 基础导航样式 */
nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
}

nav li {
  position: relative;
  padding: 10px 20px;
}

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

/* 使用 :has() 为包含下拉菜单的菜单项添加样式 */
nav li:has(.dropdown) {
  background-color: #f8f9fa;
  border-radius: 4px;
}

/* 为包含下拉菜单的菜单项的链接添加箭头 */
nav li:has(.dropdown) > a::after {
  content: ' ▼';
  font-size: 12px;
}

/* 下拉菜单样式 */
.dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  display: none;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  min-width: 150px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

/* 鼠标悬停时显示下拉菜单 */
nav li:has(.dropdown):hover .dropdown {
  display: block;
}

效果

  • 包含下拉菜单的菜单项会显示不同的背景色
  • 这些菜单项的链接后面会显示向下的箭头
  • 鼠标悬停时会显示下拉菜单

案例二:表单验证反馈

需求:根据表单输入是否有效,为表单字段容器添加不同样式。

HTML 结构

<form>
  <div class="form-group">
    <label for="name">姓名</label>
    <input type="text" id="name" required>
    <span class="error-message">请输入姓名</span>
  </div>
  <div class="form-group">
    <label for="email">邮箱</label>
    <input type="email" id="email" required>
    <span class="error-message">请输入有效的邮箱地址</span>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" required minlength="8">
    <span class="error-message">密码至少需要8个字符</span>
  </div>
  <button type="submit">提交</button>
</form>

CSS 样式

/* 基础表单样式 */
.form-group {
  margin-bottom: 20px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.error-message {
  display: none;
  color: red;
  font-size: 12px;
  margin-top: 5px;
}

/* 使用 :has() 为包含无效输入的表单组添加样式 */
.form-group:has(input:invalid) {
  border-left: 3px solid red;
  padding-left: 10px;
}

/* 当输入无效且已被访问时显示错误信息 */
.form-group:has(input:invalid:user-invalid) .error-message {
  display: block;
}

/* 为包含有效输入的表单组添加样式 */
.form-group:has(input:valid) {
  border-left: 3px solid green;
  padding-left: 10px;
}

button {
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #0069d9;
}

效果

  • 当输入无效时,表单组左侧会显示红色边框
  • 当输入有效时,表单组左侧会显示绿色边框
  • 当用户输入无效内容后,会显示错误提示信息

案例三:卡片布局样式控制

需求:根据卡片内容类型,为卡片添加不同样式。

HTML 结构

<div class="card-container">
  <div class="card">
    <h3>纯文本卡片</h3>
    <p>这是一张只包含文本的卡片。</p>
  </div>
  <div class="card">
    <h3>包含图片的卡片</h3>
    <img src="https://via.placeholder.com/200" alt="示例图片">
    <p>这是一张包含图片的卡片。</p>
  </div>
  <div class="card">
    <h3>包含按钮的卡片</h3>
    <p>这是一张包含按钮的卡片。</p>
    <button>了解更多</button>
  </div>
  <div class="card">
    <h3>完整卡片</h3>
    <img src="https://via.placeholder.com/200" alt="示例图片">
    <p>这是一张包含图片和按钮的完整卡片。</p>
    <button>了解更多</button>
  </div>
</div>

CSS 样式

/* 基础卡片样式 */
.card-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  padding: 20px;
}

.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  background-color: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
}

.card:hover {
  transform: translateY(-5px);
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}

.card h3 {
  margin-top: 0;
  color: #333;
}

.card p {
  color: #666;
  line-height: 1.6;
}

.card img {
  max-width: 100%;
  height: auto;
  border-radius: 4px;
  margin-bottom: 15px;
}

.card button {
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 10px;
}

.card button:hover {
  background-color: #0069d9;
}

/* 使用 :has() 为包含图片的卡片添加特殊样式 */
.card:has(img) {
  border-top: 4px solid #007bff;
}

/* 使用 :has() 为包含按钮的卡片添加特殊样式 */
.card:has(button) {
  border-bottom: 4px solid #28a745;
}

/* 使用 :has() 为同时包含图片和按钮的卡片添加特殊样式 */
.card:has(img):has(button) {
  background-color: #f8f9fa;
  border-left: 4px solid #ffc107;
  border-right: 4px solid #ffc107;
}

效果

  • 所有卡片都有基础样式和悬停效果
  • 包含图片的卡片顶部有蓝色边框
  • 包含按钮的卡片底部有绿色边框
  • 同时包含图片和按钮的卡片有黄色左右边框和浅灰色背景

知识总结

  1. :has() 伪类的核心价值

    • 允许根据元素的后代元素来选择元素
    • 是 CSS 中第一个能够选择父元素或祖先元素的选择器
    • 大大增强了 CSS 选择器的表达能力
  2. :has() 伪类的主要用法

    • 基本用法:选择包含特定后代元素的元素
    • 组合使用:与其他选择器结合使用
    • 否定逻辑:与 :not() 伪类结合使用
    • 复杂选择器:内部使用复杂的选择器
    • 嵌套使用:实现更复杂的选择逻辑
  3. :has() 伪类的实际应用场景

    • 导航菜单样式控制
    • 表单验证反馈
    • 卡片布局样式控制
    • 动态内容样式调整
    • 响应式设计中的条件样式
  4. 浏览器兼容性

    • 现代浏览器(Chrome、Safari、Firefox、Edge)已支持
    • 对于不支持的浏览器,可以使用 JavaScript 作为降级方案
  5. 性能考虑

    • :has() 伪类可能会影响页面渲染性能,特别是在复杂页面中
    • 建议合理使用,避免过度复杂的选择器
    • 对于大型应用,应进行性能测试

学习建议

  1. 实践练习

    • 创建包含不同类型内容的卡片,使用 :has() 为它们添加不同样式
    • 设计一个导航菜单,使用 :has() 控制下拉菜单的显示和样式
    • 构建一个表单,使用 :has() 实现实时验证反馈
  2. 深入学习

    • 结合其他 CSS 选择器,探索更复杂的选择逻辑
    • 研究 :has() 伪类在不同浏览器中的性能表现
    • 了解 :has() 伪类在 CSS Selectors Level 4 规范中的完整定义
  3. 应用拓展

    • 在实际项目中使用 :has() 伪类简化 JavaScript 代码
    • 探索 :has() 伪类与 CSS 变量、CSS Grid 等其他 CSS 特性的结合使用
    • 关注浏览器对 :has() 伪类的支持情况和性能优化

通过本教程的学习,相信你已经掌握了 CSS :has() 伪类的基本概念和使用方法。在实际项目中,合理运用这一强大的选择器特性,可以大大简化你的 CSS 代码,实现更灵活、更动态的样式控制。

« 上一篇 CSS Nesting 下一篇 » CSS3 最新特性 - CSS Logical Properties