37
头图

In the CSS selector family, a new type of selector, the logical selector , has been added. There are currently 4 members:

  • :is
  • :where
  • :not
  • :has

This article will lead you to understand and deepen them. Apply what you have learned and write a more modern selector.


:is pseudo-class selector

:is() The CSS pseudo-class function takes a selector list as a parameter, and selects any element in the list that can be selected by any selector.

Before, for some common style settings of the same child element of multiple different parent containers, the following CSS code may appear:

 header p:hover,
main p:hover,
footer p:hover {
  color: red;
  cursor: pointer;
}

And now with the :is() pseudo-class, the above code can be rewritten as:

 :is(header, main, footer) p:hover {
  color: red;
  cursor: pointer;
}

It does not implement a new function of a selector, it is more like a syntactic sugar, similar to the Class() syntax in JavaScript ES6, but a re-encapsulation design of the original function, which makes it easier to express an operation. Syntax, which simplifies the writing of some complex codes.

Syntactic sugar refers to the syntax in a programming language that can express an operation more easily. It can make it easier for programmers to use the language, and the operation can become clearer, more convenient, or more in line with the programmer's programming. Habit. To understand in a relatively easy-to-understand way is to change a way of writing on the basis of a previous grammar, and to achieve the same function, but the way of writing is different, mainly to make it easier for developers to understand in the process of use. .

A picture is worth a preface (quoted from New CSS functional pseudo-class selectors :is() and :where() ):

Support multi-layer stacking

Let's look at this situation again. The original CSS code is as follows:

 <div><i>div i</i></div>
<p><i>p i</i></p>
<div><span>div span</span></div>
<p><span>p span</span></p>
<h1><span>h1 span</span></h1>
<h1><i>h1 i</i></h1>

If you want to set the color of ---9894149a12a5d7b3340ff291742eb59c <div> and <p> 35a3ca88e74fd5be--- in the above HTML, the color of <span> and <i> is normal to be red. so:

 div span,
div i,
p span,
p i {
    color: red;
}

With :is() the code can be simplified to:

 :is(div, p) :is(span, i) {
    color: red;
}

The result is as follows:

Here, the cascaded use of :is() is also supported. Through the permutation and combination of :is(div, p) :is(span, i) , the selectors of the above 4 lines can be combined to achieve the same effect.

Of course, this example is relatively simple, and I can't see the power of :is() . The following example is more obvious, such a large piece of CSS selector code:

 ol ol ul,     ol ul ul,     ol menu ul,     ol dir ul,
ol ol menu,   ol ul menu,   ol menu menu,   ol dir menu,
ol ol dir,    ol ul dir,    ol menu dir,    ol dir dir,
ul ol ul,     ul ul ul,     ul menu ul,     ul dir ul,
ul ol menu,   ul ul menu,   ul menu menu,   ul dir menu,
ul ol dir,    ul ul dir,    ul menu dir,    ul dir dir,
menu ol ul,   menu ul ul,   menu menu ul,   menu dir ul,
menu ol menu, menu ul menu, menu menu menu, menu dir menu,
menu ol dir,  menu ul dir,  menu menu dir,  menu dir dir,
dir ol ul,    dir ul ul,    dir menu ul,    dir dir ul,
dir ol menu,  dir ul menu,  dir menu menu,  dir dir menu,
dir ol dir,   dir ul dir,   dir menu dir,   dir dir dir {
  list-style-type: square;
}

It can be optimized with :is() as:

 :is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) {
  list-style-type: square;
}

Pseudo-elements are not supported

There is a special case where :is() cannot be used to select the two pseudo-elements ::before and ::after . for example:

Note that only pseudo-elements are not supported, pseudo-classes such as :focus and :hover are supported.
 div p::before,
div p::after {
    content: "";
    //...
}

cannot be written as:

 div p:is(::before, ::after) {
    content: "";
    //...
}

:is The priority of the selector

Look at this interesting situation:

 <div>
    <p class="test-class" id="test-id">where & is test</p>
</div>
<div>
    <p class="test-class">where & is test</p>
</div>

We set a default color for elements with .test-class :

 div .test-class {
    color: red;
}

If, at this time, we introduce :is() for matching:

 div :is(p) {
    color: blue;
}

At this time, since div :is(p) can be seen as div p , the priority is no div .test-class high, so the color of the selected text will not change .

However, if we add a #test-id to the :is() selector, the situation is different.

 div :is(p, #text-id) {
    color: blue;
}

According to understanding, if the above selector is split, the above code can be split into:

 div p {
    color: blue;
}
div #text-id {
    color: blue;
}

Then, we have reason to guess that the <p> element with #text-id has a higher priority selector, and the color will become blue , and The other one div p because the priority is not high enough, the first text is still green .

But here, magically, both texts become blue :

CodePen Demo -- the specificity of CSS :is selector

This is because the priority of :is() is determined by the highest priority selector in its selector list. We can't take them apart.

For div :is(p, #text-id) , is:() there is an id selector inside, so all elements matched by this rule will be applied div #id this level of selector priority. This is very important, and again, for the priority of :is() selectors, we cannot separate them, they are a whole, and the priority depends on the selector with the highest priority in the selector list .

:is aliases :matches() and :any()

:is() is the latest norm named. Before, there were options with the same function, namely:

 :is(div, p) span {}
// 等同于
:-webkit-any(div, p) span {}
:-moz-any(div, p) span {}
:matches(div, p) span {}

Of course, the following three have been abandoned, and it is not recommended to continue to use them. As of today (2022-04-27) :is() has a very good compatibility. If you don't need to be compatible with IE series, you can start using it (with autoprefixer ), take a look at CanIUse :

:where pseudo-class selector

After understanding :is , we can look at :where , the two of them are very closely related. :where also takes a selector list as its argument and selects any element that can be selected by one of the selectors in that list.

Or this example:

 :where(header, main, footer) p:hover {
  color: red;
  cursor: pointer;
}

The above code uses :where , which can be approximated as:

 header p:hover,
main p:hover,
footer p:hover {
  color: red;
  cursor: pointer;
}

This is interesting, isn't it the same as the above :is ?

So what's the difference between them?

The difference between :is and :where

First, grammatically, :is and :where are identical. The core difference between them is priority .

Consider this example:

 <div>
    <p>where & is test</p>
</div>

The CSS code is as follows:

 :is(div) p {
    color: red;
}
:where(div) p {
    color: green;
}

正常按我们的理解而言, :is(div) p :where(div) p div p:where(div) p后定义,所以文字的颜色, it should be green green, but the actual color is color: red red:

This is because the difference between :where() and :is() is that the priority of :where() is always 0 , but the priority of :is() 5067dcaf2bddf41e9abf573deff2bddf41e9abf573deff2bddf41e9abf53d26d97fae93d3daee2c87330229--- is always The level is determined by the selector with the highest priority in its selector list.

The above example is not particularly obvious, let's make a little modification:

 <div id="container">
    <p>where & is test</p>
</div>

We add the last id attribute to the div and modify the above CSS code:

 :is(div) p {
    color: red;
}
:where(#container) p {
    color: green;
}

Even so, since the priority of :where(#container) is 0, the color of the text is still red. The priority of :where() is always 0 , which needs to be kept in mind during use.

combination, nesting

A very big feature of CSS selectors is the nesting of combinations. :is and :where are no exception, so they can also be nested in combination with each other. The following CSS selectors are reasonable:

 /* 组合*/
:is(h1,h2) :where(.test-a, .test-b) {
  text-transform: uppercase;
}
/* 嵌套*/
.title:where(h1, h2, :is(.header, .footer)) {
  font-weight: bold;
}

Here is a brief summary, :is and :where are very good grouping logic selectors, the only difference is that the priority of :where() is always 0 , and The priority of :is() is determined by the highest priority selector in its selector list.

:not pseudo-class selector

Below we introduce the very useful :not pseudo-class selector.

:not Pseudo-class selectors are used to match elements that do not match a set of selectors. Since its role is to prevent specific elements from being selected, it is also known as the negation pseudo-class.

For example, the HTML structure is as follows:

 <div class="a">div.a</div>
<div class="b">div.b</div>
<div class="c">div.c</div>
<div class="d">div.d</div>
 div:not(.b) {
    color: red;
}

div:not(.b) It can select all div elements except those with class .b :

Bad example of MDN? an interesting phenomenon

Interestingly, there is such an example on the page where MDN introduces :not :

 /* Selects any element that is NOT a paragraph */
:not(p) {
  color: blue;
}

Meaning, :not(p) can select any element that is not a <p> tag. However, the above CSS selector, in the following HTML structure, the measured results are not quite right.

 <p>p</p>
<div>div</div>
<span>span</span>
<h1>h1</h1>

The result is as follows:

Meaning, :not(p) can still select the <p> element. I've tried multiple browsers and the results are consistent.

CodePen Demo -- :not pesudo demo

Why is this? :not(p) <body> ,那么<body>的color blue ,由于color is an inheritable attribute, the <p> tag inherits the color attribute of ---19329a3318ee1f8bc68ec3f67b419c1d <body> , which causes the <p> to be seen as blue.

Let's make it a non-inheritable property and try it out:

 /* Selects any element that is NOT a paragraph */
:not(p) {
  border: 1px solid;
}

OK, this time <p> there is no frame, no problem! When actually using it, you need to pay attention to this layer of inheritance!

:not priority issues

Here are some issues to be aware of when using :not .

:not , :is , :where Unlike other pseudo-classes, it does not increase the priority of the selector. Its priority is the priority of its parameter selector.

Also, in CSS Selectors Level 3 , :not() only supports a single selector, and starting from CSS Selectors Level 4 , :not() supports multiple selectors, like this:

 /* CSS Selectors Level 3,:not 内部如果有多个值需要分开 */
p:not(:first-of-type):not(.special) {
}
/* CSS Selectors Level 4 支持使用逗号分隔*/
p:not(:first-of-type, .special) {
}

Similar to :is() , the :not() selector itself does not affect the priority of the selector, its priority is determined by the highest priority selector in its selector list.

:not(*) problem

Using :not(*) will match any element that is not an element, so this rule will never be applied.

Equivalent to a piece of code that makes no sense.

:not() cannot nest :not()

Dolls are prohibited. :not Pseudo-classes do not allow nesting, which means :not(:not(...)) is invalid.

:not() combat analysis

So, what are the interesting application scenarios of :not()? I list one here.

In the W3 CSS selectors-4 specification , a very interesting :focus-visible pseudo-class has been added.

:focus-visible This selector effectively displays different forms of focus depending on the user's input method (mouse vs keyboard).

With this pseudo-class, you can do, when the user uses the mouse to operate the focusable element, do not display the :focus style or make it weaker, and when the user uses the keyboard to operate the focus, use :focus-visible , so that the focusable element gets a stronger expression style.

Take a look at a simple Demo:

 <button>Test 1</button>
 button:active {
  background: #eee;
}
button:focus {
  outline: 2px solid red;
}

Click with the mouse:

It can be seen that when clicking with the mouse, the :active :focus -class is also triggered, which is not very beautiful. But if it is set outline: none it will make the keyboard user experience very bad. Because when the keyboard user uses Tab to try to switch the focus, it will be at a loss because of outline: none .

Therefore, you can use the :focus-visible pseudo-class to transform it:

 button:active {
  background: #eee;
}
button:focus {
  outline: 2px solid red;
}
button:focus:not(:focus-visible) {
  outline: none;
}

Take a look at the effect, click on the Button with the mouse and click on the Button with the keyboard control focus:

CodePen Demo -- :focus-visible example

It can be seen that clicking with the mouse will not trigger :foucs , only when the keyboard operates the focused element and uses Tab to switch the focus, outline: 2px solid red this code will take effect.

In this way, we ensure not only the click experience of normal users, but also the focus management experience of users who cannot use a mouse, and we have made great efforts in terms of accessibility.

It is worth noting that why the use of button:focus:not(:focus-visible) is used here instead of writing it directly:

 button:focus {
  outline: unset;
}
button:focus-visible {
  outline: 2px solid red;
}

Explain, button:focus:not(:focus-visible) means that the button element triggers the focus state, and it is not triggered by focus-visible, and it is understood that it supports the browser of :focus-visible , which is activated by the mouse :focus The button element of :focus , in this case, no need to set outline .

In order to be compatible with browsers that do not support :focus-visible , when :focus-visible is not compatible, the :focus pseudo-class is still required.

Therefore, here, with the help of the :not() pseudo-class, a practical effect of the program downgrade is cleverly realized.

There is a bit of a twist here, and it needs to be well understood.

:not compatibility

After going through two versions of CSS Selectors Level 3 & CSS Selectors Level 4, today (2020-05-04), except for the IE series, the compatibility of :not has been very good:

:has pseudo-class selector

OK. Finally, the most important of all logical selectors :has appeared. It's important because it was born to fill a gap in previous CSS selectors that didn't have a true parent selector in the core sense.

:has pseudo-class accepts a selector group as a parameter that matches at least one element relative to the element's :scope .

A practical example:

 <div>
    <p>div -- p</p>
</div>
<div>
    <p class="g-test-has">div -- p.has</p>
</div>
<div>
    <p>div -- p</p>
</div>
 div:has(.g-test-has) {
    border: 1px solid #000;
}

We pass the div:has(.g-test-has) selector, which means that there is a div element with class .g-test-has under the selected div.

Note that the element selected by the selector wrapped in the :has() is not selected here, but the host element of the :has() pseudo-class is used.

The effect is as follows:

It can be seen that because there is an element with class .g-test-has under the second div, the second div is added with a border.

:has() parent selector -- parent element selection for nested structures

Let's deepen our impression through a few DEMOs. :has() can also write a little more complicated.

 <div>
    <span>div span</span>
</div>

<div>
    <ul>
        <li>
            <h2><span>div ul li h2 span</span></h2>
        </li>
    </ul>
</div>

<div>
    <h2><span>div h2 span</span></h2>
</div>
 div:has(>h2>span) {
    margin-left: 24px;
    border: 1px solid #000;
}

Here, it is required to accurately select the div element whose direct child element is h2, and the direct child element under h2 has span. Note that the topmost level of the selection uses :has() on the parent div. The result is as follows:

The nested structure is reflected here, and the corresponding parent element is precisely found .

:has() parent selector -- sibling element selection

There is another situation, which was more difficult to deal with before, the selection of sibling elements of the same level structure.

Check out this DEMO:

 <div class="has-test">div + p</div>
<p>p</p>

<div class="has-test">div + h1</div>
<h1>h1</h1>

<div class="has-test">div + h2</div>
<h2>h2</h2>

<div class="has-test">div + ul</div>
<ul>ul</ul>

We want to find the ---739a8cc58d9baa2a2cd63778d8d7911e--- element in the sibling hierarchy, followed by the <h2> .has-test element, which can be written as follows:

 .has-test:has(+ h2) {
    margin-left: 24px;
    border: 1px solid #000;
}

The effect is as follows:

What is embodied here is the brother structure, and the corresponding pre-brother element is precisely found .

In this way, the parent selector that CSS has not implemented for a long time, starting with :has() , can also do it. This selector can greatly improve the development experience and solve things that previously required more JavaScript code to complete.

The above DEMO summary, you can click here CodePen Demo -- :has Demo

:has() compatibility, give it some time

It is a pity that :has() was determined in the recent Selectors Level 4 specification, and the current compatibility is still relatively bleak. As of 2022-05-04, Safari and the latest version of Chrome (V101, available through Open Experimental Web Platform features experience)

To enable this feature under Chrome, 1. Enter chrome://flags in the browser URL box, 2. Enable #enable-experimental-web-platform-features

Just be patient, give time, such a good selector will be ready for mass adoption in no time.

At last

This concludes this article, I hope it helps you :)

If you want to get the most interesting CSS information, don't miss my official account -- iCSS front-end anecdotes 😄

More wonderful CSS technical articles are summarized in my Github -- iCSS , which will be updated continuously. Welcome to click star to subscribe to the collection.

If you have any questions or suggestions, you can communicate more. Original articles are limited in writing and knowledge. If there are any inaccuracies in the article, please let me know.


chokcoco
12.3k 声望18.5k 粉丝