Web Components详解-Shadow DOM样式控制

本文最后更新于:5 个月前

前言

本文继续Web Components系列文章,介绍一下Shadow DOM的样式及选择器。

Shadow DOM的样式与外界是隔离的,即自定义元素的样式只会影响到Shadow DOM内部,不会影响到外部的页面元素,这点在之前有说到过。那么有什么办法可以在Shadow DOM中使用全局样式?样式选择器又有什么异同呢?请跟着本篇文章一起探究

:host伪类

作为伪类使用

:host一般在Shadow DOM中使用,代指宿主标签(自定义标签)使用它可以在Shadow DOM内部为自定义元素定义样式,host伪类的语法是::host { /* 样式规则 */ },例如

<body>
    <host-element></host-element>

    <script>
        class HostElement extends HTMLElement {
            constructor() {
                super()
                this.attachShadow({ mode: "open" })
                this.shadowRoot.textContent = "host-element"
                this.addStyle()
            }
            addStyle() {
                const styleEle = document.createElement("style");
                // 加点样式
                styleEle.textContent = `
            :host {
                font-size:20px;
                color:lightblue;
                border: 1px solid black;
            }`
                this.shadowRoot.appendChild(styleEle)
            }
        }
        customElements.define("host-element", HostElement)
    </script>
</body>

作为选择器使用

除了使用:host伪类外,:host还提供了selector的选择器使用方式,用于过滤某个条件下的宿主标签,比如我们选择属性为isHost的标签、class为isHost的标签以及id为isHost的标签并分别设置不同的样式进行区分

<body>
    <host-element></host-element>
    <host-element isHost></host-element>
    <host-element class="isHost"></host-element>
    <host-element id="isHost"></host-element>
    <script>
        class HostElement extends HTMLElement {
            constructor() {
                super()
                this.attachShadow({ mode: "open" })
                this.shadowRoot.textContent = "host-element"
                this.addStyle()
            }
            addStyle() {
                const styleEle = document.createElement("style");
                // 加点样式
                styleEle.textContent = `
            :host {
                font-size:20px;
                color:lightcoral;
                border: 1px solid black;
            }
            :host([isHost]) {
                background:lightgreen;
            }
            :host(.isHost) {
                background:lightblue;
            }
            :host(#isHost) {
                background:green;
            }`
                this.shadowRoot.appendChild(styleEle)
            }
        }
        customElements.define("host-element", HostElement)
    </script>
</body>

效果如下

::part伪元素

::part伪元素选择器是Shadow DOM中的选择器之一,它可以为自定义元素内部的特定部分(一般是子元素)定义样式,结合上面的伪类选择器host我们可以使用它选择当前宿主标签下的某个部分的子元素,并设置该部分独有的样式,思考下面的代码:

<body>
    <part-element></part-element>
    <script>
        class PartElement extends HTMLElement {
            constructor() {
                super()
                this.attachShadow({ mode: "open" })
                this.addStyle()
                this.addTreeElems()
            }
            addTreeElems() {
                this.shadowRoot.innerHTML += `
                    <div part="part1 part3">part1</div>
                    <div part="part2">part2</div>
                    <div part="part3">part3</div>`
            }
            addStyle() {
                const styleEle = document.createElement("style");
                // 加点样式
                styleEle.textContent = `
            :host {
                font-size:20px;
                display:flex;
                justify-content: space-evenly;
                border: 1px solid black;
            }
            :host::part(part1) {
                color: lightcoral;
            }
            :host::part(part2) {
                color: lightblue;
            }
            :host::part(part3) {
                background: lightgreen;
            }`
                this.shadowRoot.appendChild(styleEle)
            }
        }
        customElements.define("part-element", PartElement)
    </script>
</body>

我们将div分成3个part(tips:part用法和class相似,一个part属性可以设置多个值,使用空格隔开),分别为三者设置不同的样式,在css中同样针对不同part设置了不同的样式,效果如下

::slotted伪元素

使用::slotted可以对Shadow DOM中的绑定了插槽的元素设置样式,并且它只能给外层元素设置样式,无法选择到子元素,参考下面的代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ShadowDOM</title>
</head>

<body>
    <slotted-element>
        <header slot="header">header</header>
        <main class="main" slot="content">
            <span>content</span>
        </main>
        <footer slot="footer">footer</footer>
    </slotted-element>
    <div id="slots">
        <slot name="header"></slot>
        <slot name="content"></slot>
        <slot name="footer"></slot>
    </div>
    <script>
        class SlottedElement extends HTMLElement {
            constructor() {
                super()
                this.attachShadow({ mode: "open" })
                this.addStyle()
                this.shadowRoot.appendChild(slots)
            }
            addStyle() {
                const styleEle = document.createElement("style");
                // 加点样式
                styleEle.textContent = `
                #slots{
                    /* 根标签 */
                    background: lightblue;
                }
                ::slotted(header) {
                    /* 标签选择器 */
                    background: lightcoral;
                }
                ::slotted(.main) {
                    /* 类选择器 */
                    color: lightcoral;
                }
                ::slotted(.main) span{
                    /* 无效的选择器,只能选择插槽元素,选不到子元素 */
                    color: red;
                }
                ::slotted([slot="footer"]) {
                    /* 属性选择器 */
                    background: lightcoral;
                    color: lightseagreen;
                }
                `
                this.shadowRoot.appendChild(styleEle)
            }
        }
        customElements.define("slotted-element", SlottedElement)
    </script>
</body>

</html>

效果如下:

:host-context伪类

host-context表示宿主外部祖先节点满足某个选择器时生效对应样式,比如下面代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ShadowDOM</title>
</head>

<body>
    <div>
        <host-element></host-element>
    </div>
    <div isHost>
        <host-element></host-element>
    </div>
    <div class="isHost">
        <host-element></host-element>
    </div>
    <div id="isHost">
        <host-element></host-element>
    </div>

    <script>
        class HostElement extends HTMLElement {
            constructor() {
                super()
                this.attachShadow({ mode: "open" })
                this.shadowRoot.textContent = "host-element"
                this.addStyle()
            }
            addStyle() {
                const styleEle = document.createElement("style");
                // 加点样式
                styleEle.textContent = `
            :host-context(div) {
                font-size:20px;
                color:lightcoral;
                border: 1px solid black;
            }
            :host-context([isHost]) {
                background:lightgreen;
            }
            :host-context(.isHost) {
                background:lightblue;
            }
            :host-context(#isHost) {
                background:green;
            }`
                this.shadowRoot.appendChild(styleEle)
            }
        }
        customElements.define("host-element", HostElement)
    </script>
</body>

</html>

上面的代码中,我们分别对div标签,isHost属性,类名为isHost,id为isHost这四类选择器生效时其子标签展示出不同的的样式效果做了一个示例,效果如下

样式变量

在影子DOM中使用全局样式可以通过CSS的样式变量的形式来实现,在CSS样式变量的文章中,我们探讨了CSS样式var关键字的用法,我们可以在全局定义变量,然后在阴影DOM中使用对应变量的方式实现全局样式的引用:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ShadowDOM</title>
    <style>
        /* 可以在shadowDom中使用 */
        [theme="theme1"] {
            --primary-color: lightblue;
            --primary-font-size: 20px;
        }

        /* 只能作用于自定义标签上 */
        variable-element {
            background: lightcoral;
        }
    </style>
</head>

<body theme="theme1">
    <div>
        <variable-element></variable-element>
    </div>
    <script>
        class VariableElement extends HTMLElement {
            constructor() {
                super()
                this.attachShadow({ mode: "open" })
                this.shadowRoot.textContent = "variable-element"
                this.addStyle()
                this.addElem()
            }
            addElem() {
                const divEle = document.createElement("div")
                divEle.textContent = "divEle"
                this.shadowRoot.appendChild(divEle)
            }
            addStyle() {
                const styleEle = document.createElement("style");
                // 加点样式
                styleEle.textContent = `
            :host {
                color: var(--primary-color);
                font-size: var(--primary-font-size);
            }
            `
                this.shadowRoot.appendChild(styleEle)
            }
        }
        customElements.define("variable-element", VariableElement)
    </script>
</body>

</html>

总结

本文介绍了在Web Components中使用Shadow DOM的样式及选择器的相关知识点,其中:host伪类用于在Shadow DOM内部为自定义元素定义样式;::part伪元素选择器可以为Shadow DOM内部的特定部分(一般是子元素)定义样式;::slotted伪元素选择器用于选择插槽中的元素;:host-context(selector)则是用于选择满足特定选择器条件的宿主外部祖先节点,并为宿主标签设置样式;最后是全局样式的使用途径:CSS变量。

以上就是文章的全部内容了,如果觉得文章不错的话,还望三连支持一下!谢谢~

相关代码:

myCode: 基于js的一些小案例或者项目 - Gitee.com

参考文章:

给 Shadow DOM 添加样式