在 Web 组件中覆盖外部定义的样式
Overriding externally-defined styles in a web component
我正在 web components 迈出我的第一步,而不使用任何第三方库,例如 Polymer。主要卖点之一是 Web 组件样式与其他地方定义的样式分开,允许组件的影子-DOM 在类似沙盒的环境中设置样式。
我 运行 关注的问题是样式如何通过开槽元素级联。由于开槽元素不是影子 DOM 的一部分,它们只能在组件模板中使用 ::slotted()
选择器作为目标。这很好,但它几乎不可能保证 Web 组件在所有上下文中都能正确显示,因为外部定义的样式也以不可战胜的特异性*应用于开槽元素。
*除了!important
.
这个问题可以归结为:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
}
);
a {
color: red; /* >:( */
}
<template id="my-nav">
<style>
.links-container ::slotted(a) {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
<slot name="links"></slot>
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#" slot="links">Link 1</a>
<a href="#" slot="links">Link 2</a>
<a href="#" slot="links">Link 3</a>
</my-nav>
我很难理解这个 "feature" 的价值。我要么必须以其他格式指定我的链接并使用 JS 创建它们的节点,要么将 !important
添加到我的颜色 属性 - still 不能保证关于字面上任何其他 属性 我尚未定义的一致性。
这个问题是否已在某处得到解决,或者通过更改我的灯 DOM 结构是否可以轻松解决?我不确定还有什么方法可以将链接列表放入插槽中。
你说得对,除了对每个 CSS 属性.
使用 !important
之外没有其他解决方案
相反,我不会使用 <slot>
并复制您需要的节点:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var links = this.querySelectorAll( 'a[slot]' )
var container = this.shadowRoot.querySelector( '.links-container' )
links.forEach( l => container.appendChild( l ) )
}
}
);
a {
color: red; /* >:( */
}
<template id="my-nav">
<style>
.links-container > a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#" slot="links">Link 1</a>
<a href="#" slot="links">Link 2</a>
<a href="#" slot="links">Link 3</a>
</my-nav>
<slot>
是有意设计的,允许外部代码对放置在其中的内容进行样式设置。如果使用得当,这是一个很棒的功能。
但是如果您想更好地控制 Web 组件中显示的内容,则需要将内容的克隆副本从 this.childNodes
复制到影子 DOM 中。那么您就可以 100% 控制 CSS.
好的。您实际上只有 90% 的控制权,因为使用您的组件的人仍然可以设置 style
属性。
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
正如您在上面的示例中看到的那样,第三个 link 仍然是红色,因为我们设置了 style
属性。
如果你想防止这种情况发生,那么你需要从内部内容中删除 style
属性。
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
container.querySelectorAll('[style]').forEach(el => el.removeAttribute('style'));
}
}
}
);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
我什至创建了一些允许我读入并转换为自定义内部节点的唯一 children 组件。
想想 <video>
标签及其 <source>
children。那些 children 并没有真正渲染任何东西,它们只是一种保存数据的方式,用于指示要播放的视频的源位置。
这里的关键是理解 <slot>
应该用于什么,并且只以这种方式使用它,而不是试图强迫它做一些它从未打算做的事情。
奖励积分
由于每次将此节点放入 DOM 时都会调用 ConnectedCallback
,因此每次都必须小心删除阴影 DOM 内的任何内容,否则会复制 children一遍又一遍。
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
function reInsert() {
var el = document.querySelector('my-nav');
var parent = el.parentNode;
el.remove();
parent.appendChild(el);
}
setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
因此删除重复的节点很重要:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
function reInsert() {
var el = document.querySelector('my-nav');
var parent = el.parentNode;
el.remove();
parent.appendChild(el);
}
setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
我正在 web components 迈出我的第一步,而不使用任何第三方库,例如 Polymer。主要卖点之一是 Web 组件样式与其他地方定义的样式分开,允许组件的影子-DOM 在类似沙盒的环境中设置样式。
我 运行 关注的问题是样式如何通过开槽元素级联。由于开槽元素不是影子 DOM 的一部分,它们只能在组件模板中使用 ::slotted()
选择器作为目标。这很好,但它几乎不可能保证 Web 组件在所有上下文中都能正确显示,因为外部定义的样式也以不可战胜的特异性*应用于开槽元素。
*除了!important
.
这个问题可以归结为:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
}
);
a {
color: red; /* >:( */
}
<template id="my-nav">
<style>
.links-container ::slotted(a) {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
<slot name="links"></slot>
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#" slot="links">Link 1</a>
<a href="#" slot="links">Link 2</a>
<a href="#" slot="links">Link 3</a>
</my-nav>
我很难理解这个 "feature" 的价值。我要么必须以其他格式指定我的链接并使用 JS 创建它们的节点,要么将 !important
添加到我的颜色 属性 - still 不能保证关于字面上任何其他 属性 我尚未定义的一致性。
这个问题是否已在某处得到解决,或者通过更改我的灯 DOM 结构是否可以轻松解决?我不确定还有什么方法可以将链接列表放入插槽中。
你说得对,除了对每个 CSS 属性.
使用!important
之外没有其他解决方案
相反,我不会使用 <slot>
并复制您需要的节点:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var links = this.querySelectorAll( 'a[slot]' )
var container = this.shadowRoot.querySelector( '.links-container' )
links.forEach( l => container.appendChild( l ) )
}
}
);
a {
color: red; /* >:( */
}
<template id="my-nav">
<style>
.links-container > a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#" slot="links">Link 1</a>
<a href="#" slot="links">Link 2</a>
<a href="#" slot="links">Link 3</a>
</my-nav>
<slot>
是有意设计的,允许外部代码对放置在其中的内容进行样式设置。如果使用得当,这是一个很棒的功能。
但是如果您想更好地控制 Web 组件中显示的内容,则需要将内容的克隆副本从 this.childNodes
复制到影子 DOM 中。那么您就可以 100% 控制 CSS.
好的。您实际上只有 90% 的控制权,因为使用您的组件的人仍然可以设置 style
属性。
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
正如您在上面的示例中看到的那样,第三个 link 仍然是红色,因为我们设置了 style
属性。
如果你想防止这种情况发生,那么你需要从内部内容中删除 style
属性。
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
container.querySelectorAll('[style]').forEach(el => el.removeAttribute('style'));
}
}
}
);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
我什至创建了一些允许我读入并转换为自定义内部节点的唯一 children 组件。
想想 <video>
标签及其 <source>
children。那些 children 并没有真正渲染任何东西,它们只是一种保存数据的方式,用于指示要播放的视频的源位置。
这里的关键是理解 <slot>
应该用于什么,并且只以这种方式使用它,而不是试图强迫它做一些它从未打算做的事情。
奖励积分
由于每次将此节点放入 DOM 时都会调用 ConnectedCallback
,因此每次都必须小心删除阴影 DOM 内的任何内容,否则会复制 children一遍又一遍。
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
function reInsert() {
var el = document.querySelector('my-nav');
var parent = el.parentNode;
el.remove();
parent.appendChild(el);
}
setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>
因此删除重复的节点很重要:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
function reInsert() {
var el = document.querySelector('my-nav');
var parent = el.parentNode;
el.remove();
parent.appendChild(el);
}
setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#" style="color: red">Link 3</a>
</my-nav>