我如何 select 基于页面中另一个元素状态的元素 CSS?

How do I select an element based on the state of another element in the page with CSS?

我有可以反映不同状态的元素,可以由用户触发(:hover:focus 等)或由服务器操作(data-status="finished"disabled,等等)。

我可以定位状态已更改的元素,但我似乎无法根据相关元素的状态找到定位 DOM 中其他元素的方法。

示例:

<section>
    <div>Element 1</div>
    <div data-status="finished">Element 2</div>
    <div>Element 3</div>
</section>
<section>
    <div>Element 4</div>
    <div class="blink">Element 5</div>
    <div>Element 4</div>
    <div>Element 4</div>
    <div class="spin">Element 4</div>
    ...
</section>

或者只在服务器端呈现具有适当样式的元素。

是否有 CSS 选择器可以让我根据目标元素的状态指定应选择哪些元素?

类似于:

div[data-status~=finished]:affect(.blink, .spin)

这样我也可以 将不具有相同父元素 的元素与 CSS 一起定位?

在 CSS 的当前状态下,您可以实现的目标非常有限。

简而言之 - 您可以让 CSS 元素对元素的状态变化做出反应,如果它们具有相同的父元素并且是兄弟姐妹,或者是父元素的子元素。


CSS 中元素的状态由 pseudo-classes 处理,它涵盖了浏览器根据用户输入处理的大多数典型交互。

虽然这使您能够处理 DOM 树中元素及其子元素的当前状态的视觉外观,但您仍然无法使其他非子元素做出反应(通过视觉变化样式)到元素的当前状态,因为 CSS 不提供特定类型的 select 或者以灵活的方式做到这一点。

但是,您可以将伪classes 与其他类型的CSS selector 结合起来,并在某些情况下使其工作(我将使用悬停状态,因为这是最明显的):

伪class+相邻兄弟select或

如果 element1element2 在文档树中共享相同的父级并且 element1 紧接在 element2 之前,则

或匹配。 (W3C specification of Adjacent sibling selectors)

div:hover + div {
    background:red;
}
Hover on elements:
<div>Element 1</div>
<div>Element 2</div>
<div>Element 3</div>
<div>Element 4</div>
<div>Element 5</div>

伪class+一般兄弟组合

此组合与相邻同级 selector 的工作方式相同,只是被 selected 的元素不需要紧接在第一个元素之后;它可以出现在它之后的任何地方。

div:hover ~ div {
    background:red;
}
<div>Element 1</div>
<section>Element 2</section>
<em>Element 3</em>
<div>Element 4</div>
<div>Element 5</div>

Attribute selector + General sibling combinator / Attribute selector + adjacent sibling selector

DOM 个元素的状态通常存储在数据属性中。 CSS 为您提供了一个属性 selector,它允许您根据属性的值应用样式。

以下示例为状态为 »finished« 的元素之后的所有元素设置较低的不透明度:

div[data-status~=finished] ~ div {
    opacity: 0.3;
}
<div>Element 1</div>
<div data-status="finished">Element 2</div>
<div>Element 3</div>
<div>Element 4</div>
<div>Element 5</div>

:not()

在某些情况下,:not() 可以帮助您 select 所有其他没有激活状态的元素,但是,非简单的 select 或者还没有CSS3 in :not() 支持,尽管它们在新草案 Selectors Level 4 draft 中被提出。所以目前,你可以div:not(.class, .class)——因为只支持简单的selector(类型selector,通用selector ,属性selector,classselector,IDselector,or伪class).


最后你可以构建一些复杂的 selector 来实现你想做的事情,但通常是静态的,并且可能会在 DOM 结构发生变化时立即停止工作,但对于极端情况,您可能会解决问题。 MDN's pseudo selector list 组合起来会很方便。

不幸的是,在写这篇文章的时候,你仍然需要在CSS之外处理它以获得可靠的解决方案。

由于@easwee 已经发布了一个很好的答案,我不会重复他讨论的任何 pseudo-class 选择器,


我们现在在 css3 中有一个新的伪 class 是 :target

The :target pseudo selector in CSS matches when the hash in the URL and the id of an element are the same.

(:target 的具体用途是设置目标元素的样式,该元素当前在视口顶部可见)

因此,当与另一个伪 classes 或兄弟选择器一起使用时,这实际上可以根据用户交互(具体单击)被(误)用于更改其他元素样式。

例如:parent 的兄弟姐妹的目标 child。

:target section div[data-status=finished] {
  color: red;
}
a,
a:visited {
  text-decoration: none;
  color: #000;
}
section {
  border: 1px solid grey;
}
<nav id='nav'>
  <h4>This is Navigation section</h4>
  <section>
    sibling
    <div><a href='#nav'>(child)click me to target child  of sibling of parent</a>
    </div>
  </section>
  <section>
    sibling
    <div data-status="finished">(child)I am child of parent of sibling</div>
  </section>

</nav>

请注意,在引入 :target 之前,无法为 parent 元素或 html 层次结构中的任何其他元素设置样式。它仍然是新的,做这样的事情(根据点击另一个元素选择另一个元素)并不是 :target 设计的原因。

缺点

  1. 使用 target 定位元素,要求不要在单个页面中使用太多,否则会使您在整个页面中随意导航。

  2. 元素的目标样式将保留在其上,直到目标保持不变。

你可以玩这个fiddle

规范问题的一般答案

How do I select an element based on the state of another element in the page with CSS?

是它恰好取决于三个条件:

  1. 是否可以使用简单的选择器来表示这些元素的状态,
  2. 是否可以使用组合子来表达这两个元素之间的结构关系,以形成一个单个复杂选择器,以及
  3. 您要定位的元素是否可以成为生成的复杂选择器的主题。

current Selectors standard has some interesting and sometimes potentially powerful features, the way it is designed makes it extremely limited in area #2 (with #3 being a direct consequence). Some of these limited possibilities are enumerated in other answers, e.g. through the most basic use of child and sibling combinators, (which actually relates to condition #1), or .

另一方面,由于这个原因,无法使用选择器中当前可用的方法解决问题中给出的问题。大部分原因归结为缺少 parent 选择器 and/or 之前的同级选择器,这两个功能看起来微不足道,但具有某些含义,使它们难以定义或很好地实现。总结:

  1. 是的,这些元素的状态可以用简单的选择器来表示:div[data-status~=finished]代表前者,.blink.spin 对于后两者。

  2. 第一个元素可以用section > div[data-status~=finished]表示,两个主题元素分别可以用section + section > .blinksection + section > .spin表示。问题是不可能编写包含所有这些结构的复杂选择器,因为组合器是 one-way,并且没有 parent 对应 child 组合器在第一个 section 元素。

  3. 假设前两题的答案也是"yes",.blink.spin都可以作为其[=203=的主语]own 复杂选择器。 (下一节会详细介绍。)

如果您被定向到这个问题,很可能您要解决的问题(如上面给出的问题)由于这些限制而无法使用选择器解决。

upcoming standard 拥有一些新功能,这些功能将极大地丰富选择器语法并可能将其(和 CSS)打开到许多新的可能性,包括示例问题的可能解决方案。所有这些内容都将在以下部分中介绍,但首先我将解释每个条件的含义以及它与给定示例的关系:

元素状态和元素之间的结构关系

选择器的定义特征是它表示文档树中一个或多个元素的特定结构。这不仅仅是我编造的东西——你实际上可以在 informative overview of the Selectors standard:

中找到这个描述

A Selector represents a structure. This structure can be used as a condition (e.g. in a CSS rule) that determines which elements a selector matches in the document tree, or as a flat description of the HTML or XML fragment corresponding to that structure.

Selectors may range from simple element names to rich contextual representations.

每个元素都由一个或多个简单选择器的序列表示。这个序列被称为复合选择器(我在这里使用的是 Selectors 4 中的术语,因为它比 Selectors 3 中使用的术语更清晰——参见 this answer 的 non-exhaustive 术语列表)。

每个简单的选择器代表一个元素的特定状态。有一些简单的选择器用于匹配元素的类型(或标记名称)、class 名称、ID 或任意属性。还有 pseudo-classes,表示抽象和其他未在文档树中直接表示的特殊状态,例如元素在其层次结构中的顺序和位置(:nth-child():nth-of-type()),用户交互(:hover:active:focus:checked)、超链接的访问量(:link:visited)等等.

在给定的示例中,具有 data-status 属性的 div 元素的 space-delimited 值包含 finished 可以用类型选择器和属性选择器表示:

div[data-status~=finished]

如果您希望选择器仅在指针位于该元素上时应用,只需输入 :hover pseudo-class:

div[data-status~=finished]:hover

复合选择器通过组合器链接以形成复合选择器。这些组合符,即您可能熟悉的 >+~ 符号,表达了每个复合选择器所表示的元素之间的关系。仅使用这两个工具,您就已经能够创建一些非常有趣的结果,如此处的其他答案所示。我在 .

中更深入地解释了这些基础知识

在给出的例子中,可以建立如下结构关系:

  • 第一个 section 元素是 div[data-status~=finished] 的 parent。这是使用 child combinator >:

    表示的
    section > div[data-status~=finished]
    
  • 第二个 section 紧跟在第一个之后作为其兄弟。这是使用 adjacent sibling combinator +:

    表示的
    section + section
    
  • 另外,第二个section.blink.spin的parent。这可以使用两个选择器来表示,每个选择器一个 child:

    section + section > .blink, 
    section + section > .spin
    

    为什么需要两个选择器?在这种情况下,主要是因为目前没有 subgrouping two compound selectors into one 的语法,因此您必须分别表示每个 child 元素。即将推出的 Selectors 4 标准引入了:matches() pseudo-class 将提供这种分组功能:

    section + section > :matches(.blink, .spin)
    

现在,由于复合选择器中的每个复合选择器代表一个元素,因此 section + section 代表两个兄弟元素,section > div 代表一个 parent 和一个 child,而section + section > div表示next-sibling的child,你会认为parent组合子和previous-sibling组合子是相当多余的。那么为什么我们经常会遇到这些问题:

  • Is there a CSS parent selector?
  • Is there a "previous sibling" CSS selector?

而且,更重要的是,为什么这两个问题的答案都是?原因在下一点解决:

选择器的主题

选择器的subject总是由最右边的复合选择器表示。例如,选择器section + section > div代表三个元素,其中div是主语。您可能会说 divselected,或 targeted,如问题所示,但如果您曾经想过是否有是一个专有名词,它被称为选择器的主题。

在 CSS 规则中,样式应用于选择器主题所代表的元素。任何 child 框和 pseudo-element 框在适当的地方继承此元素的样式。 (例外情况是,如果选择器的主题包含 pseudo-element,在这种情况下,样式仅直接应用于 pseudo-element。)

采用上一节中的选择器,我们有以下内容:

  • section > div[data-status~=finished]的主题是div[data-status~=finished]
  • section + section 的主题是 second section 选择器。
  • section + section > .blink, section + section > .spin的主题分别是.blink.spin
  • 使用:matches()section + section > :matches(.blink, .spin)的主语是:matches(.blink, .spin)

因此,我们似乎确实需要 parent 选择器或 previous-sibling 选择器。但请记住,选择器已经可以表示复杂的结构。与其简单地添加与现有组合器相反的新组合器,不如寻找更灵活的解决方案,这正是 CSSWG 一直在做的事情。

这使我们从原始问题中得出以下结论:

Is there a CSS selector that would let me specify which elements should get selected based on target element state?

这个问题的答案是否定的,并且将保持否。然而,在 Selectors 4 的早期草稿中(来自 FPWD up to the latest working draft from May 2013),有一项新功能的提议,可以让您选择除最右边的那个之外的任何复合选择器,并将其指定为主题选择器。

一个潜在的解决方案

但是,主题指标最近被删除,取而代之的是 :has() pseudo-class(这又是 adopted from jQuery). I speculate on a likely reason :

The reason :has() is more versatile is because, with the subject selector, it was never made clear in any draft if a single complex selector could have more than one subject selector (since a single complex selector can only ever have one subject) and/or if functional pseudo-classes such as :matches() accepted the subject selector. But because a pseudo-class is a simple selector, you know that :has() can be accepted anywhere a pseudo-class is accepted.

因此,虽然您无法更改选择器的主题,但由于 pseudo-class 的性质,:has() 将完全取消这样做的需要。最好的部分是它做到了这一点——然后是一些——所有这些都没有从根本上改变选择器语法。

事实上,示例问题 可以使用 Selectors 4 的 :has():

来解决
/* Combined with the :matches() example from above */
section:has(> div[data-status~=finished]) + section > div:matches(.blink, .spin)

注意 child 组合子的使用:这将相对选择器参数的范围限定为第一个 section 的 children。是的,这就是全世界的 Web 开发人员多年来一直想要的难以捉摸的 "parent selector"。

并且由于 :has() 来自 jQuery,您今天可以使用它 ,虽然 :matches() 还不存在,所以您'同时,我必须将其替换为对 .filter() 的调用:

$('section:has(> div[data-status~=finished]) + section > div')
    .filter('.blink, .spin')
    .css('color', 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>
    <div>Element 1</div>
    <div data-status="finished">Element 2</div>
    <div>Element 3</div>
</section>
<section>
    <div>Element 4</div>
    <div class="blink">Element 5</div>
    <div>Element 4</div>
    <div>Element 4</div>
    <div class="spin">Element 4</div>
    ...
</section>

它用途广泛,不仅可以让您 "target elements that don't have the same parent",还可以让您处理完全不相关的元素,包括在文档树中的位置可能互不相同的元素。这将 有效地消除 上面的条件 #2,尽管这样做有一个重要的警告,我稍后会谈到。例如,如果我们假设所讨论的 div 元素可能出现在彼此之间没有结构关系的任何地方,则 :has() 允许您执行以下操作:

:root:has(div[data-status~=finished]) div:matches(.blink, .spin)

... 当 div[data-status~=finished] 存在 文档树中的任何位置时

... 找到 div.blink, div.spin,因为文档树中的任何元素都必须是文档根元素。

现在,我提到的警告是,使用带有 :has() 的任意复杂选择器可能会对性能产生严重影响,这就是为什么最长的时间 parent 选择器从未被实现,并且两者主题指标和 :has() 尚未实施。后两者尤其有问题,因为 "rightmost compound selector" 定义 serves as the basis of mainstream CSS selector engines,而这两个特征试图完全挑战它。

这也是为什么 :has() 暂时被排除在 fast profile and may therefore not be usable in CSS, as it requires real-time selector matching during page rendering, a situation that is undeniably performance-critical. It will still be accessible through the DOM methods querySelector(), querySelectorAll() and matches()(以及碰巧使用它们的任何选择器库)之外的原因。

也就是说,CSSWG 有 plans 来测试 :has() 的有限变体(例如 wih 单个 child 组合器或兄弟组合器)以查看它们是否可以很好地实现以包含在 CSS 中,这将满足 大量 大多数用例, 包括上面第一个例子

结论

不幸的是,CSS 选择器语法在今天仍然非常有限;然而,该标准的新提案将带来强大的新可能性,其中一些新增功能是基于 jQuery 等选择器库已经提供的功能。希望实现将支持这些新功能,以便在 CSS.

中使用