CSS在2024年可是整了不少狠活儿,跨文档视图过渡、滚动驱动动画、锚点定位、高度auto动画……这一波接一波的更新,让写样式的小伙伴直呼过瘾。但话说回来,哪怕已经这么香了,心里头总还有那么几个痒痒的地方,盼着能早点安排上。这篇文章就聊聊那些被大家惦记着、还没进到浏览器里的CSS宝贝们,顺便手把手教几招在没有它们的时候怎么用土办法顶上。
核心概念
先掰扯清楚几个被频繁提到的关键词。if()条件语句,顾名思义就是在CSS里写条件判断,比如根据某个元素的样式来决定另一堆样式要不要生效,这玩意儿是容器样式查询的基石。CSS mixins类似预处理器的函数块,可以把一组样式打包成一个名字到处复用。//内联注释就是像写JS那样单行注释,不用再敲/* */那么费劲。sibling-count()和sibling-index()这俩函数还没正式发布,但它们能直接拿到一个元素有多少个兄弟、自己是第几个娃,返回的是整数而不是计数器那种字符串。还有顶层图层#top-layer,那是浏览器留给弹窗、对话框的特殊位置,用z-index根本够不着它。
手把手搓方案
既然有些好东西还捂在草案里头,那现在碰到类似需求咋整?下面整几个实际场景,拿现有CSS加上一点小技巧把效果做出来。
方案一:获取孩子编号
想要给列表里的每一项搞个错开动画,或者根据顺序染不同颜色,偏偏CSS没有直接暴露索引值。目前常用的硬编码方式比较笨,但能稳稳跑起来。
第一步,在HTML里手动塞自定义属性。比如一个购物清单:
<ul>
<li style="--idx: 0">🥛 牛奶</li>
<li style="--idx: 1">🥚 鸡蛋</li>
<li style="--idx: 2">🧀 奶酪</li>
</ul>第二步,在CSS里用上这个--idx变量。比如让每个条目淡入动画延迟不一样:
li {
animation: fadeIn 0.3s ease forwards;
animation-delay: calc(var(--idx) * 0.1s);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}第三步,如果HTML是动态生成的(比如JS渲染列表),可以循环输出时顺手带上style="--idx: {{index}}"。要是实在不想动模板,还能用JS在DOM加载完后批量加上:
document.querySelectorAll('ul li').forEach((el, i) => {
el.style.setProperty('--idx', i);
});这么干有个坑:如果列表内容会动态增删,那--idx也得跟着重新计算。写代码的时候记得监听变化事件或者用MutationObserver来更新。另一种纯CSS土办法是用counter()搭配伪元素的content,但counter()吐出来的是字符串,没法直接塞到calc()里做数学运算。上面这个自定义属性的路子虽然多写几行,但胜在灵活,动画延迟、背景色深浅都能随意调。
方案二:模拟条件判断
if()还没进浏览器,但现在想根据某个条件切样式,可以拐个弯用@container样式查询或者CSS变量配合calc()玩点花活。比如要实现:当父容器背景是深色时,里面的文字自动变亮。
第一步,给父容器注册一个用来判断的变量,比如--theme: dark。然后子元素通过变量组合来切换颜色:
<div style="--theme: dark;" class="card">
<p>这段文字应该亮色</p>
</div>
<div style="--theme: light;" class="card">
<p>这段文字应该深色</p>
</div>第二步,在CSS里定义一个颜色映射表,利用calc()做数值切换。比如亮色文字#333,暗色文字#f5f5f5:
.card p {
--text-light: #f5f5f5;
--text-dark: #333;
/* 当 --theme 等于 dark 时,--is-dark 为 1,否则为 0 */
--is-dark: clamp(0, var(--theme, light), 1);
--final-color:
linear-gradient(
90deg,
var(--text-dark) 0%,
var(--text-light) 100%
);
color: var(--final-color);
}上面的clamp技巧其实要求--theme本身传的就是0或1数字,更稳妥的方式是直接传数字:
<div style="--is-dark: 1;">...</div>
<div style="--is-dark: 0;">...</div>.card p {
color: hsl(0, 0%, calc(33% + 62% * var(--is-dark, 0)));
/* 33%对应深灰,加62%变成95%接近白色 */
}第三步,更复杂的多条件可以用@container style()查询。比如根据组件自身的某个自定义属性来变样式:
.card {
--variant: primary;
}
@container style(--variant: primary) {
.card {
background: blue;
color: white;
}
}
@container style(--variant: secondary) {
.card {
background: gray;
color: black;
}
}注意@container style()目前浏览器支持还不是全覆盖,而且需要显式声明container-type。写的时候记得给容器加上container-type: inline-size,否则样式查询不干活。这招虽然绕,但已经能实现按条件分发样式了,比等if()落地要现实得多。
| 方案类型 | 适用场景 | 代码量 | 浏览器兼容 |
|---|---|---|---|
| 自定义属性传值 | 静态索引 | 小 | 全兼容 |
| JS动态注入 | 动态列表 | 中 | 全兼容 |
| 样式查询 | 条件样式 | 中 | 较新 |
方案三:平衡换行布局
flex-wrap换行后要么左边挤一堆右边空荡荡,要么拉伸得歪七扭八。想要那种最后一行也整整齐齐的效果,现在可以用Grid布局加自动填充来模拟。
第一步,把容器从display: flex改成display: grid。然后设置grid-template-columns用repeat(auto-fill, minmax(120px, auto))。这样每个卡片最小120px,能塞几个就塞几个,最后一行不会强行拉伸。
.card-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 1rem;
}
.card {
background: #f0f0f0;
padding: 1rem;
text-align: center;
}第二步,如果硬要用flex实现接近效果,可以配合margin和伪类。比如给最后一个元素加margin-right: auto让它靠左,但这个方法在行数多时很鸡肋。更干净的做法是改Grid:用media query配合max-width断点来调整grid-template-columns的重复次数。
第三步,针对响应式场景,可以结合clamp()让每个卡片宽度在一个区间内自动缩放。比如:
.card {
width: clamp(120px, 25%, 200px);
flex-grow: 1;
}这样在flex容器里,每个卡片最小120px、最大200px,并且会均匀填满多余空间,换行后最后一行也能保持差不多的宽度。写的时候要留神,clamp的中间值如果是百分比,是相对于父容器的宽度,得保证父容器有确定的宽度才行。另外这种方案在卡片数量少的时候看起来还行,数量多了还是Grid更稳当。
其他念叨念叨
除了上面那几个能动手搓的,还有些愿望纯纯等着浏览器发善心。比如给<link>标签加个layer属性,引用第三方reset样式时直接塞到指定层里,不用再手写@layer包裹。还有能把元素扔到#top-layer顶层图层而不靠popover,那样做悬浮菜单、全局提示就不用跟z-index: 9999斗智斗勇了。甚至有人想要random()函数,让背景颜色随机撒花,或者能精准选中文本里的某个词单独加样式。这些要是真上了线,CSS的乐子可就更大了。
