À quoi sert la pseudo-classe :scope CSS ?

:scope est défini comme suit dans les sélecteurs CSS 4:

Pseudo-classe représentant tout élément de l'ensemble d'éléments de référence contextuels. Il s'agit d'un ensemble d'éléments (potentiellement vide) explicitement spécifié, tel que celui spécifié par querySelector(), ou l'élément parent d'un élément <style scoped>, qui permet de définir la portée d'un sélecteur afin qu'il ne corresponde qu'à une sous-arborescence.

Voici un exemple d'utilisation dans un <style scoped> (en savoir plus):

<style>
    li {
    color: blue;
    }
</style>

<ul>
    <style scoped>
    li {
        color: red;
    }
    :scope {
        border: 1px solid red;
    }
    </style>
    <li>abc</li>
    <li>def</li>
    <li>efg</li>
</ul>

<ul>
    <li>hij</li>
    <li>klm</li>
    <li>nop</li>
</ul>

Cela colore les éléments li dans le premier ul rouge et, en raison de la règle :scope, place une bordure autour de ul. En effet, dans le contexte de ce <style scoped>, ul correspond à :scope. C'est le contexte local. Si nous ajoutions une règle :scope dans la règle <style> externe, elle correspondrait à l'ensemble du document. Cela équivaut essentiellement à :root.

Éléments contextuels

Vous connaissez probablement la version Element de querySelector() et querySelectorAll(). Au lieu d'interroger l'ensemble du document, vous pouvez limiter l'ensemble de résultats à un élément contextuel:

<ul>
    <li id="scope"><a>abc</a></li>
    <li>def</li>
    <li><a>efg</a></li>
</ul>
<script>
    document.querySelectorAll('ul a').length; // 2

    var scope = document.querySelector('#scope');
    scope.querySelectorAll('a').length; // 1
</script>

Lorsque ces nœuds sont appelés, le navigateur renvoie une NodeList qui est filtrée pour n'inclure que l'ensemble de nœuds qui a.) correspondent au sélecteur et b.) qui sont également descendants de l'élément de contexte. Ainsi, dans le deuxième exemple, le navigateur trouve tous les éléments a, puis filtre ceux qui ne se trouvent pas dans l'élément scope. Cela fonctionne, mais peut entraîner un comportement étrange si vous n'y faites pas attention. Lisez la suite.

Cas de non-respect de la requête querySélecteur

Il y a un point vraiment important dans les spécifications des sélecteurs que les utilisateurs oublient souvent. Même lorsque querySelector[All]() est appelé sur un élément, les sélecteurs effectuent l'évaluation dans le contexte de l'ensemble du document. Cela signifie que des choses imprévues peuvent se produire:

    scope.querySelectorAll('ul a').length); // 1
    scope.querySelectorAll('body ul a').length); // 1

Sérieusement ?! Dans le premier exemple, ul est mon élément, mais je peux toujours l'utiliser et établir une correspondance avec les nœuds. Dans la seconde, body n'est même pas un descendant de mon élément, mais "body ul a" correspond toujours. Ces deux aspects sont déroutants et ne correspondent pas à vos attentes.

Il peut être intéressant de comparer la bibliothèque jQuery, qui adopte l'approche appropriée et répond à vos attentes:

    $(scope).find('ul a').length // 0
    $(scope).find('body ul a').length // 0

... saisissez :scope pour résoudre ces problèmes sémantiques.

Corriger le sélecteur de requête avec :scope

WebKit a récemment permis d'utiliser la pseudo-classe :scope dans querySelector[All](). Vous pouvez la tester dans Chrome Canary 27.

Vous pouvez l'utiliser pour limiter les sélecteurs à un élément de contexte. Voyons un exemple. Dans l'exemple suivant, :scope permet de définir la portée du sélecteur sur la sous-arborescence de l'élément de champ d'application. C'est exact, j'ai dit la portée trois fois !

    scope.querySelectorAll(':scope ul a').length); // 0
    scope.querySelectorAll(':scope body ul a').length); // 0
    scope.querySelectorAll(':scope a').length); // 1

L'utilisation de :scope rend la sémantique des méthodes querySelector() un peu plus prévisible et adaptée à ce que font déjà d'autres méthodes comme jQuery.

Gain de performance ?

Pas encore :(

J'aimerais savoir si l'utilisation de :scope dans qS/qSA permet d'améliorer les performances. Donc... comme un bon ingénieur, j'ai organisé un test. Ma justification: une surface réduite pour la mise en correspondance du sélecteur permet au navigateur d'accélérer les recherches.

Dans mon expérience, WebKit prend actuellement environ 1,5 à 2 fois plus de temps que de ne pas utiliser :scope. Dommage ! Lorsque crbug.com/222028 sera corrigé, vous devriez en théorie améliorer les performances plutôt que de l'utiliser.