简体   繁体   English

在 shadow-root 元素中覆盖 styles

[英]Override styles in a shadow-root element

Is there a way to change styles found in a shadow element?有没有办法更改影子元素中的 styles? Specifically, extend/overwrite some properties found in a css class ?具体来说,扩展/覆盖在 css class中找到的一些属性? I am using a chrome-extension called Beanote which hasn't been updated since April(2017) and there's a pesky bug I'd like to fix.我正在使用一个名为Beanote的 chrome 扩展程序,它自 4 月(2017 年)以来就没有更新过,并且有一个我想修复的讨厌的错误。 I found that one line of css patches it up enough for me, but I am at a loss at applying it without going inside of the shadow element itself and directly editing those styles in the dev tools.我发现一行 css 对我来说已经足够了,但是我不知道如何应用它而不进入阴影元素本身并直接在开发工具中编辑那些 styles。

I'm looking for a way for this:我正在为此寻找一种方法:

/*global css rule*/
.the-class-name { property-name: my-value; }

to overwrite this:覆盖这个:

/* style tag inside the shadow-root */
.the-class-name { property-name: bad-value; }


Most of the resources I've found online with queries involving shadow-root override style or edit shadow-root styling had something to do with :host which, if its meant for this, doesn't work for my needs or deprecated functionality like ::shadow .我在网上找到的大多数资源都涉及shadow-root override styleedit shadow-root styling:host有关,如果它是为此目的,则不适用于我的需要或已弃用的功能,例如::shadow

Because of the isolation of styles, which is a feature of Shadow DOM, you cannot define a global CSS rule that will be applied in the Shadow DOM scope.由于样式隔离是 Shadow DOM 的一个特性,因此您无法定义将应用于 Shadow DOM 范围的全局 CSS 规则。

It could be possible with CSS variables but they should be implemented explicitly in the shadowed component (which is not the case with this 3rd party library). CSS 变量可能是可能的,但它们应该在阴影组件中显式实现(这个 3rd 方库不是这种情况)。

A workaround is to inject the line of style in the shadow DOM directly.一种解决方法是直接在 shadow DOM 中注入样式行。

//host is the element that holds the shadow root:
var style = document.createElement( 'style' )
style.innerHTML = '.the-class-name { property-name: my-value; }'
host.shadowRoot.appendChild( style )

NB: it will work only if the Shadow DOM mode is set to 'open' .注意:只有当 Shadow DOM mode设置为'open'它才会工作。


2019 update for Chrome 73+ and Opera 60+ Chrome 73+ 和 Opera 60+ 的 2019 更新

Now it is possible to instantiate a CSSStyleSheet object directly and to affect it to a Shadow DOM or a document:现在可以直接实例化 CSSStyleSheet 对象并将其影响到 Shadow DOM 或文档:

var sheet = new CSSStyleSheet
sheet.replaceSync( `.color { color: pink }`)
host.shadowRoot.adoptedStyleSheets = [ sheet ] 

Ionic V4 select down icon color change example Ionic V4 选择向下图标颜色更改示例

document.querySelector('#my-select').shadowRoot.querySelector('.select-icon-inner').setAttribute('style', 'opacity:1');


ionViewDidEnter() {
    document.querySelector('#my-select').shadowRoot.querySelector('.select-icon-inner').setAttribute('style', 'opacity:1');
  }

If you want overwrite the default generated shadowRoot style then have to call js function after page loaded fully.如果要覆盖默认生成的 shadowRoot 样式,则必须在页面完全加载后调用 js 函数。

Extending on the previous answers.扩展以前的答案。

Outside styles always win over styles defined in the Shadow DOM, ie when you add a global style rule that reference the component you are styling.外部样式总是胜过 Shadow DOM 中定义的样式,即当您添加引用您正在设置样式的组件的全局样式规则时。 See: https://developers.google.com/web/fundamentals/web-components/shadowdom#stylefromoutside请参阅: https : //developers.google.com/web/fundamentals/web-components/shadowdom#stylefromoutside

Otherwise this will depend on if the elements shadow DOM was embedded with a styleSheet , or if it adopted a style-sheet using adoptedStyleSheets .否则,这将取决于元素 shadow DOM 是否嵌入了styleSheet ,或者它是否采用了使用adoptedStyleSheets的样式表。

If the element was embedded in the element you can add or insert a rule to the existing style-sheet using addRule or insertRule .如果元素嵌入在元素中,您可以使用addRuleinsertRule向现有样式表添加或插入规则。 This also work for style-sheets added with adopedStyleSheets .这也适用于添加了adopedStyleSheets样式表。

As mentioned in the previous answer, you can append a new style-sheet to the list of adopted style-sheets instead.正如在前面的答案中提到的,您可以将一个新的样式表附加到采用的样式表列表中。 This also work when the shadowRoot contains a embedded styleSheet , since adoptedStyleSheets takes precedence, and styleSheetList is a read-only property.这也正常工作时shadowRoot包含一个嵌入式styleSheet ,因为adoptedStyleSheets优先,并styleSheetList是一个只读属性。

assert(myElement.shadowRoot.styleSheets.length != 0);
myElement.shadowRoot.styleSheets[0].addRule(':host', 'display: none;');

assert(myElement.shadowRoot.adoptedStyleSheets.length != 0);
`myElement.shadowRoot.adoptedStyleSheets[0].addRule(':host', 'display: none;');`

const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: none; }`);

const elemStyleSheets = myElement.shadowRoot.adoptedStyleSheets;

// Append your style to the existing style sheet.
myElement.shadowRoot.adoptedStyleSheets = [...elemStyleSheets, sheet];

// Or if just overwriting a style set in the embedded `styleSheet`
myElement.shadowRoot.adoptedStyleSheets = [sheet];

I'd like to second an answer given by @Renato in one of the comments, since it points out the good, IMHO, way to solve the problem of customizing a WebComponent from the hosting application.我想在其中一条评论中附上@Renato 给出的答案,因为它指出了解决从托管应用程序自定义 WebComponent 问题的好方法,恕我直言。

@Supersharp is right in the fact, that the external CSS rules are not propagating into thee Shadow Root, that's by design. @Supersharp 是正确的,外部 CSS 规则没有传播到你的 Shadow Root 中,这是设计使然。

CSS variables are a good direction, but from my personal experience are a bit of an overkill for a value of a singular usage, AND yes, they MUST be supported be the WebComponent up-front. CSS 变量是一个很好的方向,但根据我的个人经验,对于单一用法的值来说有点矫枉过正,而且是的,必须预先支持 WebComponent。

Propagating the properties through the :host via inheritance (exactly as @Renato mentioned) is, IMHO, the perfectly right pattern aligned with the API design:通过继承(正如@Renato 提到的)通过:host传播属性是,恕我直言,与 API 设计一致的完全正确的模式:

  • Custom element's ( :host 's) CSS rules are by design overridable by the outer rules自定义元素( :host的)CSS 规则设计为可被外部规则覆盖
  • :host 's children, the inner content of the Shadow DOM, MAY inherit the CSS rules of the :host , either by default or by explicit rule - and this is too, by design :host的孩子,Shadow DOM 的内部内容,可以默认继承:host的 CSS 规则,或者通过显式规则 - 这也是,设计

I'd say, that where applicable, this approach would better be taken before considering CSS stylesheet injection, and also does not suffer from the limitation of open mode only.我想说,在适用的情况下,最好在考虑 CSS 样式表注入之前采用这种方法,并且也不会受到仅open模式的限制。

Of course, this approach won't help when:当然,这种方法在以下情况下无济于事:

  • Inner elements are not inheriting relevant rules from the :host内部元素没有从:host继承相关规则
  • The structure of a WebComponent is quite complex, so that single :host simply can't help them all WebComponent 的结构相当复杂,以至于单个:host根本无法帮助他们

Yet, again from my own experience, simple components with desirably overridable CSS rules may benefit much from the non-intrusive pattern of propagating rules via :host .然而,再次根据我自己的经验,具有可覆盖的 CSS 规则的简单组件可能会从通过:host传播规则的非侵入式模式中受益匪浅。

A solution for cases when you have nested elements with shadowRoot.当你有带有 shadowRoot 的嵌套元素时的解决方案。

Imagine you have:想象一下你有:

<div class="container">
  #shadowRoot
    <div class="inner-component">
      #shadowRoot
        <div class="target-element">some</div> <!-- gonna be color:red -->
    </div>
</div>

Usage:用法:

const topLevelComponentSelector = '.container';
const innerComponentSelector = '.inner-component';
const cssToAdd = '.inner-component > * { color: red }';

adjustShadowRootStyles([topLevelComponentSelector, innerComponentSelector], cssToAdd);

The implementation (Typescript):实施(打字稿):


//main function
function adjustShadowRootStyles(hostsSelectorList: ReadonlyArray<string>, styles: string): void {
  const sheet = new CSSStyleSheet();
  sheet.replaceSync(styles);

  const shadowRoot = queryShadowRootDeep(hostsSelectorList);
  shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, sheet];
}

// A helper function
function queryShadowRootDeep(hostsSelectorList: ReadonlyArray<string>): ShadowRoot | never {
  let element: ShadowRoot | null | undefined = undefined;

  hostsSelectorList.forEach((selector: string) => {
    const root = element ?? document;
    element = root.querySelector(selector)?.shadowRoot;
    if (!element) throw new Error(`Cannot find a shadowRoot element with selector "${selector}". The selectors chain is: ${hostsSelectorList.join(', ')}`);
  });

  if (!element) throw new Error(`Cannot find a shadowRoot of this chain: ${hostsSelectorList.join(', ')}`);
  return element;
}

PS Keep in mind that such webcomponents might appear with a small delay or async, so you have to call adjustShadowRootStyles() after they were loaded. PS请记住,此类 webcomponents 可能会出现小延迟或异步,因此您必须在加载后调用adjustShadowRootStyles()

Or just wrap it with setTimeout , eg setTimeout(() => adjustShadowRootStyles(...), 60) .或者只是用setTimeout包装它,例如setTimeout(() => adjustShadowRootStyles(...), 60) Hacky but works. Hacky 但有效。

All the answers that suggest using new CSSStyleSheet() should be taken with a grain of salt, as of today (almost 2023, happy new year everyone,) Safari STILL does not support the stylesheet constructor, that means your code won't work on ANY iOS device as every browser you can have there is forced to use Apples Webkit engine (yes. even if you download an app that says "Chrome",), So if we use this (proper) approach to inject styles into a shadowRoot, it will fail on iOS devices.所有建议使用new CSSStyleSheet()的答案都应该持保留态度,截至今天(将近 2023 年,大家新年快乐,)Safari 仍然不支持样式表构造函数,这意味着您的代码将无法正常工作任何 iOS 设备,因为您可以拥有的每个浏览器都被迫使用 Apples Webkit 引擎(是的。即使您下载了一个显示“Chrome”的应用程序),所以如果我们使用这种(正确的)方法将 styles 注入 shadowRoot,它会在 iOS 设备上失败。 which is not acceptable for my standards.这对我的标准来说是不可接受的。 unfortunately...很遗憾...

https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet https://developer.mozilla.org/zh-CN/docs/Web/API/CSSStyleSheet

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM