我相信 Web 组件插槽的原则之一是它们旨在模仿标准 html 期望。它们允许我们将元素投影到组件中,就像开发人员已经习惯使用其他元素(例如
select
的 option
元素和 ul
的 li
元素)一样。
然而,我看到的一个关键区别是默认情况下插槽似乎是强制性的 - 插槽元素始终出现在消费客户端浏览器中。当它或父级被设置样式时,这会导致问题,但当没有插槽时不应该出现问题,因为样式始终保留。
以我们有一个卡片 Web 组件的场景为例,该组件旨在将可选的标头部分实现为插槽。由于此标题部分需要不同的背景颜色,因此插槽的父级已应用相关样式。
我已经设法使标头可选,其中进行检查以计算插槽分配的节点的数量,但是为了满足与“标准”html元素的奇偶校验,我希望它在被CSS隐藏时也能做出反应。
我制作了一个 jsfiddle 示例来演示我的意思:https://jsfiddle.net/ajbrun/obLxdc9t/
您可以看到第一部分,我已成功根据标题槽的存在使标题部分显示或隐藏。然而,在其下面,我有一个类似的演示,该演示无法成功显示基于父类设置的可见性的标题。在每种情况下,左侧都有一个带有类似代码的标准 HTML 表示。
我想要实现的目标是可能的,还是 Web 组件的消费者的“期望”,他们应该完全删除 slot 元素以使其可选?
.cards {
display: flex;
flex-direction: row;
gap: 10px;
}
.html-card,
.wc-card {
width: 50%;
}
.card {
border: 1px solid lightgray;
padding: 10px;
}
.card .header {
background: grey;
padding: 10px;
}
<script type="module">
import {LitElement, html, css, styleMap} from 'https://cdn.jsdelivr.net/gh/lit/[email protected]/all/lit-all.min.js';
class CardComponent extends LitElement {
static get styles() {
return [css`
:host {
display:block;
}
.card {
border: 1px solid lightgray;
padding: 10px;
}
.card .header {
background: grey;
padding: 10px;
}
`];
}
static properties = {
showHeader: {type: Boolean},
};
render() {
const headerSlotStyles = { display: this.showHeader ? '' : 'none' };
return html`
<div class="card">
<div class="header" style=${styleMap(headerSlotStyles)}>
<slot name="header" @slotchange="${this.handleSlotChange}"></slot>
</div>
<slot></slot>
</div>
`;
}
handleSlotChange() {
console.log('slot change');
const headerSlot = this.shadowRoot.querySelector('slot[name=header]');
this.showHeader = headerSlot.assignedNodes().length > 0;
}
}
window.customElements.define('demo-card', CardComponent);
</script>
<script type="text/javascript">
let htmlHeaderElemCopy, wcHeaderElemCopy;
function toggleHeaderElement() {
const htmlHeaderElem = document.querySelector('.slot-exists .header');
const wcHeaderElem = document.querySelector('.slot-exists [slot=header]');
if (!htmlHeaderElem) {
document.querySelector('.slot-exists .card').prepend(htmlHeaderElemCopy);
document.querySelector('.slot-exists .wc-card demo-card').prepend(wcHeaderElemCopy);
} else {
htmlHeaderElemCopy = htmlHeaderElem.cloneNode(true);
wcHeaderElemCopy = wcHeaderElem.cloneNode(true);
htmlHeaderElem.remove();
wcHeaderElem.remove();
}
}
function toggleParentClass() {
const hasClass = document.querySelector('.parent-class.no-header');
if (!hasClass) {
document.querySelector('.parent-class').classList.add('no-header');
} else {
hasClass.classList.remove('no-header');
}
}
</script>
<style>
.parent-class.no-header .header,
.parent-class.no-header [slot=header] {
display: none;
}
</style>
<h1>
Slot exists
<button onclick="toggleHeaderElement()">
Toggle header
</button>
</h1>
<div class="cards slot-exists">
<div class="html-card">
<h2>
Html card
</h2>
<div class="card">
<div class="header">
Card header
</div>
Content...
</div>
</div>
<div class="wc-card">
<h2>
Web component card ✔
</h2>
<demo-card>
<div slot="header">
Card header
</div>
Content...
</demo-card>
</div>
</div>
<h1>
Parent class toggled
<button onclick="toggleParentClass()">
Toggle header
</button>
</h1>
<div class="cards parent-class">
<div class="html-card">
<h2>
Html card
</h2>
<div class="card">
<div class="header">
Card header
</div>
Content...
</div>
</div>
<div class="wc-card">
<h2>
Web component card ✗
</h2>
<demo-card>
<div slot="header">
Card header
</div>
Content...
</demo-card>
</div>
</div>
就像我在评论中所说,你有(作为最小的例子):
<div style="padding:10px"><slot></slot></div>
你总会看到那个填充:
<style id="STYLE">
div {
padding: 5px;
background: hotpink;
font-size: 10px;
}
</style>
<div id="ONE">ONE</div><hr>
<div id="TWO"></div><hr>
<wc-number id="THREE"><div>THREE</div></wc-number><hr>
<wc-number id="FOUR">FOUR</wc-number><hr>
<wc-number id="FIVE"></wc-number>
<script>
customElements.define("wc-number", class extends HTMLElement {
constructor() {
super().attachShadow({
mode: "open"
}).innerHTML = STYLE.outerHTML + `<div><slot></slot></div>`;
}
})
</script>