A cosa serve la pseudo-classe CSS :scope?

:scope è definito in CSS Selectors 4 come:

Una pseudo-classe che rappresenta qualsiasi elemento nell'insieme di elementi di riferimento contestuale. Si tratta di un insieme (potenzialmente vuoto) di elementi specificati esplicitamente, come quello specificato da querySelector() o dall'elemento principale di un elemento <style scoped>, che viene utilizzato per "definire l'ambito" di un selettore in modo che corrisponda solo all'interno di un sottoalbero.

Un esempio di utilizzo è all'interno di un <style scoped> (scopri di più):

<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>

In questo modo, gli elementi li nel primo ul vengono colorati di rosso e, a causa della regola :scope, viene applicato un bordo intorno al ul. Questo perché nel contesto di questo <style scoped>, il valore di ul corrisponde a :scope. È il contesto locale. Se aggiungessimo una regola :scope nel campo <style> esterno, corrisponderà all'intero documento. Essenzialmente, è equivalente a :root.

Elementi contestuali

Probabilmente conosci la versione Element di querySelector() e querySelectorAll(). Anziché eseguire query sull'intero documento, puoi limitare il set di risultati a un elemento contestuale:

<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>

Quando vengono chiamati, il browser restituisce un NodeList filtrato in modo da includere solo l'insieme di nodi che a.) corrispondono al selettore e b.) sono anche discendenti dell'elemento di contesto. Pertanto, nel secondo esempio, il browser trova tutti gli elementi a, quindi filtra quelli non presenti nell'elemento scope. Funziona, ma se non fai attenzione può portare a comportamenti bizzarri. Continua a leggere.

Quando querySelector non va a buon fine

Nella specifica dei selettori c'è un punto molto importante che spesso viene trascurato. Anche quando querySelector[All]() viene invocato su un elemento, i selettori vengono comunque valutati nel contesto dell'intero documento. Ciò significa che possono verificarsi eventi imprevisti:

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

WTF! Nel primo esempio, ul è il mio elemento, ma posso ancora utilizzarlo e corrisponde ai nodi. Nel secondo, body non è nemmeno un discendente del mio elemento, ma "body ul a" corrisponde comunque. Entrambi sono confusi e non sono ciò che ti aspetti.

Vale la pena fare un confronto con jQuery, che adotta l'approccio giusto e fa ciò che ti aspetti:

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

…inserisci :scope per risolvere questi problemi semantici.

Correzione di querySelector con :scope

WebKit ha recentemente implementato il supporto per l'utilizzo dell'pseudo-classe :scope in querySelector[All](). Puoi testarlo in Chrome Canary 27.

Puoi utilizzare questa opzione per limitare i selettori a un elemento di contesto. Vediamo un esempio. Di seguito, :scope viene utilizzato per "limitare" il selettore al sottoalbero dell'elemento ambito. Esatto, ho detto ambito tre volte.

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

L'utilizzo di :scope rende la semantica dei metodi querySelector() un po' più prevedibile e in linea con ciò che fanno già altri, come jQuery.

Conquista le prestazioni?

Non ancora :(

Mi chiedevo se l'utilizzo di :scope in qS/qSA migliorasse le prestazioni. Così, da buon ingegnere, ho creato un test. La mia motivazione: meno superficie per la corrispondenza dei selettori del browser significa ricerche più rapide.

Nel mio esperimento, WebKit attualmente impiega circa 1,5-2 volte più tempo rispetto all'utilizzo di :scope. Drat! Una volta risolto il problema con crbug.com/222028, il suo utilizzo dovrebbe in teoria incrementare le prestazioni rispetto al suo utilizzo.