CSS容器查询和悬浮间隙问题

容器查询和悬浮间隙问题

1. 容器查询

在开发一个现代网页时,例如一个卡片组件。当它摆在页面正中央时,横向排版。然而,当把同一个组件复用到窄的侧边栏时,整个画面会挤压变形。

过去,为了解决这个问题,我们只能配合全局的媒体查询去套不同的父级类名:

css 复制代码
@media (min-width: 1024px) {
  /* 放到主内容区,横着排 */
  .main-content .my-card { flex-direction: row; }
  
  /* 放到侧边栏,竖着排 */
  .sidebar .my-card { flex-direction: column; }
}

这种做法不仅打破了组件的封装性(必须关注屏幕视口大小),还让代码变得极难维护。

媒体查询 和 容器查询:

  • 媒体查询: 组件根据浏览器视口有多宽,来决定自己的形态。
  • 容器查询: 组件根据父级容器有多大,来决定自己的形态。

这意味着,组件不再关心它被放在了哪里,它只关心分给自己的空间有多少。

如何开启容器查询?

以下面的代码为例

第一步:HTML 结构

首先,我们在页面里创造两个容器:一个窄(模拟侧边栏),一个宽(模拟主内容区),里面放的组件 HTML 代码完全一模一样。

html 复制代码
<div class="page-layout">
  <div class="sidebar-container">
    <div class="weather-widget">
      <div class="weather-status">☀️</div>
      <div class="weather-info">
        <h3>北京</h3>
        <p>晴间多云 · 26°C</p>
        <span class="tips">适合户外运动</span>
      </div>
    </div>
  </div>

  <div class="main-container">
    <div class="weather-widget">
      <div class="weather-status">☀️</div>
      <div class="weather-info">
        <h3>北京</h3>
        <p>晴间多云 · 26°C</p>
        <span class="tips">适合户外运动</span>
      </div>
    </div>
  </div>
</div>
第二步:在 CSS 中声明谁是容器

要想让天气组件能够识别父级大小,我们必须先让浏览器看这两个包裹层的宽度变化

css 复制代码
/* 让两个容器一个窄,一个宽 */
.sidebar-container { width: 300px; }
.main-container { width: 800px; }

/* 将其定义为一个"宽度监听容器" */
.sidebar-container, .main-container {
  container-type: inline-size;
}

注: container-type: inline-size; 意思是让浏览器只监听该容器的水平宽度变化(只有宽度变化才能触发容器查询)。因为网页布局绝大多数都在纵向滚动,宽度才是决定组件是该横排还是竖排的关键。

第三步:用 @container 编写自适应样式

现在,开始编写天气组件自己的样式。我们先写出它在窄空间下的默认状态(垂直排列),然后用 @container 写它在大空间下的横向排列。

css 复制代码
/* 1. 默认状态(窄容器/侧边栏):垂直布局,像个普通的列表项 */
.weather-widget {
  display: flex;
  flex-direction: column; /*主轴设为垂直方向*/
  align-items: center;
  padding: 15px;
  background: #f0f8ff;
  border-radius: 12px;
  text-align: center;
}
.weather-status { font-size: 40px; }
.tips { display: none; } /* 窄的时候空间不够,把温馨提示隐藏掉 */


/* 2. 当这个组件所在的"父容器"宽度大于 500px 时,横向排列 */
@container (min-width: 500px) {
  .weather-widget {
    flex-direction: row;      
    justify-content: space-around;
    padding: 30px;
    background: linear-gradient(135deg, #e0f2fe, #bae6fd); 
    text-align: left;
  }
  .weather-status {
    font-size: 80px;         
  }
  .tips {
    display: inline-block;   
    margin-top: 8px;
    color: #0369a1;
  }
}
动态改变的一个样例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *,
    *::before,
    *::after {
      box-sizing: border-box;
    }

    html,
    body {
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
      align-items: center;
      background-color: #ffc048;
      color: #1e272e;
    }

    img {
      display: block;
      border: 0 none;
      width: 100%;
      max-width: 100%;
      height: auto;
      margin: 0;
    }

    main {
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
      width: 100vw;
      max-width: 1400px;
      margin: 0 auto;
      padding: 1rem;
    }

    .main-content {
      width: 100%;
      margin: 1rem 0 0 0;
    }

    .card {
      background: #fff;
      padding: 1rem;
      border: 1px solid #1e272e;
      box-shadow: 5px 5px 0px #1e272e;

      /* 容器查询 */
      container-type: inline-size;
      container-name: card;
      resize: both;
      overflow: scroll;
    }

    .card .content {
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
    }

    p {
      font-size: 1rem;
      line-height: 1.5;
      margin: 1rem 0 0 0;
    }

    .card .copy p:first-child {
      font-size: 1.25rem;
    }

    @media (min-width: 600px) {
      main {
        flex-direction: row;
      }

      .main-content {
        width: calc(100% - 400px);
        margin: 0 0 0 0.5rem;
      }
    }

    @container card (min-width: 600px) {
      .card .content {
        flex-direction: row;
      }

      .card img {
        align-self: flex-start;
        width: 50%;
        max-width: 400px;
      }

      .card .copy {
        margin: 0 0 0 1rem;
      }

      .card .copy p {
        font-size: 1.125rem;
      }

      .card .copy p:first-child {
        font-size: 1.75rem;
        margin: 0;
      }
    }
  </style>
</head>

<body>
  <main>
    <div class="main-content">
      <div class="card">
        <div class="content">
          <img src="./IMG_8945.JPG" alt="">
          <div class="copy">
            <p><strong>容器查询</strong></p>
            <p>CSS 容器查询允许开发者根据容器元素自身的尺寸来调整子元素的样式,而不再仅仅依赖视口宽度。这使得组件在不同布局上下文中都能保持优雅的响应式表现。</p>
            <p>调整卡片大小,当容器宽度超过 600px 时,图片和文字会自动切换为水平排列,字号也会相应增大。这就是容器查询带来的组件级响应能力。</p>
          </div>
        </div>
      </div>
    </div>
  </main>
</body>

</html>
  1. 使用resize: both;配合overflow: scroll; 实现调整容器大小。
  2. container-name: card;给开启容器查询的盒子命名,精准匹配,提高可读性也更易于维护,不加名字的话是自动检测上一个开启了容器查询的父盒子。@container card (min-width: 600px) 后面用容器查询的时候加上名字。

2. 悬浮间隙问题

在写百度页面时需要实现鼠标悬停在某个按钮上,然后出现下拉菜单或是说明,但是在鼠标从按钮移动到悬浮窗的过程中,鼠标会经过一个空白区域,导致悬浮窗消失。

解决方法

这个功能有多个html结构都可以实现

  1. 按钮元素作为父亲,包含下拉菜单元素。
html 复制代码
<div class="button">
  <div class="float">菜单</div>
</div>
  1. 按钮和下拉菜单为兄弟元素。
html 复制代码
<div class="button"></div>
<div class="float">菜单</div>
  1. 有一个包裹盒。
html 复制代码
<div class="container">
  <div class="button"></div>
	<div class="float">菜单</div>	
</div>
css 复制代码
		.container {
      position: relative;
    }
    
    .float {
      visibility: hidden;
      position: absolute;
      left: 55px;
      top: 55px;
    }

    .container:hover .float {
      visibility: visible;
    }

以第三种结构为例:

1. 缩小间隙

向上移动float盒子,让其紧贴着包裹盒 也就是container

css 复制代码
    .float {
      margin-top: -10px;
      padding-top: 10px;
    }
2. 伪元素补位

在菜单和按钮的空隙处定位一个菜单的伪元素,再加opacity: 0; / background color: transparent;将其隐藏。

但是在这里不能加display: none; / visibility: hidden;这两个属性无法被 hover。

属性 是否占位 能否被 hover
display: none 不存在 不能
visibility: hidden 占位 不能
opacity: 0 占位
background color: transparent; 占位
css 复制代码
		.float::before {
      position: absolute;
      top: -10px;
      height: 10px;
      width: 100%;
      opacity: 0;
    }
3. 延迟消失

给菜单的消失添加延迟,注意这里显示和隐藏的延迟要分开写,显示是0s,隐藏才加延迟。

css 复制代码
 		.float {
      visibility: hidden;
      transition: visibility 0s .2s;
    }
    
    .container:hover .float {
      transition: visibility 0s 0s;
      visibility: visible;
    }
4. clip-path

用 clip-path + pointer-events 让包裹盒的实际 hover 区域比视觉更大。

比较少用。

相关推荐
kyriewen1 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
IT_陈寒1 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
小林攻城狮2 小时前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
前端缘梦2 小时前
告别 TS 运行时类型漏洞!Zod 完整入门实战教程(前端 / 全栈必备)
前端·react.js·全栈
the_answer2 小时前
Webpack vs Vite 深度对比分析
前端·webpack
转转技术团队3 小时前
验证码识别实战:前端不写页面,改训模型了?
前端
MomentYY3 小时前
Temperature:AI 的“脑洞旋钮”
前端·llm·ai编程
远航_3 小时前
OpenSpec 完整详细介绍
前端·后端
召钱熏3 小时前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
SkyWalking中文站3 小时前
认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台
运维·前端·监控