容器查询和悬浮间隙问题
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>
- 使用
resize: both;配合overflow: scroll;实现调整容器大小。 container-name: card;给开启容器查询的盒子命名,精准匹配,提高可读性也更易于维护,不加名字的话是自动检测上一个开启了容器查询的父盒子。@container card (min-width: 600px)后面用容器查询的时候加上名字。
2. 悬浮间隙问题
在写百度页面时需要实现鼠标悬停在某个按钮上,然后出现下拉菜单或是说明,但是在鼠标从按钮移动到悬浮窗的过程中,鼠标会经过一个空白区域,导致悬浮窗消失。
解决方法
这个功能有多个html结构都可以实现
- 按钮元素作为父亲,包含下拉菜单元素。
html
<div class="button">
<div class="float">菜单</div>
</div>
- 按钮和下拉菜单为兄弟元素。
html
<div class="button"></div>
<div class="float">菜单</div>
- 有一个包裹盒。
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 区域比视觉更大。
比较少用。