admin管理员组文章数量:1355748
I'm taking my first steps into web ponents without using any third-party libraries, such as Polymer. One of the main selling points is that web ponent styles are separated from styles defined elsewhere, allowing the ponent's shadow-DOM to be styled in a sandbox-like environment.
The issue I'm running into is how styles cascade through slotted elements. Since slotted elements are not part of the shadow DOM, they can only be targed with the ::slotted()
selector within the ponent template. This is great, but it makes it almost impossible to guarantee a web ponent will display correctly in all contexts, since externally-defined styles also apply with undefeatable specificity* to slotted elements.
*besides !important
.
This issue can be distilled down to this:
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>
I'm taking my first steps into web ponents without using any third-party libraries, such as Polymer. One of the main selling points is that web ponent styles are separated from styles defined elsewhere, allowing the ponent's shadow-DOM to be styled in a sandbox-like environment.
The issue I'm running into is how styles cascade through slotted elements. Since slotted elements are not part of the shadow DOM, they can only be targed with the ::slotted()
selector within the ponent template. This is great, but it makes it almost impossible to guarantee a web ponent will display correctly in all contexts, since externally-defined styles also apply with undefeatable specificity* to slotted elements.
*besides !important
.
This issue can be distilled down to this:
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>
I'm having a hard time understanding the value of this "feature". I either have to specify my links in some other format and create their nodes with JS, or add !important
to my color property - which still doesn't guarantee consistency when it es to literally any other property I haven't defined.
Has this issue been addressed somewhere, or is this easily solved by changing my light DOM structure? I am not sure how else to get a list of links into a slot.
Share Improve this question edited Apr 24, 2018 at 14:51 Intervalia 11k2 gold badges34 silver badges70 bronze badges asked Apr 23, 2018 at 15:56 ScottScott 5,3795 gold badges48 silver badges73 bronze badges2 Answers
Reset to default 4The <slot>
is intentionally designed to allow the outer code to style the content placed into it. This is a great feature when used correctly.
But if you want better control of what shows in the web ponent then you need to copy cloned copies of the content from this.childNodes
into the shadow DOM. Then you have 100% control over the CSS.
OK. You really only have 90% control because the person using your ponent can still set the style
attribute.
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>
As you can see in the example above the third link is still red because we set the style
attribute.
If you want to prevent that from happening then you would need to strip the style
attribute from the inner content.
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>
I have even created some ponents that allow unique children that I read in and convert into custom internal nodes.
Think of the <video>
tag and its <source>
children. Those children don't really render anything, they are just a way of holding data that is used to indicate the source location of the video to be played.
The key here is to understand what <slot>
is supposed to be used for and only use it that way without trying to force it to do something it was never intended to do.
BONUS POINTS
Since ConnectedCallback
is called every time this node in placed into the DOM you have to be careful to remove anything within the shadow DOM each time or you will duplicate the children over and over.
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>
So removing the duplicated nodes is 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));
}
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>
You're right, there's no solution other that using !important
for every CSS property.
Instead, I would not use <slot>
and copy the nodes you need:
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>
本文标签: javascriptOverriding externallydefined styles in a web componentStack Overflow
版权声明:本文标题:javascript - Overriding externally-defined styles in a web component - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744009221a2575255.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论