web-dev-qa-db-fra.com

Pourquoi mon sélecteur jQuery: not () ne fonctionne-t-il pas en CSS?

J'ai cette disposition:

<div id="sectors">
    <h1>Sectors</h1>
    <div id="s7-1103" class="alpha"></div>
    <div id="s8-1104" class="alpha"></div>
    <div id="s1-7605" class="beta"></div>
    <div id="s0-7479"></div>
    <div id="s2-6528" class="gamma"></div>
    <div id="s0-4444"></div>
</div>

Avec ces règles CSS:

#sectors {
    width: 584px;
    background-color: #ffd;
    margin: 1.5em;
    border: 4px dashed #000;
    padding: 16px;
    overflow: auto;
}

#sectors > h1 {
    font-size: 2em;
    font-weight: bold;
    text-align: center;
}

#sectors > div {
    float: left;
    position: relative;
    width: 180px;
    height: 240px;
    margin: 16px 0 0 16px;
    border-style: solid;
    border-width: 2px;
}

#sectors > div::after {
    display: block;
    position: absolute;
    width: 100%;
    bottom: 0;
    font-weight: bold;
    text-align: center;
    text-transform: capitalize;
    background-color: rgba(255, 255, 255, 0.8);
    border-top: 2px solid;
    content: attr(id) ' - ' attr(class);
}

#sectors > div:nth-of-type(3n+1) {
    margin-left: 0;
}

#sectors > div.alpha { color: #b00; background-color: #ffe0d9; }
#sectors > div.beta  { color: #05b; background-color: #c0edff; }
#sectors > div.gamma { color: #362; background-color: #d4f6c3; }

J'utilise jQuery pour ajouter la classe unassigned à des secteurs qui n'ont pas autrement l'une des classes alpha, beta ou gamma:

$('#sectors > div:not(.alpha, .beta, .gamma)').addClass('unassigned');

Ensuite, j'applique différentes règles à cette classe:

#sectors > div.unassigned {
    color: #808080;
    background-color: #e9e9e9;
    opacity: 0.5;
}

#sectors > div.unassigned::after {
    content: attr(id) ' - Unassigned';
}

#sectors > div.unassigned:hover {
    opacity: 1.0;
}

Et tout fonctionne parfaitement dans les navigateurs modernes.

Aperçu jsFiddle interactif

Mais vu que le sélecteur :not() dans jQuery est basé sur :not() dans CSS , je pensais pouvoir le déplacer directement dans ma feuille de style, donc je n'aurais pas à compter sur l'ajout d'une classe supplémentaire à l'aide de jQuery. En outre, je ne suis pas vraiment intéressé par la prise en charge des anciennes versions d'IE, et d'autres navigateurs ont un excellent support pour le sélecteur :not().

J'essaie donc de changer la partie .unassigned Ci-dessus en sachant que je n'aurai que les secteurs Α, Β et Γ dans ma mise en page):

#sectors > div:not(.alpha, .beta, .gamma) {
    color: #808080;
    background-color: #e9e9e9;
    opacity: 0.5;
}

#sectors > div:not(.alpha, .beta, .gamma)::after {
    content: attr(id) ' - Unassigned';
}

#sectors > div:not(.alpha, .beta, .gamma):hover {
    opacity: 1.0;
}

Mais dès que je fais cela, il cesse de fonctionner - dans tous les navigateurs ! Mes secteurs non attribués ne sont plus grisés, décolorés ou étiquetés "Non attribués".

Aperçu jsFiddle mis à jour mais pas si interactif

Pourquoi le sélecteur :not() fonctionne-t-il dans jQuery mais échoue-t-il en CSS? Cela ne devrait-il pas fonctionner de manière identique dans les deux endroits, car jQuery prétend être "conforme à CSS3", ou y a-t-il quelque chose qui me manque?

Existe-t-il une solution de contournement CSS pure pour cela ou devrai-je compter sur un script?

58
BoltClock

Pourquoi le sélecteur :not() fonctionne-t-il dans jQuery mais échoue-t-il en CSS? Cela ne devrait-il pas fonctionner de manière identique dans les deux endroits, car jQuery prétend être "conforme à CSS3", ou y a-t-il quelque chose qui me manque?

Peut-être que cela devrait, mais il s'avère que ne le fait pas: jQuery étend le sélecteur :not() de telle sorte que vous peut passer n'importe quel sélecteur pour lui , peu importe sa complexité, et je soupçonne que la principale raison en est la parité avec le méthode .not() , qui prend également tout sélecteur arbitrairement complexe et filtre en conséquence. Il maintient en quelque sorte une syntaxe de type CSS, mais il s'étend de ce qui est défini dans la norme.

Comme autre exemple, cela fonctionne très bien (je sais que c'est un exemple incroyablement ridicule par rapport à ce qui est donné dans la question, mais c'est juste à des fins d'illustration):

/* 
 * Select any section
 * that's neither a child of body with a class
 * nor a child of body having a descendant with a class.
 */
$('section:not(body > [class], body > :has([class]))')

aperçu jsFiddle

N'oubliez pas que passer une liste de sélecteurs séparés par des virgules à :not() signifie filtrer les éléments qui ne correspondent à aucun des sélecteurs répertoriés.

Maintenant, la pseudo-classe :not() dans les sélecteurs de niveau , en revanche, est très limitée par elle-même. Vous ne pouvez passer que un seul sélecteur simple comme argument à :not(). Cela signifie que vous ne pouvez passer que l'un de ces éléments à la fois:

  • Sélecteur universel (*), Avec éventuellement namespace
  • Sélecteur de type (a, div, span, ul, li, etc.), éventuellement avec un espace de noms
  • Sélecteur d'attributs ([att], [att=val], Etc.), éventuellement avec un espace de noms
  • Sélecteur de classe (.class)
  • Sélecteur d'ID (#id)
  • Pseudo-classe (:pseudo-class)

Voici donc les différences entre sélecteur :not() de jQuery et sélecteur :not() de la norme actuelle :

  1. Tout d'abord, pour répondre directement à la question: vous ne pouvez pas passer une liste de sélection séparée par des virgules. 1 Par exemple, alors que le sélecteur donné fonctionne dans jQuery comme démontré dans le violon, il n'est pas valide en CSS:

    /* If it's not in the Α, Β or Γ sectors, it's unassigned */
    #sectors > div:not(.alpha, .beta, .gamma)
    

    Existe-t-il une solution de contournement CSS pure pour cela ou devrai-je compter sur un script?

    Heureusement, dans ce cas, il y en a. Vous devez simplement enchaîner plusieurs sélecteurs :not(), l'un après l'autre, afin de le rendre valide CSS:

    #sectors > div:not(.alpha):not(.beta):not(.gamma)
    

    Cela ne rend pas le sélecteur ça beaucoup plus long, mais l'incohérence et les inconvénients restent évidents.

    Aperçu jsFiddle interactif mis à jour

  2. Vous ne pouvez pas combiner des sélecteurs simples en sélecteurs composés à utiliser avec :not(). Cela fonctionne dans jQuery, mais ce n'est pas du CSS valide:

    /* Do not find divs that have all three classes together */
    #foo > div:not(.foo.bar.baz)
    

    Vous devrez le diviser en plusieurs négations (pas seulement les enchaîner!) Pour le rendre valide en CSS:

    #foo > div:not(.foo), #foo > div:not(.bar), #foo > div:not(.baz)
    

    Comme vous pouvez le voir, cela est encore plus gênant que le point 1.

  3. Vous ne pouvez pas utiliser de combinateurs. Cela fonctionne dans jQuery, mais pas CSS:

    /* 
     * Grab everything that is neither #foo itself nor within #foo.
     * Notice the descendant combinator (the space) between #foo and *.
     */
    :not(#foo, #foo *)
    

    Il s'agit d'un cas particulièrement désagréable, principalement parce qu'il n'a pas de solution de contournement appropriée. Il existe des solutions lâches ( 1 et 2 ), mais elles dépendent presque toujours de la structure HTML et sont donc très limitées en termes d'utilité. .

  4. Dans un navigateur qui implémente querySelectorAll() et le sélecteur :not(), l'utilisation de :not() dans une chaîne de sélecteur d'une manière qui en fait un sélecteur CSS valide entraînera la méthode retourner les résultats directement, au lieu de retomber sur Sizzle (le moteur de sélection de jQuery qui implémente l'extension :not()). Si vous êtes un adepte de la performance, c'est un bonus vraiment minuscule que vous saliverez certainement.

La bonne nouvelle est que les sélecteurs 4 améliorent le sélecteur :not() pour permettre une liste de sélecteurs complexes séparés par des virgules. Un sélecteur complexe est simplement soit un seul sélecteur simple ou composé, soit une chaîne entière de sélecteurs composés séparés par des combinateurs. Bref, tout ce que vous voyez ci-dessus.

Cela signifie que les exemples jQuery ci-dessus deviendront des sélecteurs de niveau 4 valides, ce qui rendra la pseudo-classe beaucoup plus utile lorsque les implémentations CSS commenceront à la prendre en charge dans les années à venir.


1Bien que cet article indique que vous pouvez transmettre une liste de sélecteurs séparés par des virgules à :not() dans Firefox 3, vous n'êtes pas censé pouvoir le faire. Si cela fonctionne dans Firefox 3 comme le prétend cet article, c'est à cause d'un bogue dans Firefox 3 pour lequel je ne trouve plus le ticket, mais cela ne devrait pas fonctionner tant que les futurs navigateurs n'auront pas mis en place les futures normes. En voyant la fréquence à laquelle cet article est cité à ce jour, j'ai laissé un commentaire à cet effet, mais en voyant également l'âge de l'article et la fréquence à laquelle le site est mis à jour, je ne compte vraiment pas sur le retour de l'auteur pour le réparer.

79
BoltClock