河源做网站:使用共享样式表设置Web组件样式

2019.08.13 mf_web

50

Web组件是Web的一个惊人的新功能,允许开发人员定义自己的自定义HTML元素。当与样式指南结合使用时,Web组件可以创建组件API,允许开发人员停止和粘贴代码片段,而只是使用DOM元素。通过使用shadow DOM,我们可以封装Web组件,而不必担心页面上任何其他样式表的特异性战争。

但是,Web组件和样式指南目前似乎彼此不一致。一方面,样式指南提供了一组全局应用于页面的规则和样式,并确保整个网站的一致性。另一方面,具有阴影DOM的Web组件可防止任何全局样式穿透其封装,从而防止样式指南影响它们。

河源做网站那么,两者如何共存,全局风格指南继续提供一致性和样式,甚至是带有影子DOM的Web组件?值得庆幸的是,现在有一些解决方案,以及更多的解决方案,使全局样式指南能够为Web组件提供样式。(对于本文的其余部分,我将使用术语“Web组件”来引用带有shadow DOM的自定义元素。)

Web组件中的全局样式指南样式应该是什么?

在讨论如何获取全局样式指南来设置Web组件的样式之前,我们应该讨论它应该和不应该尝试的样式。

首先,Web组件的当前最佳实践声明应该封装Web组件(包括其样式),以便它不依赖于任何外部资源来运行。这使得它可以在网站上或网站外的任何地方使用,即使样式指南不可用。

下面是一个简单的登录表单Web组件,它封装了它的所有样式。

<template id="login-form-template">
  <style>
    :host {
      color: #333333;
      font: 16px Arial, sans-serif;
    }
    p {
      margin: 0;
    }
    p + p {
      margin-top: 20px;
    }
    a {
      color: #1f66e5;
    }
    label {
      display: block;
      margin-bottom: 5px;
    }
    input[type="text"],
    input[type="password"] {
      background-color: #eaeaea;
      border: 1px solid grey;
      border-radius: 4px;
      box-sizing: border-box;
      color: inherit;
      font: inherit;
      padding: 10px 10px;
      width: 100%;
    }
    input[type="submit"] {
      font: 16px/1.6 Arial, sans-serif;
      color: white;
      background: cornflowerblue;
      border: 1px solid #1f66e5;
      border-radius: 4px;
      padding: 10px 10px;
      width: 100%;
    }
    .container {
      max-width: 300px;
      padding: 50px;
      border: 1px solid grey;
    }
    .footnote {
      text-align: center;
    }
  </style>
  <div class="container">
    <form action="#">
      <p>
        <label for="username">User Name</label>
        <input type="text" id="username" name="username">
      </p>
      <p>
        <label for="password">Password</label>
        <input type="password" id="password" name="password">
      </p>
      <p>
        <input type="submit" value="Login">
      </p>
      <p class="footnote">Not registered? <a href="#">Create an account</a></p>
    </form>
  </div></template><script>
  const doc = (document._currentScript || document.currentScript).ownerDocument;
  const template = doc.querySelector('#login-form-template');
  customElements.define('login-form', class extends HTMLElement {
    constructor() {
      super();
      const root = this.attachShadow({mode: 'closed'});
      const temp = document.importNode(template.content, true);
      root.appendChild(temp);
    }
  });</script>

注意:代码示例是针对Web组件的版本1规范编写的。

使用用户名和密码登录表单。
一个简单的登录表单Web组件

但是,完全封装每个Web组件将不可避免地导致大量重复的CSS,尤其是在设置本机元素的排版和样式时。如果开发人员想要在其Web组件中使用段落,锚标记或输入字段,则应该像网站的其余部分一样进行样式设置。

如果我们完全封装了Web组件所需的所有样式,那么样式段落,锚标记,输入字段等的CSS将在使用它们的所有Web组件中重复。这不仅会增加维护成本,还会为用户带来更大的下载大小。

Web组件不应封装所有样式,而应仅封装其独特样式,然后依赖一组共享样式来处理其他所有样式。这些共享样式实际上将成为一种Normalize.css,Web组件可以使用它来确保根据样式指南设置本机元素的样式。

在前面的示例中,登录表单Web组件将仅为其两个唯一类声明样式:.container和.footnote。其余样式将属于共享样式表,并将设置段落,锚标记,输入字段等样式。

简而言之,样式指南不应该尝试设置Web组件的样式,而应该提供一组共享样式,Web组件可以使用这些样式来实现一致的外观。

如何使用外部样式表设置阴影DOM

Web组件的初始规范(称为版本0)允许任何外部样式表通过使用::shadow或/deep/CSS选择器穿透阴影DOM 。采用::shadow和/deep/使你有一个风格指南穿透影子DOM和设置共享样式,网络组件是否希望你与否。

/* Style all p tags inside a web components shadow DOM */login-form::shadow p {
  color: red;}

随着最新版本的Web组件规范(称为版本1)的出现,作者已经删除了外部样式表来穿透阴影DOM的能力,并且他们没有提供任何替代方案。相反,哲学已经从使用龙变为风格Web组件变为使用桥。换句话说,Web组件作者应该负责允许外部样式规则为其组件设置样式,而不是被迫允许它们。

不幸的是,这种哲学还没有真正赶上网络,这让我们有点发酵。幸运的是,今天可以使用的一些解决方案,以及一些在不久的将来出现的解决方案,将允许共享样式表来设置Web组件的样式。

今天你能做什么

您现在可以使用三种技术来允许Web组件共享样式:@import,自定义元素和Web组件库。

使用@IMPORT

今天将样式表引入Web组件的唯一本机方法是使用@import。虽然这有效,但它是一种反模式。但是,对于Web组件,这是一个更大的性能问题。

<template id="login-form-template">
  <style>
    @import "styleguide.css"
  </style>
  <style>
    .container {
      max-width: 300px;
      padding: 50px;
      border: 1px solid grey;
    }
    .footnote {
      text-align: center;
    }
  </style>
  <!-- rest of template DOM --></template>

通常,它@import是一种反模式,因为它会按顺序下载所有样式表,而不是并行下载,特别是如果它们是嵌套的。在我们的情况下,无法帮助下载单个样式表,因此理论上应该没问题。但是,当我在Chrome中对此进行测试时,结果显示,使用@import该页面时,比仅将样式直接嵌入到Web组件中的速度慢了半秒。

注:由于在如何区别HTML的填充工具进口的作品相比,本地的HTML进口,WebPagetest.org只能用来给在原生地支持HTML进口(即浏览器)的浏览器可靠的结果。

本机Web组件性能的条形图。
三个性能测试的结果表明,@import与仅将样式直接嵌入到Web组件中相比,浏览器的渲染速度要慢半秒。

最后,@import仍然是反模式,可能是Web组件中的性能问题。所以,这不是一个很好的解决方案。

不要使用SHADOW DOM

因为尝试向Web组件提供共享样式的问题源于使用shadow DOM,所以完全避免问题的一种方法是不使用shadow DOM。

通过不使用shadow DOM,您将创建自定义元素而不是Web组件(请参阅下面的旁边),唯一的区别是缺少shadow DOM和范围。你的元素将受页面样式的影响,但我们今天已经必须处理它,所以我们还不知道如何处理。webcomponentjs polyfill完全支持自定义元素,它具有出色的浏览器支持。

自定义元素的最大好处是您可以立即使用它们创建模式库,而不必等到共享样式问题得到解决。并且因为Web组件和自定义元素之间的唯一区别是shadow DOM,所以一旦共享样式的解决方案可用,您就可以始终在自定义元素中启用shadow DOM。

如果您决定创建自定义元素,请注意自定义元素和Web组件之间的一些差异。

首先,因为自定义元素的样式受页面样式的影响,反之亦然,因此您需要确保选择器不会引起任何冲突。如果您的页面已经使用了样式指南,那么请在样式指南中保留自定义元素的样式,并让元素输出预期的DOM和类结构。

通过将样式保留在样式指南中,您将为开发人员创建平滑的迁移路径,因为他们可以像以前一样继续使用样式指南,但随后可以慢慢迁移到使用新的自定义元素。一旦每个人都使用自定义元素,您可以将样式移动到元素内部,以便将它们保持在一起,以便以后更轻松地重构Web组件。

其次,请确保将任何JavaScript代码封装在一个立即调用的函数表达式(IFFE)中,这样就不会将任何变量泄露到全局范围。除了不提供CSS作用域之外,自定义元素不提供JavaScript作用域。

第三,您需要使用connectedCallback自定义元素的功能将模板DOM添加到元素中。根据Web组件规范,自定义元素不应在构造函数期间添加子元素,因此您需要将DOM添加到connectedCallback函数中。

最后,该<slot>元素在shadow DOM之外不起作用。这意味着您必须使用不同的方法为开发人员提供一种将内容插入自定义元素的方法。通常,这需要自己操纵DOM以将其内容插入到您想要的位置。

但是,因为shadow DOM和light DOM之间没有自定义元素的分离,所以由于元素的级联样式,你还必须非常小心不要为插入的DOM设置样式。

<!-- login-form.html --><template id="login-form-template">
  <style>
    login-form .container {
      max-width: 300px;
      padding: 50px;
      border: 1px solid grey;
    }
    login-form .footnote {
      text-align: center;
    }
  </style>
  <!-- Rest of template DOM --></template><script>(function() {
  const doc = (document._currentScript || document.currentScript).ownerDocument;
  const template = doc.querySelector('#login-form-template');
  customElements.define('login-form', class extends HTMLElement {
    constructor() {
      super();
    }
    // Without the shadow DOM, we have to manipulate the custom element
    // after it has been inserted in the DOM.
    connectedCallback() {
      const temp = document.importNode(template.content, true);
      this.appendChild(temp);
    }
  });})();</script>
<!-- index.html --><link rel="stylesheet" href="styleguide.css"><link rel="import" href="login-form.html"><login-form></login-form>

在性能方面,定制元素是几乎一样快如不使用Web组件(即链接所述共享样式表中head,并且仅使用源DOM元素)。在您今天可以使用的所有技术中,这是迄今为止最快的技术。

自定义元素性能的条形图。
两个性能测试的结果表明,自定义元素几乎与不使用Web组件一样快。

旁白:自定义元素仍然是用于所有意图和目的的Web组件。术语“Web组件”用于描述四种不同的技术:自定义元素,模板标签,HTML导入和shadow DOM。

不幸的是,该术语已被用于描述使用这四种技术的任何组合的任何东西。这就引起了人们在说“网络组件”时人们意味着什么的混淆。正如Rob Dodson发现的那样,我发现在讨论带阴影DOM和没有阴影DOM的自定义元素时使用不同的术语会很有帮助。

我所谈到的大多数开发人员倾向于将术语“Web组件”与使用shadow DOM的自定义元素相关联。因此,出于本文的目的,我创建了Web组件和自定义元素之间的人为区分。

使用WEB组件库

您今天可以使用的另一个解决方案是Web组件库,例如Polymer,SkateJS或X-Tag。这些库有助于填补今天支持的漏洞,还可以简化创建Web组件所需的代码。它们通常还提供附加功能,使编写Web组件更容易。

例如,Polymer允许您只用几行JavaScript创建一个简单的Web组件。另一个好处是Polymer提供了使用shadow DOM和共享样式表的解决方案。这意味着您可以创建共享样式的Web组件。

为此,创建他们称之为样式模块的样式,其中包含所有共享样式。它可以是<style>内联共享样式的标记,也可以是<link rel=“import”>指向共享样式表的标记。在任何一种情况下,<style include>都要在Web组件中包含带有标记的样式,然后Polymer将解析样式并将它们作为内联<style>标记添加到Web组件中。

<!-- shared-styles.html --><dom-module id="shared-styles">
  <!-- Link to a shared style sheet -->
  <!-- <link rel="import" href="styleguide.css"> -->
  <!-- Inline the shared styles -->
  <template>
    <style>
    :host {
      color: #333333;
      font: 16px Arial, sans-serif;
    }
    /* Rest of shared CSS */
    </style>
  </template></dom-module>
<!-- login-form.html --><link rel="import" href="../polymer/polymer.html"><link rel="import" href="../shared-styles/shared-styles.html"><dom-module id="login-form">
  <template>
    <!-- Include the shared styles -->
    <style include="shared-styles"></style>
    <style>
      .container {
        max-width: 300px;
        padding: 50px;
        border: 1px solid grey;
      }
      .footnote {
        text-align: center;
      }
    </style>
    <!-- Rest of template DOM -->
  </template>
  <script>
  Polymer({
    is: 'login-form'
  });
  </script></dom-module>

使用库的唯一缺点是它可能会延迟Web组件的渲染时间。这不应该是一个惊喜,因为下载库的代码并处理它需要时间。在完成库处理之前,页面上的任何Web组件都无法开始呈现。

在Polymer的情况下,与本机Web组件相比,它可以将页面呈现时间延迟最多半秒。一个嵌入的样式风格模块相比稍慢的风格模块链接的样式,并直接嵌入样式到Web组件是一样使用样式模块一样快。

同样,Polymer没有特别做任何事情使渲染时间变慢。下载Polymer库并处理其所有令人敬畏的功能,以及创建所有模板绑定,需要时间。这只是您使用Web组件库所要做的权衡。

聚合物网组件性能的条形图。

性能测试的结果表明,使用Polymer,Web组件的渲染速度比原生Web组件慢半秒。

未来的承诺

如果当前的解决方案都不适合您,请不要绝望。如果一切顺利,在几个月到几年内,我们将能够使用几种不同的方法来使用共享样式。

自定义属性

自定义属性(或CSS变量,因为它们已被调用)是一种在CSS中设置和使用变量的方法。这个概念对于CSS预处理器来说并不陌生,但作为本机CSS特性,自定义属性实际上比预处理器变量更强大。

要声明自定义属性,请使用自定义属性表示法–my-variable: value,并使用property: var(–my-variable)。自定义属性像任何其他CSS规则一样级联,因此其值从其父级继承并可以被覆盖。对自定义属性的唯一警告是它们必须在选择器内声明,并且不能像预处理器变量那样自行声明。

<style>/* Declare the custom property */html {
  --main-bg-color: red;}/* Use the custom property */input {
  background: var(--main-bg-color);}</style>

使自定义属性如此强大的一件事是它们能够穿透阴影DOM。这/deep/与::shadow选择器的想法不同,因为它们不会强行进入Web组件。相反,Web组件的作者必须在其CSS中使用自定义属性才能应用它。这意味着Web组件作者可以创建自定义属性API,Web组件的使用者可以使用它来应用自己的样式。

<template id="my-element-template">
  <style>
  /* Declare the custom property API */
  :host {
    --main-bg-color: brown;
  }
  .one {
    color: var(--main-bg-color);
  }
  </style>
  <div class="one">Hello World</div></template><script>/* Code to set up my-element web component */</script><my-element></my-element><style>/* Override the custom variable with own value */my-element {
  --main-bg-color: red;}</style>

浏览器对自定义属性的支持非常好。这不是你今天可以使用的解决方案的唯一原因是,有没有工作填充工具,而不自定义元素的版本1. webcomponentjs填充工具背后的团队目前正在努力增加它,但它尚未发布,并在建状态,这意味着如果您将资产哈希用于生产,则无法使用它。根据我的理解,它将在明年初的某个时候发布。

即便如此,自定义属性也不是在Web组件之间共享样式的好方法。因为它们只能用于声明单个属性值,所以Web组件仍然需要嵌入样式指南的所有样式,尽管它们的值用变量替换。

自定义属性更适合主题选项,而不是共享样式。因此,自定义属性不是解决我们问题的可行方案。

/ *使用自定义属性* / input {background:var(-main-bg-color); } </ style>

使自定义属性如此强大的一件事是它们能够穿透阴影DOM。这/deep/与::shadow选择器的想法不同,因为它们不会强行进入Web组件。相反,Web组件的作者必须在其CSS中使用自定义属性才能应用它。这意味着Web组件作者可以创建自定义属性API,Web组件的使用者可以使用它来应用自己的样式。

<template id="my-element-template">
  <style>
  /* Declare the custom property API */
  :host {
    --main-bg-color: brown;
  }
  .one {
    color: var(--main-bg-color);
  }
  </style>
  <div class="one">Hello World</div></template><script>/* Code to set up my-element web component */</script><my-element></my-element><style>/* Override the custom variable with own value */my-element {
  --main-bg-color: red;}</style>

浏览器对自定义属性的支持非常好。这不是你今天可以使用的解决方案的唯一原因是,有没有工作填充工具,而不自定义元素的版本1. webcomponentjs填充工具背后的团队目前正在努力增加它,但它尚未发布,并在建状态,这意味着如果您将资产哈希用于生产,则无法使用它。根据我的理解,它将在明年初的某个时候发布。

即便如此,自定义属性也不是在Web组件之间共享样式的好方法。因为它们只能用于声明单个属性值,所以Web组件仍然需要嵌入样式指南的所有样式,尽管它们的值用变量替换。

自定义属性更适合主题选项,而不是共享样式。因此,自定义属性不是解决我们问题的可行方案。

@APPLY规则

除了自定义属性,CSS也在获取@apply规则。应用规则本质上是CSS世界的mixins。它们以与自定义属性类似的方式声明,但可用于声明属性组而不仅仅是属性值。就像自定义属性一样,它们的值可以被继承和覆盖,并且它们必须在选择器内声明才能工作。

<style>/* Declare rule */html {
  --typography: {
    font: 16px Arial, sans-serif;
    color: #333333;
  }}/* Use rule */input {
  @apply --typography;}</style>

浏览器对@apply规则的支持基本上不存在。Chrome目前支持功能标志(我找不到),但这就是它。由于没有用于自定义属性的polyfill,因此也没有工作的polyfill。webcomponentjs polyfill团队还在努力添加@apply规则以及自定义属性,因此一旦新版本发布,两者都将可用。

与自定义属性不同,@apply规则是共享样式的更好解决方案。因为它们可以设置一组属性声明,所以您可以使用它们为所有本机元素设置默认样式,然后在Web组件中使用它们。为此,您必须@apply为每个本机元素创建规则。

但是,要使用样式,您必须手动将它们应用于每个本机元素,这仍然会每个Web组件中的样式声明。虽然这比嵌入所有样式更好,但它不是很方便,因为它成为每个Web组件顶部的样板,为了使样式正常工作,您必须记住添加它们。

/* styleguide.css */html {
  --typography: {
    color: #333333;
    font: 16px Arial, sans-serif;
  }
  --paragraph: {
    margin: 0;
  }
  --label {
    display: block;
    margin-bottom: 5px;
  }
  --input-text {
    background-color: #eaeaea;
    border: 1px solid grey;
    border-radius: 4px;
    box-sizing: border-box;
    color: inherit;
    font: inherit;
    padding: 10px 10px;
    width: 100%;
  }
  --input-submit {
    font: 16px/1.6 Arial, sans-serif;
    color: white;
    background: cornflowerblue;
    border: 1px solid #1f66e5;
    border-radius: 4px;
    padding: 10px 10px;
    width: 100%;
  }
  /* And so on for every native element */}
<!-- login-form.html --><template id="login-form-template">
  <style>
    :host {
      @apply --typography;
    }
    p {
      @apply --paragraph;
    }
    label {
      @apply --label;
    }
    input-text {
      @apply --input-text;
    }
    .input-submit {
      @apply --input-submit;
    }
    .container {
      max-width: 300px;
      padding: 50px;
      border: 1px solid grey;
    }
    .footnote {
      text-align: center;
    }
  </style>
  <!-- Rest of template DOM --></template>

由于需要广泛的样板,我不相信@apply规则是在Web组件之间共享样式的好方法。不过,它们是主流的理想解决方案。

根据Web组件规范,浏览器会忽略<link rel=“stylesheet”>shadow DOM中的任何标记,就像处理文档片段一样对待它们。这使我们无法链接到我们的Web组件中的任何共享样式,这是不幸的 - 也就是说,直到几个月前,Web组件工作组提出<link rel=“stylesheet”>标记应该在shadow DOM中工作。经过一周的讨论,他们都同意他们应该,并且几天后他们将它添加到HTML规范中。

<template id="login-form-template">
  <link rel="stylesheet" href="styleguide.css">
  <style>
    .container {
      max-width: 300px;
      padding: 50px;
      border: 1px solid grey;
    }
    .footnote {
      text-align: center;
    }
  </style>
  <!-- rest of template DOM --></template>

如果工作组对规范达成一致意见有点太快,那是因为它不是新提案。让link标签在阴影中工作DOM实际上至少在三年前被提出,但是它被积压直到它们能够确保它不是性能问题。

如果对提案的接受程度不够令人兴奋,Chrome 55(目前是Chrome Canary)添加了使link标签在shadow DOM中工作的初始功能。甚至看来这个功能已经落入当前版本的Chrome中。甚至Safari也在Safari 18中实现了该功能。

能够链接共享样式是迄今为止在Web组件之间共享样式的最方便的方法。您所要做的就是创建link标记,并且所有原生元素都将相应地进行样式设置,而无需任何额外的工作。

当然,浏览器制造商实施该功能的方式将决定该解决方案是否可行。为了使其正常工作,link需要对标记进行重复数据删除,以便请求相同CSS文件的多个Web组件只会导致一个HTTP请求。CSS也需要只解析一次,这样Web组件的每个实例都不必重新计算共享样式,而是重用计算样式。

Chrome 已经完成了这两项工作。因此,如果所有其他浏览器制造商以相同的方式实现它,那么link在shadow DOM中工作的标签肯定会解决如何在Web组件之间共享样式的问题。

可构造的样式表

您可能会发现很难相信,因为我们还没有得到它,但是link在DOM中工作的标签不是一个长期的解决方案。相反,它只是一个短期的解决方案,让我们得到真正的解决方案:可构造的样式表。

可构造样式表是允许StyleSheet通过构造函数在JavaScript中创建对象的提议。然后可以通过API将构造的样式表添加到shadow DOM,这将允许shadow DOM使用一组共享样式。

不幸的是,这是我可以从提案中收集的全部内容。我试图通过询问Web组件工作组找到有关可构造样式表的更多信息,但他们将我重定向到W3C的CSS工作组的邮件列表,我再次询问,但没有人回复。我甚至无法弄清楚提案是如何进展的,因为它在两年多来一直没有更新。

即便如此,Web组件工作组使用它作为该解决方案的共享风格的Web组件之间。希望该提案能够更新,或者Web组件工作组将发布有关该提案及其采用的更多信息。在此之前,“长期”解决方案似乎在可预见的未来不会发生。

得到教训

经过数月的研究和测试,我对未来充满希望。令人欣慰的是,经过多年没有在Web组件之间共享样式的解决方案,最终有答案。这些答案可能不会再建立几年,但至少它们存在。

如果您想使用共享样式指南来设置Web组件的样式,要么您不能使用shadow DOM而是创建自定义元素,或者您可以使用polyfills支持共享样式的Web组件库。这两种解决方案都有其优点和缺点,因此请使用最适合您项目的解决方案。

如果您决定在深入研究Web组件之前等待一段时间,那么在几年内我们应该有一些很好的解决方案来共享它们之间的样式。因此,请继续检查它的进展情况。

要记住的事情

如果您决定今天使用自定义元素或Web组件,请记住一些事项。

最重要的是,Web组件规范仍在积极开发中,这意味着事情可以而且将会发生变化。网络组件仍然是最前沿的,所以随着你的发展,准备好保持你的脚趾。

如果您决定使用shadow DOM,请知道它在polyfilled浏览器中非常慢且无法使用。正是出于这个原因,Polymer的开发人员创建了他们的shady DOM实现并将其作为默认设置。

Chrome,Opera和最近的Safari是唯一支持shadow DOM版本0的浏览器。Firefox仍处于开发阶段,尽管它已经支持它自29版以来的实验。微软仍在考虑将其用于Edge并将其作为其路线图的高优先级。

但是,shadow DOM版本0是旧规范。Shadow DOM版本1是新版本,只有Chrome,Safari和Opera 完全支持它。更不用说自定义元素版本0经历了相同的升级,只有Chrome 完全支持自定义元素版本1,而Safari技术预览支持版本17。自定义元素版本1在Web组件的编写方式上有一些重大更改,因此请务必完全了解其所需的内容。

河源做网站

最新案例

寒枫总监

来电咨询

400-6065-301

微信咨询

寒枫总监

TOP