<template> + querySelector 使用 :scope 伪类适用于文档,但不适用于 documentFragment

Posted

技术标签:

【中文标题】<template> + querySelector 使用 :scope 伪类适用于文档,但不适用于 documentFragment【英文标题】:<template> + querySelector using :scope pseudo class works with document but not documentFragment 【发布时间】:2015-11-12 12:53:53 【问题描述】:

根据&lt;template&gt; 的内容,我想将其内容包装在一个容器中以便更轻松/一致地遍历。如果内容是顶层的&lt;style&gt;&lt;one-other-element&gt;,我就不用了。否则,其中的任何内容都会被包裹在 &lt;div&gt; 中。

最初我的代码是这样的:

var hasCtnr = template.content.querySelector(':scope > :only-child, :scope > style:first-child + :last-child') != null;

但是,我注意到它不起作用 - 也就是说,hasCtnr 始终是 false。所以,我做了一个reduced test case (jsfiddle)。如您所见,:scope 可用于常规 DOM 元素。但是,它似乎不适用于DocumentFragments。 我知道这项技术是新的/实验性的,但这是一个错误还是我做错了什么?

如果我使用 jQuery,它可以工作......但我的猜测是因为 jQuery 是手动做的。

var hasCtnr = !!$(template.content).children(':only-child, style:first-child + :last-child').length;

顺便说一句,我只关心 Chrome/Electron 支持。

这是 jsfiddle 内联:

var nonTmplResult = document.querySelector('#non-template-result');
var tmplResult = document.querySelector('#template-result');

var grandparent = document.querySelector('#grandparent');
var parent = document.querySelector('#parent');
var child = document.querySelector('#child');

var who = grandparent.querySelector(':scope > div');
if (who === parent) 
    nonTmplResult.innerhtml = 'parent as expected, :scope worked';
 else if (who === child) 
    nonTmplResult.innerHTML = "child (unexpected), :scope didn't work";



var tmpl = document.querySelector('template');
var content = tmpl.content;

var proto = Object.create(HTMLElement.prototype);

var hasCtnr = content.querySelector(':scope > div'); // this and even ':scope div' results in null, 'div' results in DIV
tmplResult.innerHTML += hasCtnr == null ? "null for some reason, :scope didn't work" : hasCtnr.nodeName + ', :scope worked'; // Why is this null..?
tmplResult.innerHTML += '<br/>';

proto.createdCallback = function() 
    var clone = document.importNode(content, true);
    var root = this.createShadowRoot();
    root.appendChild(clone);
    var rootHasCtnr = root.querySelector(':scope > div'); // ':host > div' seems to work but I prefer this check to happen once (above) so createdCallback can be efficient as I'll likely have many custom elements
    tmplResult.innerHTML += rootHasCtnr == null ? "null again, :scope didn't work" : rootHasCtnr.nodeName + ', :scope worked'; // Why is this also null..?
;

document.registerElement('x-foo',  prototype: proto );
#non-template-result 
    background: red;
    color: white;

#template-result 
    background: green;
    color: springgreen;

* /deep/ * 
    margin: 10px;
    padding: 5px;

#grandparent 
    display: none;
<div id="grandparent">
    <div id="parent">
        <div id="child"></div>
    </div>
</div>

<div id="non-template-result">????</div>
<div id="template-result"></div>
<x-foo>
    <p>I should be dark golden rod with khaki text.</p>
</x-foo>

<template>
    <style>
        :host 
            background: blue;
            display: block;
        
        :host > div > p 
            color: white;
        
        ::content > p 
            background: darkgoldenrod;
            color: khaki;
        
    </style>
    <div>
        <p>I should be blue with white text</p>
        <content></content>
    </div>
    
</template>

<a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components#Enabling_Web_Components_in_Firefox">Enabling Web Components in Firefox</a>

【问题讨论】:

【参考方案1】:

在声明中:

var hasCtnr = template.content.querySelector(':scope > :only-child' ) //...

...:scope 伪类表示调用querySelector() 的元素。

但是 DocumentFragment(template.content 的类型)根据定义不是元素(没有根元素,没有容器),它的localName 属性是未定义

这就是为什么这个调用永远不会选择任何东西。

var df = document.createDocumentFragment()
df.appendChild( document.createElement( 'div' ) )
var res = df.querySelector( ':scope div' )  

console.info( 'df.localName is %s', df.localName )
console.info( 'df.querySelector( :scope div ) returns %s', res )

解决方法可能是将内容放入&lt;div&gt;,执行调用,然后根据结果关闭或使用&lt;div&gt;

【讨论】:

【参考方案2】:

这不是错误:

:scope CSS 伪类匹配作为选择器匹配参考点的元素。在 HTML 中,可以使用 &lt;style&gt; 元素的 scoped 属性定义新的参考点。 如果 HTML 页面上没有使用此类属性,则参考点是 &lt;html&gt; 元素。

在某些情况下,选择器可以与一组显式的:scope 元素匹配。 这是一组(可能为空的)元素,为选择器匹配提供了一个参考点,例如由 [DOM] 中的 querySelector() 调用指定的元素,或作用域的父元素[HTML5] 中的&lt;style&gt; 元素。

由于scoped 属性为no longer on any standards track,这仅适用于带有&lt;html&gt; 标记的文档,这将排除其在文档片段中的使用。

根据&lt;template&gt; 的内容,我想将其内容包装在一个容器中以便更轻松/一致地遍历。如果顶层的内容是&lt;style&gt;&lt;one-other-element&gt;,我就不用了。否则,其中的任何内容都会被包裹在 &lt;div&gt; 中。

var bar = document.body.getElementsByTagName("template");
var baz;

var iterator = function(value, index) 
  if(/<style>/.test(value.innerHTML) === false)
    
    value.innerHTML = "\n<div>" + value.innerHTML + "</div>\n";
    
  console.log(value.outerHTML);
  return value;
  ;

bar.map = Array.prototype.map;
baz = bar.map(iterator);
<template>
   <style>A</style> 
   <one-other-element>B</one-other-element> 
</template>

<template>
  <picture></picture>
</template>

参考文献

MDN: CSS :scope pseudo class The Reference Element Pseudo-class: :scope Sizzle Issue #284: Selectors on document fragment HTMLTemplateElement.webidl

【讨论】:

drafts.csswg.org/selectors-4/#scoping "一些主机应用程序可能选择将选择器范围限定为文档的特定子树或片段。范围子树的根称为范围根,它可以是真正的元素 (范围元素)或虚拟元素(例如 DocumentFragment)。”来自 MDN 的引用似乎基于未提及 DocumentFragments 的较旧版本的 selectors-4。 此外,我认为这句话无论如何都无关紧要。 :scope 伪适用于 HTML 中的作用域 @BoltClock 在 MDN 文档中,除了已弃用的作用域属性之外别无选择来确定上下文。目前是如何通过源订单或其他 API 完成的?

以上是关于<template> + querySelector 使用 :scope 伪类适用于文档,但不适用于 documentFragment的主要内容,如果未能解决你的问题,请参考以下文章

搜索框实时搜索效果

vue路由传参(学习心得)

Spring JDBC Template SQL-Query 按日期过滤

题解 CF375D Tree and Queries

模板——线段树维护最大子段和 SP1716 GSS3 - Can you answer these queries III

vue 修改当前网页标题