影子節點ShadowDOM

shadow DOM是什麼

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

  上面這是最簡單的視屏標籤,裏面有默認的音量等按鍵。在源代碼中根本沒有一點痕跡。那這些節點是從哪裏來的?
  這就是shadow DOM,視屏的控件在瀏覽器中真實面目以下:
javascript

shadow DOM瀏覽器中體現
shadow DOM瀏覽器中體現

  發現#shadow-root是灰色的,這是瀏覽器爲了代表在shadow DOM中,表明頁面其餘的部分的內容不會對內部產生影響(可用特定方式穿透,後文會說到)css

內容具體指css選擇器和javascript代碼html

  簡而言之,Shadow DOM 是一個 HTML 的新規範,其容許開發者封裝HTML組件(相似vue組件,將html,css,js獨立部分提取)。vue

爲何要使用shadow DOM

  Bootstrap的名字你必定不陌生,代碼通常以下:java

<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>複製代碼

  很是的簡單好用,可是這些東西你並無深刻了解,每每結構變複雜以後,都是一堆模板,修改是一個很難的問題,牽一髮而動全身。
  這種狀況下shadow DOM的優點十分巨大,你能夠這麼寫模板bootstrap

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

  固然想實現這麼寫還須要一些js,css配合才行瀏覽器

如何使用

先運行一個例子bash

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

運行結果
運行結果

  首先咱們指定一個 宿主節點shadow host)而後建立影子根( shadow root)爲它添加一個文本節點,結果宿主中的內容未被渲染。

如何渲染宿主節點中的內容

  只渲染影子根中的內容基本沒有實用的地方,但能自由的渲染宿主節點中的內容的話就可讓頁面展示更靈活。咱們須要content標籤app

<div class="pokemon">胖丁</div>
<template class="pokemon-template">
  <h1>一隻野生的<content></content>出現了!</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>標籤建立了一個 插入點 .pokemon裏面的文本投影出來,多個內容匹配時能夠實用 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屬性相似選擇器的形式渲染宿主節點中匹配元素的投影。這種形式不但能夠改變DOM流的順序也可讓佈局變得靈活。
  在模板的最後 <content select=""></content>是一種貪心匹配,把宿主節點中全部未被匹配的內容所有投影。須要注意的是把貪心匹配放在最前面會把全部的節點投影而且以後的select不會再獲取到被其投影的內容。
  如下都是等效的:

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

樣式渲染與封裝

  先看一個簡單的例子dom

<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>複製代碼


  在影子節點中存在邊界使 shadow DOM樣式和正常DOM流中的樣式不相互干擾。這是一種 做用域化的體現,不用再擔憂樣式的相互衝突。

(:host)選擇器

  :host是僞類選擇器選擇宿主節點,咱們能夠擴展一下上面的例子

<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標籤字體大小是12px = 影子樣式的優先級不如頁面樣式
  • :host選擇器中可使用任意合法選擇器,*應用於全部
  • 經過掛載不一樣宿主渲染出不一樣的內容,能夠實現主題化
      上面的主題化並不徹底,只根據掛載元素進行選擇也就是說.parent > .child,可是咱們還能經過:host-context實現.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>複製代碼


  上面的效果就很是不錯了,能夠進行動態組件構建

ps: 僞類,僞元素選擇器也能夠直接使用,效果和正常節點中一致

(::content)選擇器

  在使用 shadow DOM 的時候應該確保內容和表現的分離,也就是說文本應該來自頁面而不是埋在 shadow DOM 的模板裏。因此咱們須要在模板中對分佈式節點進行渲染。

<div class="widget">
    <button>分佈節點碉堡啦!</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)

  咱們能夠在掛載節點中使用::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的區別

  1. 數據並無塊級化,仍掛載在window
  • 事件重定向(原來綁定在 shadow DOM 節點中的事件被重定向了,因此他們看起來像綁定在宿主節點上同樣)
    <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>複製代碼

  能夠看到在影子節點的事件被宿主節點代理。

事件阻塞

  在監聽如下事件時會被阻塞在影子節點的根:

  • 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>複製代碼

  事件影子節點的根上被阻止,沒法冒泡到ducoment,因此沒法監聽。

分佈節點

  分佈節點指以前經過<content>標籤將宿主節點的內容投影,分佈節點不會發生上面的阻塞狀況,由於這個只是一個投影實際的內容仍是掛載在宿主節點上。

相關文章
相關標籤/搜索