CSS :has() 终于能做这些事:表单联动、卡片自适应、状态高亮,附完整案例

2026-09-05 0 205

最近手上有个内部后台改版,页面里有一堆表单和卡片。以前这种需求要么写一堆JavaScript去监听状态,要么在CSS里靠“兄弟选择器”勉强讨生活。自从把项目基础览升级到支持`:has()`的浏览器以后,好些东西直接用CSS就写完了。今天不说虚的,直接讲三个我实际写进项目的场景,把代码贴出来聊聊。

一、之前没有:has()我们是怎么憋的

想根据一个子元素的状态去改父元素的样式,这在老版本CSS里基本没门。比如表单里有个开关,开了以后后面整个块要亮起来。以前只能给开关的父级写一个class,用JS去`classList.add`。要么就用`:has()`以前的假动作——把开关放在前面,再加个兄弟选择符`~`,但遇到DOM结构被各种包裹的时候就没用。

好在新版浏览器里`:has()`已经是个“稳定”的伪类了,不光能选父元素,还能隔好几层选,甚至可以用来选择“不包含某个元素”的容器。反正给我的感觉是,现在写样式终于可以“看里面”了。

二、案例一:表单里的权限开关状态联动

需求是这样的:有一项“绕过审批”的开关,下面跟着一行风险提醒文字和一个额外输入框。当这个开关被打开时,整张卡片要出现一个高亮边框,同时风险提醒文字要变红。

原先的HTML大概是这样,为了触发CSS的`:checked`,`<input>`必须放在同级。

<div class="risk-card">
    <input type="checkbox" id="bypass-toggle" class="toggle-input">
    <label for="bypass-toggle" class="toggle-label">启用绕过审批</label>
    <div class="risk-content">
        <p class="warning-text">注意:绕过审批后一切责任由申请人承担。</p>
        <input type="text" placeholder="填写理由">
    </div>
</div>

过去要想让“切换到ON”时改变`.risk-card`的边框色,得写两段CSS,还得保证input就是第一个元素:

#bypass-toggle:checked ~ .risk-content { border-color: red; }
#bypass-toggle:checked ~ .warning-text { color: darkred; }

如果输入框被包在一个flex容器里,改DOM结构就很痛苦。现在用`:has()`根本不需要调整结构,想选哪个父元素就选哪个。

.risk-card:has(.toggle-input:checked) {
    border: 2px solid #e56b2a;
}
.risk-card:has(.toggle-input:checked) .warning-text {
    color: #d82828;
}
.risk-card:has(.toggle-input:checked) .risk-content input {
    background: #fffaf4;
}

这段代码我看一眼就知道:如果卡片里是个被勾选的开关,那么给整卡加粗边框,并且让里面的警告语变成红色,输入框也给个暖底。这种表述方式很自然,就像在说“这个卡片状态是启用的”。

三、案例二:带有“新品”标签的卡片自适应

然后是列表卡片。最近运营要求商品卡片上如果有个“新品”标签,那么卡片左上角要加一个彩条,同时当卡片宽度在某个范围内时,标题要单行省略。

虽然有很多布局方案,但结合上CSS容器查询才是真的舒服。我先给卡片容器设了`container-type: inline-size`,然后不同的尺寸区间打不同的布局。

HTML部分:

<div class="product-card">
    <span class="new-tag">新品</span>
    <h3 class="product-name">智能降噪耳麦 Wireless Pro Max</h3>
    <p class="product-desc">支持40dB主动降噪,30小时续航</p>
    <div class="buy-row">
        <span class="price">¥899</span>
        <button>立即购买</button>
    </div>
</div>

如果我还是用全局媒体查询,那么只能等屏幕宽度变化时让卡片调整。但现在我想要的是:卡片本身宽度变化时(比如栅格布局里某一列特别宽),里面的排版自动跟着变。

.product-card {
    container: card / inline-size;
    position: relative;
    padding: 1rem;
    border: 1px solid #ddd;
    border-radius: 12px;
}

/* 让带new-tag的卡片边框加一条颜色 */
.product-card:has(.new-tag) {
    border-left: 4px solid #fb8b1e;
}

/* 当卡片容器宽度小于280px时 */
@container card (max-width: 280px) {
    .product-name {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .product-desc {
        display: none;
    }
    .buy-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 6px;
    }
}

/* 当容器宽度在280-450px之间 */
@container card (min-width: 280px) and (max-width: 450px) {
    .product-name {
        font-size: 1.1rem;
    }
    .product-desc {
        margin: 8px 0;
    }
}

注意这里的重点是`.product-card:has(.new-tag)`。假设有时候列表里有些卡片是普通的,没有“新品”二字,那这个选择器就不会命中。这比后端专门传一个`is_new`然后拼一个class还要方便,因为不管后端怎么改了DOM结构,只要标签还在,样式就能生效。

四、案例三:输入框是否填写内容的提示条

再比如做表单验证。有些字段属于选填,但如果不填又希望后面的提示能逐渐淡化,填了以后提示就给个橙色的底部线。这个也可以用`:has()`。

代码大概是这样:

<div class="form-field">
    <input type="text" placeholder="邀请码(选填)" class="field-input">
    <span class="field-help">填写邀请码可获赠试用天数</span>
</div>
.form-field {
    padding: 12px;
    border-radius: 8px;
    background: #f8f8fa;
}

/* 只要输入框里有内容,就让整个字段背景变亮 */
.form-field:has(.field-input:placeholder-shown) {
    background: #f0f0f2;
}

/* 或者反过来,当有值时提示变绿 */
.form-field:has(.field-input:not(:placeholder-shown)) .field-help {
    color: #2a834d;
}
.form-field:has(.field-input:focus) {
    box-shadow: 0 0 0 1px #4a90e2;
}

原来要实现同样效果,得监听`input`事件去比较`value`值,然后判断是否切换class。现在只需要利用`placeholder-shown`和`:not()`搭配,基本等于纯CSS监听内容状态。

五、说一下兼容性坑

用之前我查了`caniuse`。`:has()` 从 Chrome 105、Safari 15.4、Firefox 121开始支持。到这个月,基本主流的浏览器都能用了。如果你的用户还在用旧版企业浏览器,那就要加个@supports去兜底:

@supports selector(:has(> *)) {
    .product-card:has(.new-tag) {
        border-left: 4px solid #fb8b1e;
    }
}

别指望所有浏览器。另外注意,`:has()`里不能放伪元素,比如`:has(::before)`是无效的,但你可以在`:has(span)` 后面自己单独写`::before`。还有就是它并不慢,但如果页面特别复杂,建议不要在全局大范围使用嵌套很深的`:has()`。

六、容器查询配:has() 才是真正顺手

很多人以为容器查询就只是“组件自适应”,没想到和:has()配合才是完全体。比如一个卡片组件内部的布局可以在自己宽度不够时改变排版,同时组件外层还能根据标签去加装饰。组件不需要知道自己在哪个栏位,只要它的容器宽度缩放,自己就有反馈。

例如上一段代码写完后,我们把那卡片放在一个两列栅格里,左边列很窄,右边列很大。窄的时候就会触发单行省略和上下排列,宽的时候保持舒适的多行展示。这个不是用media query做的,完全是卡片自我调节。

七、个人感受

我不是说以后不用JavaScript,而是很多小型交互状态真的可以用CSS来观察。尤其是表单和列表卡片这类东西,一行`:has()`能省掉用户切换时写十几个style或者加监听函数的时间。而且代码可维护性也高:样式跟着结构走,不是跟着逻辑走。项目里的工程师看这个代码,会明白“哦,带上标签就变高亮”这个视觉规则。

如果你也在支持这些特性的系统里做开发,建议试试把“JS改类名”的活换成CSS状态选择器。跑起来你会感觉更顺手。

CSS :has() 终于能做这些事:表单联动、卡片自适应、状态高亮,附完整案例
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 css CSS :has() 终于能做这些事:表单联动、卡片自适应、状态高亮,附完整案例 https://www.taomawang.com/web/css/2699.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务