如何将纯 CSS 转换为 Web 组件并正确应用子选择器?
How to translate plain CSS into web component and correctly apply child selector?
我在 https://codepen.io/veksi/pen/xxGpXWM?editors=1010 的 Pen 中也有代码,但在此处复制以供后代使用。
我有一段代码可以在不使用网络组件的情况下使用 CSS(在这种情况下为 lit-html,但应该无关紧要)。但是,由于我努力将 "plain CSS" 版本转换为可与 Web 组件一起使用的版本,因此存在某种心理障碍。我认为问题出在 :host
和 ::slotted
选择器并使用它们,但令我困惑的是问题到底是什么。有人可以帮助并用正确的选择器指明方向吗?
代码如下:
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
.sidebar-test {
overflow: hidden;
}
.sidebar-test > * {
display: flex;
flex-wrap: wrap;
margin: calc(var(10px) / 2 * -1);
}
.sidebar-test > * > * {
margin: calc(var(10px) / 2);
flex-basis: 1;
flex-grow: 1;
}
.sidebar-test > * > :first-child {
flex-basis: 0;
flex-grow: 999;
min-width: calc(50% - 10px);
}
</style>
</head>
<body style="width: 95vw; border-style: solid;">
<div style="width: 80vw; height:40vh; border-style: dotted;">
<p>Testing sidebar without web compoment</p>
<div class="sidebar-test">
<form>
<input type="text" aria-label="Search" />
<button type="submit">Some longer search text to realign on small views</button>
</form>
</div>
<div class="sidebar-test">
<div>
<p style="background-color: yellow">Strawberries.</p>
<img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
</div>
</div>
</div>
<div style="width: 80vw; height:40vh; border-style: dotted;">
<p>Testing sidebar1...</p>
<test-sidebar1 space="10px" side="right" contentMin="55.0%" style="border-style: dashed;">
<form>
<input type="text" aria-label="Search" />
<button type="submit">Some longer search text to realign on small views</button>
</form>
</test-sidebar1>
<test-sidebar1 space="10px" side="left" contentMin="55.0%" style="border-style: dashed;">
<div>
<p style="background-color: yellow">Strawberries.</p>
<img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
</div>
</test-sidebar1>
</div>
<div style="width: 80vw; height:40vh; border-style: dotted;margin-top:2rem;">
<p>Testing sidebar2...</p>
<test-sidebar2 space="10px" side="right" contentMin="55.0%" style="border-style: dashed;">
<form>
<input type="text" aria-label="Search" />
<button type="submit">Some longer search text to realign on small views</button>
</form>
</test-sidebar2>
<test-sidebar2 space="10px" side="left" contentMin="55.0%" style="border-style: dashed;">
<div>
<p style="background-color: yellow">Strawberries.</p>
<img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
</div>
</test-sidebar2>
</div>
</body>
</html>
import { LitElement, css, html } from 'https://unpkg.com/lit-element?module';
/* This seem to work almost while version 2 does not at all.
The problem in this version is alignment. The 'Search' button
is not on the right side and the search field occupying the
free space accordingly. For some reason flex-basis and
flex-grow are not matched by the CSS selector...
Basically trying to recreate .sidebar-test as given in the document CSS, but wrapped into an element.
*/
export class Sidebar1 extends LitElement {
static get styles() {
return css`
:host {
overflow: hidden;
}
:host ::slotted(*) {
display: flex;
flex-wrap: wrap;
}
:host ::slotted(*) > ::slotted(*) {
flex-grow: 1;
}
`;
}
get sideStyle() {
return html`
<style>
:host ::slotted(*) { margin: calc(${this.space} / 2 * -1); ${this.noStretch ? 'align-items: flex-start;' : ''} }
:host ::slotted(*) > ::slotted(*) { margin: calc(${this.space} / 2); ${this.sideWidth ? `flex-basis: ${this.sideWidth};` : ''} }
:host ::slotted(*) > ${this.side !== 'left' ? `::slotted(*:first-child)` : `::slotted(*:last-child)`} {
flex-basis: 0;
flex-grow: 999;
min-width: calc(${this.contentMin} - ${this.space});
}
</style>
`;
}
static get properties() {
return {
side: { type: String },
sideWidth: { type: String },
contentMin: { type: String },
space: { type: String },
x: { type: String },
noStretch: { type: Boolean, reflect: true, attribute: true }
};
}
constructor() {
super();
this.side = "left";
this.contentMin = "50%";
if (this.children[0].children.length != 2) {
console.error(`Should have exactly two children..`);
}
}
render() {
if (!this.contentMin.includes('%')) {
console.warn('Minimum content should be in percentages to avoid overflows.');
}
return html`${this.sideStyle}<slot></slot>`;
}
}
customElements.define('test-sidebar1', Sidebar1);
/* Compared to Sidebar1 there's a problem with layout. As if sideStyle wouldn't be applied correctly.*/
export class Sidebar2 extends LitElement {
static get styles() {
return css`
:host {
--sidebarSpace: 1rem;
--sidebarContentMin: 50%;
--sidebarWidth: 30ch;
overflow: hidden;
}
:host > ::slotted(*) {
display: flex;
flex-wrap: wrap;
margin: calc(var(--sidebarSpace) / 2 * -1);
}
:host([noStretch]) {
:host > ::slotted(*) {
align-items: flex-start;
}
}
:host > ::slotted(*) > ::slotted(*) {
margin: calc(var(--sidebarSpace) / 2);
flex-grow: 1;
}
:host > ::slotted(*) > ::slotted(*:first-child) {
flex-basis: 0;
flex-grow: 999;
min-width: calc(var(--sidebarContentMin) - var(--sidebarSpace));
}
`;
}
get sideStyle() {
return html`
<style>
:host > ::slotted(*) > ::slotted(*) {
${this.sideWidth ? 'flex-basis: var(--sidebarWidth)' : ''}
}
:host > ::slotted(*) > ${this.side !== 'left' ? '::slotted(*:first-child)' : '::slotted(*:last-child)'} {
flex-basis: 0;
flex-grow: 999;
min-width: calc(var(--sidebarContentMin) - var(--sidebarSpace));
}
</style>
`;
}
static get properties() {
return {
side: { type: String },
sideWidth: { type: String },
contentMin: { type: String },
space: { type: String },
x: { type: String },
noStretch: { type: Boolean, reflect: true, attribute: true }
};
}
updated(changedProperties) {
if (changedProperties.has("space")) {
this.style.setProperty("--sidebarSpace", this.space);
} else if (changedProperties.has("contentMin")) {
this.style.setProperty("--sidebarContentMin", this.contentMin);
} else if (changedProperties.has("sideWidth")) {
this.style.setProperty("--sidebarWidth", this.sideWidth);
}
}
constructor() {
super();
this.side = "left";
this.contentMin = "50%";
if (this.children[0].children.length != 2) {
console.error(`Should have exactly two children..`);
}
}
render() {
if (!this.contentMin.includes('%')) {
console.warn('Minimum content should be in percentages to avoid overflows.');
}
return html`${this.sideStyle}<slot></slot>`;
}
}
customElements.define('test-sidebar2', Sidebar2);
问题似乎是阴影 DOM 中不支持复合选择器。可以在 https://github.com/Polymer/lit-element/issues/42.
进行有关 lit-html
的一些解决方案的相关讨论
如果您转到 https://lit-element.polymer-project.org/guide/styles#style-the-components-children 并向下阅读该页面,您会发现以下内容:
Note that only direct slotted children can be styled with ::slotted().
<my-element>
<div>Stylable with ::slotted()</div>
</my-element>
<my-element>
<div><p>Not stylable with ::slotted()</p></div>
</my-element>
所以我认为 :host > ::slotted(*) > ::slotted(*)
这样的选择器不会起作用。
我在 https://codepen.io/veksi/pen/xxGpXWM?editors=1010 的 Pen 中也有代码,但在此处复制以供后代使用。
我有一段代码可以在不使用网络组件的情况下使用 CSS(在这种情况下为 lit-html,但应该无关紧要)。但是,由于我努力将 "plain CSS" 版本转换为可与 Web 组件一起使用的版本,因此存在某种心理障碍。我认为问题出在 :host
和 ::slotted
选择器并使用它们,但令我困惑的是问题到底是什么。有人可以帮助并用正确的选择器指明方向吗?
代码如下:
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
.sidebar-test {
overflow: hidden;
}
.sidebar-test > * {
display: flex;
flex-wrap: wrap;
margin: calc(var(10px) / 2 * -1);
}
.sidebar-test > * > * {
margin: calc(var(10px) / 2);
flex-basis: 1;
flex-grow: 1;
}
.sidebar-test > * > :first-child {
flex-basis: 0;
flex-grow: 999;
min-width: calc(50% - 10px);
}
</style>
</head>
<body style="width: 95vw; border-style: solid;">
<div style="width: 80vw; height:40vh; border-style: dotted;">
<p>Testing sidebar without web compoment</p>
<div class="sidebar-test">
<form>
<input type="text" aria-label="Search" />
<button type="submit">Some longer search text to realign on small views</button>
</form>
</div>
<div class="sidebar-test">
<div>
<p style="background-color: yellow">Strawberries.</p>
<img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
</div>
</div>
</div>
<div style="width: 80vw; height:40vh; border-style: dotted;">
<p>Testing sidebar1...</p>
<test-sidebar1 space="10px" side="right" contentMin="55.0%" style="border-style: dashed;">
<form>
<input type="text" aria-label="Search" />
<button type="submit">Some longer search text to realign on small views</button>
</form>
</test-sidebar1>
<test-sidebar1 space="10px" side="left" contentMin="55.0%" style="border-style: dashed;">
<div>
<p style="background-color: yellow">Strawberries.</p>
<img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
</div>
</test-sidebar1>
</div>
<div style="width: 80vw; height:40vh; border-style: dotted;margin-top:2rem;">
<p>Testing sidebar2...</p>
<test-sidebar2 space="10px" side="right" contentMin="55.0%" style="border-style: dashed;">
<form>
<input type="text" aria-label="Search" />
<button type="submit">Some longer search text to realign on small views</button>
</form>
</test-sidebar2>
<test-sidebar2 space="10px" side="left" contentMin="55.0%" style="border-style: dashed;">
<div>
<p style="background-color: yellow">Strawberries.</p>
<img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
</div>
</test-sidebar2>
</div>
</body>
</html>
import { LitElement, css, html } from 'https://unpkg.com/lit-element?module';
/* This seem to work almost while version 2 does not at all.
The problem in this version is alignment. The 'Search' button
is not on the right side and the search field occupying the
free space accordingly. For some reason flex-basis and
flex-grow are not matched by the CSS selector...
Basically trying to recreate .sidebar-test as given in the document CSS, but wrapped into an element.
*/
export class Sidebar1 extends LitElement {
static get styles() {
return css`
:host {
overflow: hidden;
}
:host ::slotted(*) {
display: flex;
flex-wrap: wrap;
}
:host ::slotted(*) > ::slotted(*) {
flex-grow: 1;
}
`;
}
get sideStyle() {
return html`
<style>
:host ::slotted(*) { margin: calc(${this.space} / 2 * -1); ${this.noStretch ? 'align-items: flex-start;' : ''} }
:host ::slotted(*) > ::slotted(*) { margin: calc(${this.space} / 2); ${this.sideWidth ? `flex-basis: ${this.sideWidth};` : ''} }
:host ::slotted(*) > ${this.side !== 'left' ? `::slotted(*:first-child)` : `::slotted(*:last-child)`} {
flex-basis: 0;
flex-grow: 999;
min-width: calc(${this.contentMin} - ${this.space});
}
</style>
`;
}
static get properties() {
return {
side: { type: String },
sideWidth: { type: String },
contentMin: { type: String },
space: { type: String },
x: { type: String },
noStretch: { type: Boolean, reflect: true, attribute: true }
};
}
constructor() {
super();
this.side = "left";
this.contentMin = "50%";
if (this.children[0].children.length != 2) {
console.error(`Should have exactly two children..`);
}
}
render() {
if (!this.contentMin.includes('%')) {
console.warn('Minimum content should be in percentages to avoid overflows.');
}
return html`${this.sideStyle}<slot></slot>`;
}
}
customElements.define('test-sidebar1', Sidebar1);
/* Compared to Sidebar1 there's a problem with layout. As if sideStyle wouldn't be applied correctly.*/
export class Sidebar2 extends LitElement {
static get styles() {
return css`
:host {
--sidebarSpace: 1rem;
--sidebarContentMin: 50%;
--sidebarWidth: 30ch;
overflow: hidden;
}
:host > ::slotted(*) {
display: flex;
flex-wrap: wrap;
margin: calc(var(--sidebarSpace) / 2 * -1);
}
:host([noStretch]) {
:host > ::slotted(*) {
align-items: flex-start;
}
}
:host > ::slotted(*) > ::slotted(*) {
margin: calc(var(--sidebarSpace) / 2);
flex-grow: 1;
}
:host > ::slotted(*) > ::slotted(*:first-child) {
flex-basis: 0;
flex-grow: 999;
min-width: calc(var(--sidebarContentMin) - var(--sidebarSpace));
}
`;
}
get sideStyle() {
return html`
<style>
:host > ::slotted(*) > ::slotted(*) {
${this.sideWidth ? 'flex-basis: var(--sidebarWidth)' : ''}
}
:host > ::slotted(*) > ${this.side !== 'left' ? '::slotted(*:first-child)' : '::slotted(*:last-child)'} {
flex-basis: 0;
flex-grow: 999;
min-width: calc(var(--sidebarContentMin) - var(--sidebarSpace));
}
</style>
`;
}
static get properties() {
return {
side: { type: String },
sideWidth: { type: String },
contentMin: { type: String },
space: { type: String },
x: { type: String },
noStretch: { type: Boolean, reflect: true, attribute: true }
};
}
updated(changedProperties) {
if (changedProperties.has("space")) {
this.style.setProperty("--sidebarSpace", this.space);
} else if (changedProperties.has("contentMin")) {
this.style.setProperty("--sidebarContentMin", this.contentMin);
} else if (changedProperties.has("sideWidth")) {
this.style.setProperty("--sidebarWidth", this.sideWidth);
}
}
constructor() {
super();
this.side = "left";
this.contentMin = "50%";
if (this.children[0].children.length != 2) {
console.error(`Should have exactly two children..`);
}
}
render() {
if (!this.contentMin.includes('%')) {
console.warn('Minimum content should be in percentages to avoid overflows.');
}
return html`${this.sideStyle}<slot></slot>`;
}
}
customElements.define('test-sidebar2', Sidebar2);
问题似乎是阴影 DOM 中不支持复合选择器。可以在 https://github.com/Polymer/lit-element/issues/42.
进行有关lit-html
的一些解决方案的相关讨论
如果您转到 https://lit-element.polymer-project.org/guide/styles#style-the-components-children 并向下阅读该页面,您会发现以下内容:
Note that only direct slotted children can be styled with ::slotted().
<my-element>
<div>Stylable with ::slotted()</div>
</my-element>
<my-element>
<div><p>Not stylable with ::slotted()</p></div>
</my-element>
所以我认为 :host > ::slotted(*) > ::slotted(*)
这样的选择器不会起作用。