:scope
é definido em Seletores de CSS 4 como:
Uma pseudoclasse que representa qualquer elemento que esteja no conjunto de elementos de referência contextual. Esse é um conjunto de elementos explicitamente especificado (possivelmente vazio), como o especificado por
querySelector()
ou o elemento pai de um elemento<style scoped>
, que é usado para definir o escopo de um seletor para que ele corresponda apenas em uma subárvore.
Um exemplo de uso é em um <style scoped>
(mais informações):
<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>
Isso colore os elementos li
no primeiro ul
de vermelho e, devido à regra :scope
, coloca uma borda ao redor do ul
. Isso ocorre porque, no contexto desse <style scoped>
, o ul
corresponde a :scope
. É o contexto local. Se adicionarmos uma regra :scope
na <style>
externa, ela vai corresponder a todo o documento. Basicamente, equivalente a :root
.
Elementos contextuais
Você provavelmente conhece a versão Element
de querySelector()
e querySelectorAll()
. Em vez de consultar o documento inteiro, você pode restringir o conjunto de resultados a um elemento contextual:
<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 eles são chamados, o navegador retorna um NodeList
filtrado para incluir apenas o conjunto de nós que a.) correspondem ao seletor e b.) que também são descendentes do elemento de contexto. No segundo exemplo, o navegador encontra todos os elementos a
e filtra aqueles que não estão no elemento scope
. Isso funciona, mas pode levar a um comportamento estranho se você não tomar cuidado. Continue lendo.
Quando o querySelector dá errado
Há um ponto muito importante na especificação de seletores que as pessoas costumam ignorar. Mesmo quando querySelector[All]()
é invocado em um elemento, os seletores ainda são avaliados no contexto de todo o documento. Isso significa que coisas inesperadas podem acontecer:
scope.querySelectorAll('ul a').length); // 1
scope.querySelectorAll('body ul a').length); // 1
É alguma piada? No primeiro exemplo, ul
é meu elemento, mas ainda posso usá-lo e corresponde aos nós. Na segunda, body
nem é um descendente do meu elemento, mas "body ul a
" ainda corresponde. Ambas são confusas e não são o que você esperaria.
Vale a pena fazer a comparação com o jQuery, que tem a abordagem certa e faz o que você espera:
$(scope).find('ul a').length // 0
$(scope).find('body ul a').length // 0
…digite :scope
para resolver essas confusões semânticas.
Como corrigir o querySelector com :scope
O WebKit lançou recentemente suporte para o uso da pseudoclasse :scope
em querySelector[All]()
. Você pode testar isso no Chrome Canary 27.
Você pode usá-lo para restringir seletores a um elemento de contexto. Vamos conferir um exemplo. No exemplo abaixo, :scope
é usado para "escopo" do seletor para o subárvore do elemento de escopo. Isso mesmo, eu disse escopo três vezes!
scope.querySelectorAll(':scope ul a').length); // 0
scope.querySelectorAll(':scope body ul a').length); // 0
scope.querySelectorAll(':scope a').length); // 1
O uso de :scope
torna a semântica dos métodos querySelector()
um pouco mais previsível e alinhada com o que outros, como o jQuery, já fazem.
Vitória de desempenho?
Ainda não :(
Eu queria saber se o uso de :scope
em qS/qSA aumenta a performance. Como um bom engenheiro, fiz um teste. Minha justificativa: menos área de superfície para o navegador fazer a correspondência de seletor significa pesquisas mais rápidas.
No meu experimento, o WebKit leva cerca de 1,5 a 2 vezes mais tempo do que não usar :scope
. Droga! Quando o crbug.com/222028 for corrigido, o uso dele teoricamente vai dar um pequeno aumento de desempenho em relação ao não uso.