Shadow DOM v1 简介

栏目: CSS · 发布时间: 7年前

内容简介:Shadow DOM 是 Web Components 定义的四大标准之一。Shadow DOM 解决了构建网络应用的脆弱性问题。脆弱性是由 HTML、CSS 和 JS 的全局性引起的。Shadow DOM 为网络开发中的常见问题提供解决方案:(图片来源 —— MDN Shadow DOM)

Shadow DOM 是 Web Components 定义的四大标准之一。Shadow DOM 解决了构建网络应用的脆弱性问题。脆弱性是由 HTML、CSS 和 JS 的全局性引起的。

Shadow DOM 为网络开发中的常见问题提供解决方案:

  • 隔离 DOM:组件的 DOM 是独立的(例如, document.querySelector() 不会返回组件 Shadow DOM 中的节点)。

  • 作用域 CSS:Shadow DOM 内部定义的 CSS 在其作用域内。样式规则不会泄漏,页面样式也不会渗入。

  • 组合:为组件设计一个声明性、基于标记的 API。

  • 简化 CSS: 作用域 DOM 意味着您可以使用简单的 CSS 选择器,更通用的 id/class 名称,而无需担心命名冲突。

  • 效率:将应用看成是多个 DOM 块,而不是一个大的(全局性)页面。

    Shadow DOM v1 简介

(图片来源 —— MDN Shadow DOM)

Shadow DOM vs DOM

HTML 因其易于使用的特点驱动着网络的发展。通过声明几个标记,即可在几秒内编写一个带有图文信息和结构的页面。 但是,HTML 自身的功能并不强大。 对于我们人类而言,理解基于文本语言很容易,但是机器需要更多帮助才能理解。 因此,文档对象模型(DOM) 应运而生。

Shadow DOM 与普通 DOM 相同,但有两点区别:

1) 创建/使用的方式;

2) 与页面其他部分有关的行为方式。

这里以 “创建/使用的方式” 为例:

创建 DOM

const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello world!';
header.appendChild(h1);
document.body.appendChild(header);

创建 Shadow DOM

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';

需要注意的是,并不是所有的元素都可以挂载 Shadow DOM,其主要原因是:

  • 浏览器已为该元素托管其自身的内部 shadow DOM(比如 textareainput )。
  • 让元素托管 shadow DOM 毫无意义 (比如 img )。

所以以下方法是行不通的:

document.createElement('input').attachShadow({mode: 'open'});

另外使用 Shadow DOM 时,有以下的注意事项:

  • Shadow DOM,一旦创建就无法删除,它只能用新的替换。

  • 要查看浏览器如何为 input 或 textarea 等元素实现 shadow DOM,对 Chrome 用户来说可以按照:

    DevTools > Settings > Preferences > Elements -> [x] Show user agent shadow DOM
    

    路径启用对应的选项。

Shadow DOM vs Light DOM

Light DOM

组件用户编写的标记,该 DOM 不在组件 shadow DOM 之内,它是元素实际的子项。

<button is="better-button">
  <img src="gear.svg" slot="icon">
  <span>Settings</span>
</button>

Shadow DOM

该 DOM 是由组件的作者编写。Shadow DOM 对于组件而言是本地的,它定义内部结构、作用域 CSS 并封装实现详情。它还可定义如何渲染由组件使用者编写的标记。

#shadow-root
  <style>...</style>
  <slot name="icon"></slot>
  <span id="wrapper">
    <slot>Button</slot>
  </span>

如何创建 Shadow DOM

<div class="dom"></div>

<script>
    let el = document.querySelector('.dom');
    el.attachShadow({ mode: 'open' });
    el.shadowRoot.innerHTML = 'Hi I am shadowed!';

    // 创建新的元素
    let hello = document.createElement('span');
    hello.textContent = 'Hi I am shadowed but wrapped in span';
    el.shadowRoot.appendChild(hello);
</script>

什么是 Shadow Root

ShadowRoot 是 Shadow DOM 的根,从技术上讲,它是一个非元素节点,是一种特殊的文档片段。你可以通过 ShadowRoot 对象上的 appendChildquerySelectorAll 等方法去操作整个 Shadow DOM 树。

对于一个普通的元素,比如 <div> ,你可以通过调用该对象上的 attachShadow 方法来创建一个ShadowRoot, attachShadow 接受一个对象进行初始化,这个对象有一个 mode 属性,它有两个取值: 'open''closed' ,这个属性是在创造 ShadowRoot 的时候需要初始化提供的,并在创建 ShadowRoot 之后成为一个只读属性。

那么 mode: 'open'mode: 'closed' 有什么区别呢?

let $element = document.createElement("div");
$element.attachShadow({ mode: "open" });
$element.shadowRoot

在调用 attachShadow 创建 ShadowRoot 之后, attachShdow 方法会返回 ShadowRoot 对象实例,你可以通过这个返回的对象去构造整个 Shadow DOM。

当 mode 为 'open' 时,在用于创建 ShadowRoot 的外部普通节点(比如 <div> )上,会有一个 shadowRoot 属性,这个属性也就是创造出来的那个 ShadowRoot,也就是说,你可以通过这个属性获取 ShadowRoot,进而对它进行操作。

而当 mode 为 'closed' 时,你将不能再得到这个属性,这个属性会被设置为 null ,比如:

let $element = document.createElement("div");
$element.attachShadow({ mode: "closed" });
$element.shadowRoot // null

一般情况下总是使用 open 模式调用 attachShadow 方法,这样的话可以让组件作者和用户都可以根据需要进行相关操作。

如何设定样式

Shadow DOM 最有用的功能是 作用域 CSS:

  • 外部页面中的 CSS 选择器不应用于组件内部。
  • 内部定义的样式也不会渗出,它们的作用域仅限于宿主元素。

设置宿主元素样式

<style>
:host {
  display: block; 
  contain: content; 
}
</style>

使用 :host 的一个问题是,父页面中的规则较之在元素中定义的 :host 规则具有更高的特异性。 也就是说,外部样式优先。这可让用户从外部替换你已定义的样式。 此外, :host 仅在影子根范围内起作用,因此无法在 shadow DOM 之外使用。

除了 :host 之外,还支持 :host(<selector>) 的函数形式,它可让你基于宿主将对用户互动或状态的反应行为进行封装,或对内部节点进行样式设定。

<style>
:host {
  opacity: 0.4;
  will-change: opacity;
  transition: opacity 300ms ease-in-out;
}
:host(:hover) {
  opacity: 1;
}
:host([disabled]) {
  background: grey;
  pointer-events: none;
  opacity: 0.4;
}
:host(.blue) {
  color: blue; 
}
:host(.pink) > #tabs {
  color: pink;
</style>

基于情景设定样式

如果 :host-context(<selector>) 或其任意父级与 <selector> 匹配,它将与组件匹配。 一个常见用途是根据组件的环境进行主题化。 例如,很多人都通过将 class 应用到 <html><body> 进行主题化:

<body class="darktheme">
  <fancy-tabs>
    ...
  </fancy-tabs>
</body>

如果 :host-context(.darktheme).darktheme 的子级,它将对 <fancy-tabs> 进行样式化:

:host-context(.darktheme) {
  color: white;
  background: black;
}

为分布式节点设定样式

比如说我们已创建了一个 name badge 组件:

<name-badge>
  <h2>Eric Bidelman</h2>
  <span class="title">
    Digital Jedi, <span class="company">Google</span>
  </span>
</name-badge>

组件的 shadow DOM 可为用户的 <h2>.title 设定样式:

<style>
::slotted(h2) {
  margin: 0;
  font-weight: 300;
  color: red;
}
::slotted(.title) {
   color: orange;
}
    
/* 以下方式不生效,因为::slotted()只支持顶层元素    
::slotted(.company),
::slotted(.title .company) {
  text-transform: uppercase;
}
*/
</style>
<slot></slot>

从外部为组件设定样式

有几种方法可从外部为组件设定样式:最简单的方法是使用标签名称作为选择器:

fancy-tabs {
  width: 500px;
  color: red;
}

fancy-tabs:hover {
  box-shadow: 0 3px 3px #ccc;
}

外部样式总是优先于在 shadow DOM 中定义的样式。例如,如果用户编写选择器 fancy-tabs { width: 500px; } ,它将优先于组件的规则: :host { width: 650px;}

使用 CSS 自定义属性创建样式钩子

如果组件的作者通过 CSS 自定义属性 提供样式钩子,则用户可调整内部样式。 从概念上看,这与 <slot> 类似。 你创建 “样式占位符” 以便用户进行替换。

比如 <fancy-tabs> 可让用户替换背景颜色:

<!-- main page -->
<style>
  fancy-tabs {
    margin-bottom: 32px;
    --fancy-tabs-bg: black;
  }
</style>
<fancy-tabs background>...</fancy-tabs>

在其 Shadow DOM 内部:

:host([background]) {
  background: var(--fancy-tabs-bg, #9E9E9E);
  border-radius: 10px;
  padding: 10px;
}

在本例中,该组件将使用 black 作为背景值,因为用户指定了该值。 否则,背景颜色将采用默认值 #9E9E9E

浏览器支持

Shadow DOM v1 简介

(图片来源 —— https://caniuse.com/#feat=shadowdomv1)

由上图可知 Chrome 53、Opera 40 和 Safari 10 以上的版本是支持 shadow DOM v1 标准。 Edge 也在考虑中,并具有 较高的优先级

你可以通过以下方法来检测当前浏览器是否支持 shadow DOM v1 标准:

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

此外对于还不支持 shadow DOM v1 标准的浏览器来说,你也可以引入 shadydomshadycss polyfill 来模拟 v1 的标准。Shady DOM 可以模拟 Shadow DOM 的 DOM 作用域,而 shadycss polyfill 则可以模拟原生 API 提供的 CSS 自定义属性和样式作用域。具体的使用方式,感兴趣的同学,请参考相应的开发文档,这里不再进一步说明。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Python

Programming Python

Mark Lutz / O'Reilly Media / 2006-8-30 / USD 59.99

Already the industry standard for Python users, "Programming Python" from O'Reilly just got even better. This third edition has been updated to reflect current best practices and the abundance of chan......一起来看看 《Programming Python》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具