首页 > 解决方案 > 有没有办法使用组合来改变带有阴影 dom 的 web 组件的 CSS?

问题描述

我的团队负责人决定使用 LitElement 创建与框架无关的 Web 组件,这是有道理的,因为我们正在创建一个适合与 Vue2、Vue3、React、Preact 等一起使用的公司范围的 UI 库。

但是,我非常担心造型。现在使用我们当前基于 (Vue 2) 的 UI 库,如果我们的一个团队出于任何原因(通常是边缘情况)需要覆盖元素的内部样式,他们可以做的一件事是使用 CSS 组合来正是这样做。就像是:

// in components/my-element.js
class MyElement extends LitElement {
  render(){
    return html`
      <div class="stackui-my-element">
        <p class="paragraph">A paragraph</p>
      </div>
    `;
  }
}
customElements.define('my-element', MyElement);

// in app/businessthing.js
import React from 'react';
import {css} from 'styled-components'
const customCSS = css`
  .this-thing {
    & .stackui-my-element .paragraph {
      border: 2px dotted pink;
    }
  }
`
export default (props) => <div class="this-thing"><my-element /></div>

据我所知,上面的代码不起作用(“我的段落”不会被粉红色的圆点包围),因为 MyElement 有自己的 shadow dom,你无法从外部进入它来更改样式的元素。

规则是否有某种例外,或者你能以某种方式进入shadow dom?除了失去 CSS 封装之外,禁用 shadow dom 的其他影响是什么?

标签: shadow-domweb-frontendlit-element

解决方案


正如您所提到的,在使用 Shadow DOM 时,样式封装会阻止组件的用户影响组件内部的样式。

但是,有几种方法可以通过不同程度的灵活性来实现您想要的,所有这些都是 Shadow DOM 规范行为方式的一部分,而不是特定于 Lit 的,因此,您可以将其与 Vanilla Web Components 或使用其他组件创建的组件一起使用图书馆也是。

以下哪种方式效果更好将取决于您希望为组件强制执行样式规则的严格程度。

下面的所有示例都假定组件的内部 DOM 看起来像您的示例

<div class="stackui-my-element">
  <p class="paragraph">A paragraph</p>
</div>
  1. 使用自定义 CSS 属性(CSS 变量)

当您想将自定义限制为仅特定部分时,这种方法很好。

例如,为了允许用户只能更改段落的边框,您可以在组件的 shadow DOM 的样式中使用带有后备边框的 CSS 变量,如下所示:

.paragraph {
  border: var(--myel-paragraph-border, 1px solid black);
}

然后,希望自定义边框的用户可以通过内联样式或类更改该 CSS 变量的值。

<style>
  .fancy-border {
    --myel-paragraph-border: 2px dotted pink;
  }
</style>
<my-element class="fancy-border"></my-element>

这种方法的最大限制是您需要为希望允许自定义的每个属性添加一个 CSS 变量。

但是,这对于某些用例(例如,严格的设计系统)可能是一个优势,因为它不允许用户自定义您不想自定义的任何内容。

  1. 使用阴影部分

Shadow 部分是 shadow DOM 规范中较新的部分之一,但目前浏览器支持已经相当不错了。它们允许您定义您希望从影子 DOM 外部完全自定义的组件的任意部分。

要使用它们,您需要将part属性添加到您希望定义为部件的 HTML 节点。

<div class="stackui-my-element">
  <p part="paragraph" class="paragraph">A paragraph</p>
</div>

并且在使用组件时,将::part()选择器添加到样式中,以便您可以自定义该特定部分而不是组件宿主。它的工作方式与您设置诸如原生输入占位符之类的样式非常相似。

<style>
  .fancy::part(paragraph) {
    /* you can do whatever you want here */
    border: 2px dotted pink;
    color: blue;
    font-style: italic;
  }
</style>
<my-element class="fancy"></my-element>

如您所见,阴影部分将允许您覆盖应用于节点的每种样式,因此,您必须小心何时使用它们,因为用户可能最终能够自定义您不想要的东西。

最后注意事项:

您也可以使用插槽来实现类似的事情,但考虑到您的问题内容,​​这可能不是一个容易的改变。

如果您想了解更多有关从组件外部设置 Shadow DOM 样式的信息,这里有一篇文章。(免责声明:我是那篇文章的作者。)


推荐阅读