• HTML5 ShadowDOM & CustomElements


    Web组件由四部分组成

    • Template
    • Shadow DOM (Chrome Opera支持)
    • Custom Elements
    • Packaging

    Shadow DOM 组成

    Shadow DOM可以和一个根节点Shadow root关联, 该Shadow DOM元素称为Shadow Host内容不会被渲染, 而Shadow root内容会被渲染。

    但是,内容不应该放进Shadow DOM内, 以便被搜索引擎 阅读器等访问到, 可重用部件无意义的标记应该放进Shadow DOM

    Shadow DOM从展现中分离细节

    内容在文档内;展现在 Shadow DOM 里。 当需要更新的时候,浏览器会自动保持它们的同步。

    <template>
      <style>
        ……
      </style>
      <div class="outer">
        <div class="boilerplate">
          Hi! My name is
        </div>
        <div class="name">
          <content></content>
        </div>
      </div>
    </template>
    
    <script>
      var shadow = document.querySelector('#nameTag').createShadowRoot();
      var template = document.querySelector('#nameTagTemplate');
      var clone = document.importNode(template.content, true);
      shadow.appendChild(clone);
      document.querySelector("#nameTag").textContent = "Shellie"
    </script>
    <div id="nameTag"></div>

    通过select特性, 可以使用多个元素并控制投射元素

    <!-- Shadow DOM -->
    <div style="background: purple; padding: 1em;">
      <div style="color: red;">
        <content select=".first"></content>
      </div>
      <div style="color: yellow;">
        <content select="div"></content>
      </div>
      <div style="color: blue;">
        <content select=".email"></content>
      </div>
    </div>
    
    <!-- DOM -->
    <div id="nameTag">
      <div class="first">Bob</div>
      <div>B. Love</div>
      <div class="email">bob@</div>
    </div>

    Shadow DOM 样式

    Shadow DOM定义的CSS样式只在Shadow Root下生效, 样式被封装起来

    样式化宿主元素(host element)

    :host样式化Shadow DOM元素, 并且无法影响到Shadow DOM外的元素

    :host(x-bar:host) {
      /* 当宿主是 <x-bar> 元素时生效。 */
    }
    :host(.different:host) {
      /* 当宿主的类 <class="diffent"> 时生效。 */
    }
    :host:hover {
      /* 当鼠标放置到宿主上时生效。 */
      opacity: 1;
    }

    ^(Hat) 和 ^^(Cat)选择器

    ^ 连接符等价于后代选择器(例如 div p {...}),只不过它能跨越 一个 shadow 边界。
    ^^ 后代选择器能够跨越 任意数量的 shadow 边界。
    querySelector()支持该选择器

    可以通过 shadowdom 样式化原生HTML控件

    video ^ input[type="range"] {
      background: hotpink;
    }

    插入点重置样式

    var root = document.querySelector('div').createShadowRoot();
    root.resetStyleInheritance = false;
    {
      reset-style-inheritance: true;
    }

    在插入点, 选择是否继承上级样式(只影响可继承的样式)

    ::content 伪元素 穿过插入点来指定样式

    <div>
      <h3>Light DOM</h3>
      <section>
        <div>I'm not underlined</div>
        <p>I'm underlined in Shadow DOM!</p>
      </section>
    </div>
    
    <script>
    var div = document.querySelector('div');
    var root = div.createShadowRoot();
    root.innerHTML = '
        <style>
          h3 { color: red; }
          content[select="h3"]::content > h3 {
            color: green;
          }
          ::content section p {
            text-decoration: underline;
          }
        </style>
        <h3>Shadow DOM</h3>
        <content select="h3"></content>
        <content select="section"></content>';
    </script>

    对于一个 ShadowRoot 或 <shadow> 插入点:reset-style-inheritance 意味着可继承的 CSS 属性在宿主元素处被设置为 initial,此时这些属性还没有对 shadow 中的内容生效。该位置称为上边界(upper boundary)。

    对于 <content> 插入点:reset-style-inheritance 意味着在宿主的子元素分发到插入点之前,将可继承的 CSS 属性设置为 initial。该位置称为下边界(lower boundary)。

    使用多个shadowdom

    最近添加的树称为 younger tree。之前添加的树称为 older tree。

    添加进宿主元素中的 shadow 树按照它们的添加顺序而堆叠起来,从最先加入的 shadow 树开始。最终渲染的是最后加入的 shadow 树。

    如果一个 shadow 树中存在多个 <shadow> 插入点,那么仅第一个被确认,其余的被忽略。

    "Shadow 插入点" (<shadow>) 作为占位符可以插入 ShadowDOM
    普通插入点 (<content>) 作为占位符可以插入 普通DOM元素

    如果一个元素托管着 Shadow DOM,你可以使用 .shadowRoot 来访问它的 youngest shadow root

    如果不想别人乱动你的 shadow,那就将 .shadowRoot 重定义为 null:

    Object.defineProperty(host, 'shadowRoot', {
      get: function() { return null; },
      set: function(value) { }
    });

    JS中构建 shadowdom

    可以使用 HTMLContentElement 和 HTMLShadowElement 接口。
    使用插入点从宿主元素中选择并"分发"到 shadow 树

    无法遍历 <content> 中的 DOM。
    .getDistributedNodes() 允许我们查询一个插入点的分布式节点:

    <div id="example4">
      <h2>Eric</h2>
      <h2>Bidelman</h2>
      <div>Digital Jedi</div>
      <h4>footer text</h4>
    </div>
    
    <template id="sdom">
      <header>
        <content select="h2"></content>
      </header>
      <section>
        <content select="div"></content>
      </section>
      <footer>
        <content select="h4:first-of-type"></content>
      </footer>
    </template>
    
    <script>
    var container = document.querySelector('#example4');
    
    var root = container.createShadowRoot();
    
    var t = document.querySelector('#sdom');
    var clone = document.importNode(t.content, true);
    root.appendChild(clone);
    
    var html = [];
    [].forEach.call(root.querySelectorAll('content'), function(el) {
      html.push(el.outerHTML + ': ');
      var nodes = el.getDistributedNodes();
      [].forEach.call(nodes, function(node) {
        html.push(node.outerHTML);
      });
      html.push('
    ');
    });
    </script>

    可以在分布式节点上调用它的 .getDestinationInsertionPoints() 来查看它被分发进了哪个插入点中

    <div id="host">
      <h2>Light DOM</h2>
    </div>
    
    <script>
      var container = document.querySelector('div');
    
      var root1 = container.createShadowRoot();
      var root2 = container.createShadowRoot();
      root1.innerHTML = '<content select="h2"></content>';
      root2.innerHTML = '<shadow></shadow>';
    
      var h2 = document.querySelector('#host h2');
      var insertionPoints = h2.getDestinationInsertionPoints();
      [].forEach.call(insertionPoints, function(contentEl) {
        console.log(contentEl);
      });
    </script>

    Shadow DOM 可视化渲染工具:
    Shadow DOM Visualizer

    shadowdom 事件模型

    事件会被重定向,使它看起来是从宿主元素上发出,而并非是 Shadow DOM 的内部元素。(event.path 来查看调整后的事件路径。)

    以下事件永远无法越过 shadow 边界:

    • abort
    • error
    • select
    • change
    • load
    • reset
    • resize
    • scroll
    • selectstart

    自定义元素

    使用场景

    • 定义新的 HTML/DOM 元素
    • 基于其他元素创建扩展元素
    • 给一个标签绑定一组自定义功能
    • 扩展已有 DOM 元素的 API

    注册新元素

    document.registerElement() 可以创建一个自定义元素

    • 第一个参数是元素的标签名。这个标签名必须包括一个连字符(-)。
    • 第二个参数是一个(可选的)对象,用于描述该元素的 prototype。在这里可以为元素添加自定义功能(例如:公开属性和方法)。
    var XFoo = document.registerElement('x-foo', {
      prototype: Object.create(HTMLElement.prototype)
    });
    // 非全局创建新元素, 可以放置到自己的命名空间内
    var myapp = {}; 
    myapp.XFoo = document.registerElement('x-foo');
    // 扩展原生元素 要创建扩展自元素 B 的元素 A,元素 A 必须继承元素 B 的 prototype。
    var MegaButton = document.registerElement('mega-button', {
      prototype: Object.create(HTMLButtonElement.prototype)
    });
    // 以下方法为重载版本
    var megaButton = document.createElement('button', 'mega-button');
    // <button is="mega-button">

    添加JS属性和方法

    var XFooProto = Object.create(HTMLElement.prototype);
    
    // 1. 为 x-foo 创建 foo() 方法
    XFooProto.foo = function() {
      alert('foo() called');
    };
    
    // 2. 定义一个只读的“bar”属性
    Object.defineProperty(XFooProto, "bar", {value: 5});
    
    // 3. 注册 x-foo 的定义
    var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
    
    // 4. 创建一个 x-foo 实例
    var xfoo = document.createElement('x-foo');
    
    // 5. 插入页面
    document.body.appendChild(xfoo);
    
    /* 更简洁的方式 */
    var XFoo = document.registerElement('x-foo', {
      prototype: Object.create(HTMLElement.prototype, {
        bar: {
          get: function() { return 5; }
        },
        foo: {
          value: function() {
            alert('foo() called');
          }
        }
      })
    });

    生命周期回调方法

    回调名称调用时间点
    createdCallback 创建元素实例
    attachedCallback 向文档插入实例
    detachedCallback 从文档中移除实例
    attributeChangedCallback(attrName, oldVal, newVal) 添加,移除,或修改一个属性
    var proto = Object.create(HTMLElement.prototype);
    
    proto.createdCallback = function() {
      this.addEventListener('click', function(e) {
        alert('Thanks!');
      });
      this.innerHTML = "<b>I'm an x-foo!</b>";
    };
    proto.attachedCallback = function() {...};
    
    var XFoo = document.registerElement('x-foo', {prototype: proto});

    用 Shadow DOM 封装内部实现

    • 一种隐藏内部实现的方法,从而将用户与血淋淋的实现细节隔离开。
    • 简单有效的样式隔离。

    从 Shadow DOM 创建元素,跟创建一个渲染基础标记的元素非常类似,区别在于 createdCallback() 回调:

    var XFooProto = Object.create(HTMLElement.prototype);
    
    XFooProto.createdCallback = function() {
      // 1. 为元素附加一个 shadow root。
      var shadow = this.createShadowRoot();
    
      // 2. 填入标记。
      shadow.innerHTML = "<b>I'm in the element's Shadow DOM!</b>";
    };
    
    var XFoo = document.registerElement('x-foo-shadowdom', {prototype: XFooProto});

    从模板创建元素

    <template id="sdtemplate">
      <style>
        p { color: orange; }
      </style>
      <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
    </template>
    
    <script>
    var proto = Object.create(HTMLElement.prototype, {
      createdCallback: {
        value: function() {
          var t = document.querySelector('#sdtemplate');
          var clone = document.importNode(t.content, true);
          this.createShadowRoot().appendChild(clone);
        }
      }
    });
    document.registerElement('x-foo-from-template', {prototype: proto});
    </script>

    为自定义元素增加样式

    <style>
      app-panel {
        display: flex;
      }
      [is="x-item"] {
        transition: opacity 400ms ease-in-out;
        opacity: 0.3;
        flex: 1;
        text-align: center;
        border-radius: 50%;
      }
      [is="x-item"]:hover {
        opacity: 1.0;
        background: rgb(255, 0, 255);
        color: white;
      }
      app-panel > [is="x-item"] {
        padding: 5px;
        list-style: none;
        margin: 0 7px;
      }
    </style>
    
    <app-panel>
      <li is="x-item">Do</li>
      <li is="x-item">Re</li>
      <li is="x-item">Mi</li>
    </app-panel>

    为使用 Shadow DOM 的元素增加样式

    使用 :unresolved 伪类避免无样式内容闪烁(FOUC)

    使用 :unresolved 伪类避免无样式内容闪烁(FOUC)

    注册后渐显的 <x-foo> 标签:

    <style>
      x-foo {
        opacity: 1;
        transition: opacity 300ms;
      }
      x-foo:unresolved {
        opacity: 0;
      }
    </style>

    :unresolved 伪类只能用于 unresolved 元素,而不能用于继承自 HTMLUnkownElement 的元素

    <style>
      /* 给所有 unresolved 元素添加边框 */
      :unresolved {
        border: 1px dashed red;
        display: inline-block;
      }
      /* unresolved 元素 x-panel 的文本内容为红色 */
      x-panel:unresolved {
        color: red;
      }
      /* 定义注册后的 x-panel 文本内容为绿色 */
      x-panel {
        color: green;
        display: block;
        padding: 5px;
        display: block;
      }
    </style>
    
    <panel>
      I'm black because :unresolved doesn't apply to "panel".
      It's not a valid custom element name.
    </panel>
    
    <x-panel>I'm red because I match x-panel:unresolved.</x-panel>

    历史和浏览器支持

    检查 document.registerElement() 是否存在:

    function supportsCustomElements() {
      return 'registerElement' in document;
    }
    
    if (supportsCustomElements()) {
      // Good to go!
    } else {
      // Use other libraries to create components.
    }

    jpg改rar 

  • 相关阅读:
    Effective C# 原则37:使用标准的配置机制(译)
    Effective C# 原则31:选择小而简单的函数(译)
    Effective C# 原则24:选择申明式编程而不是命令式编程(译)
    Effective C# 原则34:创建大容量的Web API(译)
    Effective C# 原则27:避免使用ICloneable(译)
    Effective C# 第4章:创建基于二进制的组件(译)
    Effective C# 原则39:使用.Net验证(译)
    Effective C# 原则35:选择重写函数而不是使用事件句柄(译)
    Effective C# 原则25: 让你的类型支持序列化(译)
    Effective C# 原则38:使用和支持数据绑定(译)
  • 原文地址:https://www.cnblogs.com/kuangke/p/7590934.html
Copyright © 2020-2023  润新知