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 里。 当需要更新的时候,浏览器会自动保持它们的同步。




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


Bob
B. Love

Shadow DOM 样式

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

样式化宿主元素(host element)

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

:host(x-bar:host) {
  /* 当宿主是  元素时生效。 */
}
:host(.different:host) {
  /* 当宿主的类  时生效。 */
}
: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 伪元素 穿过插入点来指定样式

Light DOM

I'm not underlined

I'm underlined in Shadow DOM!

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

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

使用多个shadowdom

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

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

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

"Shadow 插入点" () 作为占位符可以插入 ShadowDOM
普通插入点 () 作为占位符可以插入 普通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 树

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

Eric

Bidelman

Digital Jedi

footer text

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

Light DOM

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');
// 

添加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 = "I'm an x-foo!";
};
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 = "I'm in the element's Shadow DOM!";
};

var XFoo = document.registerElement('x-foo-shadowdom', {prototype: XFooProto});

从模板创建元素




为自定义元素增加样式




  
  • Do
  • Re
  • Mi
  • 为使用 Shadow DOM 的元素增加样式

    • Polymer 文档
    • Shadow DOM 201 - CSS and Styling

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

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

    注册后渐显的 标签:

    
    

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

    
    
    
      I'm black because :unresolved doesn't apply to "panel".
      It's not a valid custom element name.
    
    
    I'm red because I match x-panel:unresolved.
    

    历史和浏览器支持

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

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

    参考

    • Shadow DOM 101
    • Shadow DOM 201 - CSS and Styling
    • Shadow DOM 301 - CSS and Styling
    • Custom Elements
    • Polymer

    你可能感兴趣的:(HTML5 ShadowDOM & CustomElements)