译者注:本文中Element和Component在某些场合是通用的,本质上LitElement只是WebComponents的一种实现,所以元素也可以认为就是组件。
一、组件开发者如何定义组件样式
ShadowDOM API允许在自定义元素上创建封装好的DOM树,shadowDOM树的根节点是shadowRoot,shadowRoot所挂载的元素称为宿主元素(host element, or host)。
通常,LitElement会为每一个宿主元素创建shadowRoot,并将模板描述的DOM结构渲染到shadowRoot下。
ShadowDOM通过限制CSS的范围使得定义在shadowRoot内的样式只用于shadowRoot内的DOM元素,而不会泄露到外部DOM。除了CSS的继承属性外,shadowRoot隔离了其外部的样式,无论是位于主文档还是外部的shadowRoot。
在何处定义样式
有三处定义宿主元素和其shadowDOM样式的方式:
- LitElement类的静态
styles属性(推荐) render方法的HTML模板中插入<style>元素- 外部样式表,通过
<link rel="stylesheet" href="…">链接到LitElement模板中
LitElement类的静态styles属性
LitElement允许定义静态样式,将其应用于所有组件的实例上。
静态样式可以提高性能,它们只会被解析一次并在多个组件上复用。
如果需要定义每个组件实例的样式,需要用到CSS自定义属性(有兼容性风险,iOS9不支持):
- 组件开发者在shadowDOM内定义CSS变量
- 组件使用者在外部定义这些自定义CSS变量的值
定义静态styles:
import { LitElement, css, unsafeCSS } from 'lit-element';
class MyElement extends LitElement {
static get styles() {
const mainWidth = 800;
const padding = 20;
return css`
:host {
width: ${unsafeCSS(mainWidth + padding)}px;
}
`;
}
}
对于字面字符串(red),可以直接用${}; 变量需要用${unsafeCSS()}包裹。
⚠️unsafeCSS只可用在可信的输入,否则可能会有安全风险。
HTML模板中插入<style>元素
在模板中插入<style>可以对于每个组件实例进行定制:
import {LitElement, property} from 'lit-element';
class MyElement extends LitElement {
@property() mainColor = 'blue';
render() {
return html`
<style>
:host {
color: ${this.mainColor};
}
</style>
`;
}
}
⚠️ 不要在<style>内使用表达式,因为ShadyCSS polyfill的限制,表达式是不会每个实例更新的。
外部样式表
通过<link>标签加载外部样式表:
import {LitElement} from 'lit-element';
class MyElement extends LitElement {
render() {
return html`
<link rel="stylesheet" href="./styles.css">
`;
}
}
这一方式有两个要点需要强调下:
- 首次load时,会引起无样式内容闪烁(FOUC)
href的路径相对于主文档,对于公开的可复用组件不是很适用
为宿主元素(Host Element)和它的ShadowDOM编写CSS样式
为宿主元素编写样式
宿主元素可以通过:host和:host()CSS伪类为自己设定样式:
-
:host选取shadowRoot对应的宿主元素:host { display: block; color: blue; } -
:host(…)只有当括号内的选择器匹配宿主元素时才会生效:host(.important) { color: red; font-weight: bold; }
关于自定义元素的两条最佳实践:
-
给
:host加display样式(例如block或者inline-block),:host的默认display是inline,inline元素由它们的内容决定大小而不是通过CSS定义,它们也会导致block子元素溢出:host { display: block; } -
给
:host加hidden属性的样式:host([hidden]) { display: none; }
为ShadowDOM编写样式
对于shadowRoot内的元素,用标准CSS选择器就好了。
内部定义的CSS选择器,只会作用于shadowRoot内部,所以可以在内部为所欲为,它们不会匹配到外部的元素:
* {
color: black;
}
h1 {
font-size: 4rem;
}
#main {
padding: 16px;
}
.important {
color: red;
}
为插槽子元素(slotted children)编写样式
对于用<slot>插槽方式插入到shadowDOM的元素,可以用::slotted()伪元素选取:
::slotted(*)匹配所有的插槽元素::slotted(p)匹配所有插槽的p元素div ::slotted(*)匹配div内的所有插槽元素
import { LitElement, html } from 'lit-element';
class MyElement extends LitElement {
render() {
return html`
<style>
:host([hidden]) { display: none; }
:host { display: block; }
::slotted(*) { font-family: Roboto; }
::slotted(span) { color: blue; }
div ::slotted(*) { color: red; }
</style>
<slot></slot>
<div><slot name="hi"></slot></div>
`;
}
}
customElements.define('my-element', MyElement);
二、组件使用者如何定义组件样式
使用者在使用组件时,只需要在主文档内用自定义组件的tag作为selector即可,例如:
<style>
my-element {
font-family: Roboto;
font-size: 20;
color: blue;
}
</style>
...
<my-element></my-element>
这些外部定义的属性,作用于宿主元素上,优先级高于内部用:host定义的属性
三、主题
这一节描述的是如何使用CSS继承和自定义CSS属性,用于:
- 创建easily stylable LitElement组件
- 创建可以轻松用于导入LitElement的样式主题
CSS继承和shadowDOM
继承CSS属性(如color,font-family以及所有的CSS自定义属性(—*)可以穿过shadowRoot边界进行继承,这意味着通常情况下,元素可以从外部共享一些重要的样式。
组件的作者可以利用这一点在:hostCSS伪类上定义一些继承属性,它们会被用在shadowRoot内部的元素上:
my-element.js
render() {
return html`
<style>
:host {
display: block; //需要注意的是display不是继承属性
font-family: Roboto;
font-size: 20;
color: blue;
}
</style>
<p>Inherits font styles</p>
`;
}
我们也可以在外部主文档中定义继承属性改变:host中的属性,因为外部的元素类selector的优先级高于:host伪类selector,外部的样式会重载内部的样式:
index.html
<style>
/* Overrides the `color` property of the `:host` styles in my-element.js */
my-element {
color: blue;
}
</style>
...
<my-element></my-element>
自定义CSS属性
自定义CSS属性可以沿着DOM树继承下来,并且可以穿过shadowRoot的边界,可以利用这一点实现样式和主题定制。
my-element.js
<style>
:host {
display: block;
background-color: var(--myBackground, yellow);
}
</style>
Index.html
<style>
my-element {
--myBackground: rgb(67, 156, 144);
}
</style>
在外部定义的—myBackground自定义属性会用在内部my-element上
四、小结
- 对于组件的通用样式,即所有组件共享的样式,建议用静态
styles属性进行设置 - 对于组件的实例,推荐用HTML模板
<style>,通过${this.myColor}实现实例定制 ::host伪类用于设定宿主元素的样式,::slotted()用于设定插槽子元素的样式, shadowRoot内的样式直接用标准CSS选择器即可- 从外部透传属性有两种方式,CSS继承属性和CSS自定义属性,自定义属性更灵活,但有一定的兼容性问题