You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CSS选择器在兄弟元素上表现不一致的问题排查

问题描述

我在应用里做了一个包含两张卡片的组件,想要实现卡片hover和点击时的动画效果。发现当其中一张处于激活(点击后)状态时,另一张仍能触发hover,于是写了下面的CSS规则:

// 规则逻辑:自身data-active="true",或者hover时,没有后续兄弟元素(+或~)带有data-active="true"
&[data-active="true"],
&:hover:not(:has(+ [data-active="true"])),
&:hover:not(:has(~ [data-active="true"])){
  flex: 2 auto;
}

但存在bug:当DOM中第一张卡片active=true、第二张active=false时,第二张仍能触发hover效果;反过来第二张激活时,第一张不会触发hover。想知道为什么第二张的表现不一样?

已尝试方案

  • 拆分条件避免冲突,问题依旧;
  • 补充条件,未解决问题;
  • 将卡片改为<button>元素使用disabled属性,但无法通过JavaScript切换状态。

完整代码

卡片容器组件(onClick用于切换data.active状态)

<CardsBox>
    <OfferCard onClick={handleClick} heading="Front-End Services"/>
    <OfferCard onClick={handleClick} heading="Back-End Services"/>
</CardsBox>

OfferCard组件

const Card = styled(motion.button)`
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(2.5rem) saturate(0%);
  -webkit-backdrop-filter: blur(2.5rem) saturate(0%);
  background-color: rgba(176, 123, 255, 0.63);
  border-radius: var(--md-radius);
  box-shadow: 0px 0px 0.5rem var(--neon-purple);
  padding: 3rem;
  flex: 1;
  transition: all 0.8s cubic-bezier(0.19, 1, 0.22, 1),
    flex 0.8s cubic-bezier(0.19, 1, 0.22, 1);

  cursor: pointer;

  h4 {
    font-size: var(--secondary-heading);
    font-weight: 400;

    transition: all 0.2s ease-in-out;
  }

  &[data-active="true"],
  &:hover:not(:has(+ [data-active="true"])),
  &:hover:not(:has(~ [data-active="true"])){
    flex: 2 auto;

    h4 {
      font-weight: 700;
    }
  }
}

export default function OfferCard({ onClick, heading, children }) {
      return (
         <Card id="offer-card" data-active="false" onClick={onClick}>{heading}</Card>
      )
}

问题原因与解决方案

问题原因

你的CSS选择器逻辑有漏洞:~(匹配当前元素之后的所有兄弟)和+(匹配当前元素的紧邻后续兄弟)这两个选择器只能检测当前元素之后的兄弟元素,完全无法匹配当前元素之前的兄弟。

  • 当第二张卡片激活时,第一张卡片的:has(~ [data-active="true"])会匹配到后面的第二张,所以:hover:not(...)的条件不成立,hover样式不会触发;
  • 当第一张卡片激活时,第二张卡片前面的兄弟(第一张)不在~和+的匹配范围内,所以:has(+ [data-active="true"])和:has(~ [data-active="true"])都不成立,not(...)条件为真,hover样式就会触发。

说白了:CSS里没有“前面的兄弟”选择器,你的规则只能检测当前元素后面有没有激活的卡片,检测不到前面的。

解决方案

方案1:给父容器加状态(推荐)

既然CSS无法检测前面的兄弟,不如把激活状态放到父容器上,通过父容器的状态统一控制子元素的hover行为:

  1. 修改卡片容器,添加激活卡片的索引状态:
import { useState } from 'react';

const CardsContainer = () => {
  const [activeIndex, setActiveIndex] = useState(null);
  const handleClick = (index) => {
    // 点击同一卡片取消激活,否则切换到对应卡片
    setActiveIndex(activeIndex === index ? null : index);
  };

  return (
    <CardsBox data-active-index={activeIndex}>
      <OfferCard onClick={() => handleClick(0)} heading="Front-End Services"/>
      <OfferCard onClick={() => handleClick(1)} heading="Back-End Services"/>
    </CardsBox>
  );
};
  1. 修改Card的CSS规则:
const Card = styled(motion.button)`
  /* 原有样式保持不变 */

  /* 自身激活,或者没有激活卡片时hover,或者当前卡片就是激活的那个时hover */
  &[data-active="true"],
  [data-active-index="null"] &:hover,
  [data-active-index="0"] &:nth-child(1):hover,
  [data-active-index="1"] &:nth-child(2):hover {
    flex: 2 auto;
    h4 {
      font-weight: 700;
    }
  }

  /* 有激活卡片时,非激活卡片hover不生效 */
  [data-active-index="0"] &:nth-child(2):hover,
  [data-active-index="1"] &:nth-child(1):hover {
    flex: 1;
    h4 {
      font-weight: 400;
    }
  }
`;

方案2:用JavaScript控制hover状态

通过监听事件,动态判断是否允许当前卡片触发hover效果:

  1. 修改OfferCard组件:
import { useState, useEffect } from 'react';

export default function OfferCard({ onClick, heading, isActive }) {
  const [allowHover, setAllowHover] = useState(true);

  const checkActiveSiblings = () => {
    // 查找所有激活的卡片,排除自身
    const activeCards = document.querySelectorAll('[data-active="true"]:not(#offer-card)');
    setAllowHover(activeCards.length === 0);
  };

  useEffect(() => {
    checkActiveSiblings();
    // 监听点击事件,每次点击后检查激活状态
    document.addEventListener('click', checkActiveSiblings);
    return () => document.removeEventListener('click', checkActiveSiblings);
  }, []);

  return (
    <Card 
      id="offer-card" 
      data-active={isActive} 
      onClick={onClick}
      className={allowHover ? '' : 'disable-hover'}
    >
      {heading}
    </Card>
  );
}
  1. 补充CSS规则:
const Card = styled(motion.button)`
  /* 原有样式保持不变 */

  &.disable-hover:hover {
    flex: 1;
    h4 {
      font-weight: 400;
    }
  }
`;

内容的提问来源于stack exchange,提问作者Marya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:47:03