玩转 Shadow DOM

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 价值 200 美元的免费信用!

大约一年前,Twitter 宣布 它将开始使用 Shadow DOM 而不是 <iframe> 来显示嵌入的推文,如果浏览器支持 Shadow DOM。

为什么?好吧,速度是一个原因。

他们说

浏览器中的内存使用率大大降低,渲染速度也快得多。推文将更快地出现,页面滚动将更加流畅,即使在同一页面上显示多个推文也是如此。

为什么选择?为什么必须使用 iframe 或 Shadow DOM?为什么不直接将内容注入页面?

对于控制来说,这是一个完全可以理解的需求。嵌入的推文应该看起来和行为就像嵌入的推文一样。他们不想担心页面样式的渗入,从而破坏了这一点。

<iframe> 使样式范围变得非常容易。将 iframe 的 src 指向一个 URL,该 URL 显示您希望嵌入的推文看起来像什么,这样就可以了。唯一使用的样式将是您在该文档中包含的样式。

Twitter 以一种渐进增强和便于聚合的方式执行此 iframe 注入。他们提供了一个带有推文的 <blockquote> 和一个 <script>。脚本执行 iframe 注入。如果脚本没有运行,没关系,一个快乐的块引用。如果脚本运行,则是一个功能齐全的嵌入式推文。

该脚本是这里的关键。脚本几乎可以做任何事情,并且他们托管它,因此他们可以随时更改它。这就是他们用来检测 Shadow DOM 支持并改用该方式的方法。正如我们所述,Shadow DOM 渲染速度更快,内存需求更低。Shadow DOM 还可以帮助解决样式范围问题,我们将在稍后讨论。

高度灵活性

还有另一件事,它恰好是我最喜欢的。<iframe> 不会像您期望其他元素那样调整高度以适应其内容。您设置一个高度,就是这样。如果允许它并且内容需要它,它将有滚动条。在 Wufoo 时代,我们不得不跳过很多圈子才能使嵌入的表单(在框架中)达到它们需要的高度。如今,在 CodePen,我们的嵌入式 Pen 具有可调整的高度,但没有“与它们需要的高度一样高”的选项。(我不确定这对 CodePen 嵌入是否说得通,但无论如何,您现在无法做到这一点。)

具有 Shadow DOM 的元素就像任何其他元素一样,它会自然地扩展到内容。我相信这对 Twitter 也很有吸引力。如果他们计算的高度错误,他们就有可能截断内容,或者至少使嵌入的推文看起来很糟糕。

最基本用法

以下是建立 Shadow DOM 和在其中放置内容的最低限度

查看 CodePen 上 Chris Coyier 的 最基本的 Shadow DOM (@chriscoyier)。

注意 Shadow DOM 中的样式如何不泄漏到常规段落元素?如果这样做,两个段落都会是红色的。

还要注意 Shadow DOM 内部的段落不像外部的段落那样是 sans-serif 吗?嗯,通常是这样的。继承的样式仍然会通过 Shadow DOM 继承(因此,从某种意义上说,它不像 iframe 那样是一个强大的屏障)。但是,我们像这样有意地将其强制回到初始状态

:host {
  all: initial;
}

处理回退

我最初的想法是,对于不支持 Shadow DOM 的浏览器,您可以将与您塞入 Shadow DOM 中的完全相同的内容放入具有 srcdoc 的 iframe 中,就像这样...

<iframe srcdoc="the same content">

或者更可能是您在 JavaScript 中进行此操作,因此您将首先测试支持,然后执行 Shadow DOM 内容或动态创建 iframe

查看 CodePen 上 Chris Coyier 的 Shadow DOM 基本 (@chriscoyier)。

事实证明,srcdoc(单独使用)不是回退的最佳选择,因为它在 IE 或 Edge 中不受支持。但使用数据 URL 进行常规 src 也不是什么大问题。以下是 Šime Vidas 修复了这个问题的分支

let content = `
  <style>
    body { /* for fallback iframe */
      margin: 0;
    }
    p { 
      border: 1px solid #ccc;
      padding: 1rem;
      color: red;
      font-family: sans-serif;
    }
  </style>

  <p>Element with Shadow DOM</p>
`;

let el = document.querySelector('.my-element');

if (document.body.attachShadow) {
  
  let shadow = el.attachShadow({ mode: 'open' }); // Allows JS access inside
  shadow.innerHTML = content;
  
} else {
  
  let newiframe = document.createElement('iframe');
  'srcdoc' in newiframe ?
    newiframe.srcdoc = content :
    newiframe.src = 'data:text/html;charset=UTF-8,' + content;
  
  let parent = el.parentNode;
  parent.replaceChild(newiframe, el);

}

TL;DR

  • Shadow DOM 很酷。
  • 它在许多方面与 iframe 相似,包括样式封装。嵌入的第三方内容是一个很好的用例。
  • 可以在回退到 iframe 时使用它,这非常容易。
  • 它是 Web Components 更大世界的一部分,但如果您不想这样做,您不必完全投入其中。

以下是一些 简单的演示(这个演示使用自定义元素),但它不是自己回退,而是使用 polyfill。