影子節(jié)點 shadowDOM

shadow DOM是什么

<video controls autoplay name="media">
  <source id="mp4" src="trailer.mp4" type="video/mp4">
</video>

上面這是最簡單的視屏標(biāo)簽,里面有默認的音量等按鍵。在源代碼中根本沒有一點痕跡。那這些節(jié)點是從哪里來的?
??這就是shadow DOM,視屏的控件在瀏覽器中真實面目如下:


shadow DOM瀏覽器中體現(xiàn)

發(fā)現(xiàn)#shadow-root是灰色的,這是瀏覽器為了表明在shadow DOM中,代表頁面其他的部分的內(nèi)容不會對內(nèi)部產(chǎn)生影響(可用特定方式穿透,后文會說到)

內(nèi)容具體指css選擇器和javascript代碼

簡而言之,Shadow DOM 是一個 HTML 的新規(guī)范,其允許開發(fā)者封裝HTML組件(類似vue組件,將html,css,js獨立部分提?。?。

為什么要使用shadow DOM

Bootstrap的名字你一定不陌生,代碼一般如下:

<ul class="media-list">
  <li class="media">
    <div class="media-left">
       <a href="#">
        ![](...)
       </a>
    </div>
    <div class="media-body">
       <h4 class="media-heading">Media heading</h4>
    </div>
  </li>
</ul>

非常的簡單好用,但是這些東西你并沒有深入了解,往往結(jié)構(gòu)變復(fù)雜之后,都是一堆模板,修改是一個很難的問題,牽一發(fā)而動全身。
??這種情況下shadow DOM的優(yōu)勢十分巨大,你可以這么寫模板

// 我是一個簡潔的模板
<bootstrap-media-list>
  <a href="#">
    ![](...)
  </a>
  <h4 class="media-heading">Media heading</h4>
</bootstrap-media-list> 

當(dāng)然想實現(xiàn)這么寫還需要一些js,css配合才行

如何使用

先運行一個例子

<div class="widget">Hello, world!</div>
<script>
  var host = document.querySelector('.widget');
  var root = host.createShadowRoot();
  root.textContent = '我在你的 div 里!';
</script> 

運行結(jié)果

??首先我們指定一個宿主節(jié)點shadow host)然后創(chuàng)建影子根(shadow root)為它添加一個文本節(jié)點,結(jié)果宿主中的內(nèi)容未被渲染。

如何渲染宿主節(jié)點中的內(nèi)容

只渲染影子根中的內(nèi)容基本沒有實用的地方,但能自由的渲染宿主節(jié)點中的內(nèi)容的話就可以讓頁面展現(xiàn)更靈活。我們需要content標(biāo)簽

<div class="pokemon">胖丁</div>
<template class="pokemon-template">
  <h1>一只野生的<content></content>出現(xiàn)了!</h1>
</template>
<script>
  var host = document.querySelector('.pokemon');
  var root = host.createShadowRoot();
  var template = document.querySelector('.pokemon-template');
  root.appendChild(document.importNode(template.content, true)); 
</script>


??<content>標(biāo)簽創(chuàng)建了一個**插入點 **將.pokemon里面的文本投影出來,多個內(nèi)容匹配時可以實用select屬性指定

<div class="host">
    <p>大慈大悲,由諸葛亮進化而來。</p>
    <span class="name">觀音姐姐獸</span>
</div>
<template class="root-template">
    <dl>
      <dt>名字</dt>
      <dd><content select=".name"></content></dd>
    </dl>
    <p><content select=""></content></p>
</template>
<script>
    var host = document.querySelector('.host');
    var root = host.createShadowRoot();
    var template = document.querySelector('.root-template');
    root.appendChild(template.content);
</script>


??可以使用select屬性類似選擇器的形式渲染宿主節(jié)點中匹配元素的投影。這種形式不但可以改變DOM流的順序也可以讓布局變得靈活。
??在模板的最后<content select=""></content>是一種貪心匹配,把宿主節(jié)點中所有未被匹配的內(nèi)容全部投影。需要注意的是把貪心匹配放在最前面會把所有的節(jié)點投影并且之后的select不會再獲取到被其投影的內(nèi)容。
??以下都是等效的:

  • <content></content>
  • <conent select=""></conent>
  • <content select="*"></content>

樣式渲染與封裝

先看一個簡單的例子

<style>
    button {
        font-size: 18px;
        font-family: '華文行楷';
    }
</style>
<button>普通按鈕</button>
<div></div>
<script>
    var host = document.querySelector('div');
    var root = host.createShadowRoot();
    root.innerHTML = 
      '<style>button { font-size: 24px; color: blue; } </style>'+
      '<button>影子按鈕</button>';
</script>


??在影子節(jié)點中存在邊界使shadow DOM樣式和正常DOM流中的樣式不相互干擾。這是一種作用域化的體現(xiàn),不用再擔(dān)心樣式的相互沖突。

(:host)選擇器

:host是偽類選擇器選擇宿主節(jié)點,我們可以擴展一下上面的例子

<style>
    p {
        font-size: 12px;
    }
</style>
<p>我的文本</p>
<button>我的按鈕</button>
<template class="shadow-template">
    <style>
        :host(p) {
            color: green;
        }
        :host(button) {
            color: red;
        }
        :host(*) {
            font-size: 24px;
        }
    </style>
    <content select=""></content>
</template>
<script>
    var root1 = document.querySelector('p').createShadowRoot();
    var root2 = document.querySelector('button').createShadowRoot();

    var template = document.querySelector('.shadow-template');

    root1.appendChild(document.importNode(template.content, true));
    root2.appendChild(document.importNode(template.content, true));
</script>

??這個例子有幾個點:

  • p標(biāo)簽字體大小是12px = 影子樣式的優(yōu)先級不如頁面樣式
  • :host選擇器中可以使用任意合法選擇器,*應(yīng)用于所有
  • 通過掛載不同宿主渲染出不同的內(nèi)容,可以實現(xiàn)主題化
    ??上面的主題化并不完全,只根據(jù)掛載元素進行選擇也就是說.parent > .child,但是我們還能通過:host-context實現(xiàn).parent < .child如下
<div class="serious">
    <p class="serious-widget">
        serious-widget
    </p>
</div>
<div class="playful">
    <p class="playful-widget">
        playful-widget
    </p>
</div>
<template class="widget-template">
    <style>
        :host-context(.serious) {
            width: 250px;
            height: 50px;
            background: tomato;
        }
        :host-context(.playful) {
            width: 250px;
            height: 50px;
            background: deepskyblue;
        }
    </style>
    <content></content>
</template>
<script>
var root1 = document.querySelector('.serious-widget').createShadowRoot();
var root2 = document.querySelector('.playful-widget').createShadowRoot();
var template = document.querySelector('.widget-template');
root1.appendChild(document.importNode(template.content, true));
root2.appendChild(document.importNode(template.content, true));
</script>

上面的效果就非常不錯了,可以進行動態(tài)組件構(gòu)建

ps: 偽類,偽元素選擇器也可以直接使用,效果和正常節(jié)點中一致

(::content)選擇器

在使用 shadow DOM 的時候應(yīng)該確保內(nèi)容和表現(xiàn)的分離,也就是說文本應(yīng)該來自頁面而不是埋在 shadow DOM 的模板里。所以我們需要在模板中對分布式節(jié)點進行渲染。

<div class="widget">
    <button>分布節(jié)點碉堡啦!</button>
</div>
<template class="widget-template">
    <style>
        ::content > button {
            color: white;
            background: tomato;
            border-radius: 10px;
            border: none;
            padding: 10px;
        }
    </style>
    <content select=""></content>
</template>
<script>
var root = document.querySelector('.widget').createShadowRoot();
var template = document.querySelector('.widget-template');
root.appendChild(document.importNode(template.content, true));
</script>

打破作用域(::shadow)

我們可以在掛載節(jié)點中使用::shadow,比如

<style>
    .sign-up::shadow #username{
        font-size: 20px;
        border: 1px solid red;
    }
</style>
<div class="sign-up"></div>
<template class="sign-up-template">
    <style>
        #username{
            font-size: 12px;
        }
    </style>
    <div>
        <input type="text" id="username" placeholder="用戶名">
    </div>
</template>
<script>
var root = document.querySelector('.sign-up').createShadowRoot();
var template = document.querySelector('.sign-up-template');
root.appendChild(document.importNode(template.content, true));
</script>

不過缺點是只能穿透一層,但我們還有一個神器!

多層穿透(/deep/)

<style>
    #foo /deep/ button {
        color: red;
    }
</style>
<div id="foo"></div>
<template>
    <div id="bar"></div>
</template>
<script>
    var root1 = document.querySelector('#foo').createShadowRoot();
    var template = document.querySelector('template');
    root1.appendChild(document.importNode(template.content, true));
    var root2 = root1.querySelector('#bar').createShadowRoot();
    root2.innerHTML = '<button>點我點我</button>';
</script>

javascript的區(qū)別

  1. 數(shù)據(jù)并沒有塊級化,仍掛載在window
  • 事件重定向(原來綁定在 shadow DOM 節(jié)點中的事件被重定向了,所以他們看起來像綁定在宿主節(jié)點上一樣)
<input id="normal-text" type="text" value="I'm normal text">
<div id="host"></div>
<template>
    <input id="shadow-text" type="text" value="I'm shadow text">
</template>
<script>
    var root = document.querySelector('#host').createShadowRoot();
    var template = document.querySelector('template');
    root.appendChild(document.importNode(template.content, true));
    document.addEventListener('click', function(e) {
      console.log(e.target.id + ' clicked!');
    });
</script>

可以看到在影子節(jié)點的事件被宿主節(jié)點代理。

事件阻塞

在監(jiān)聽以下事件時會被阻塞在影子節(jié)點的根:

  • aborterror
  • select
  • change
  • load
  • reset
  • reset
  • resize
  • scroll
  • selectstar
<input id="normal-text" type="text" value="I'm normal text">
<div id="host">
    <input id="distributed-text" type="text" value="I'm distributed text">
</div>
<template>
    <div><content></content></div>
    <div>
        <input id="shadow-text" type="text" value="I'm shadow text">
    </div>
</template>
<script>
    var root = document.querySelector('#host').createShadowRoot();
    var template = document.querySelector('template');
    root.appendChild(document.importNode(template.content, true));
    document.addEventListener('select', function(e) {
      console.log(e.target.id + ' text selected!');
    });
</script>

事件影子節(jié)點的根上被阻止,無法冒泡到ducoment,所以無法監(jiān)聽。

分布節(jié)點

分布節(jié)點指之前通過<content>標(biāo)簽將宿主節(jié)點的內(nèi)容投影,分布節(jié)點不會發(fā)生上面的阻塞情況,因為這個只是一個投影實際的內(nèi)容還是掛載在宿主節(jié)點上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容