如何正确使用 vue js Web 组件内部的插槽并应用样式
Posted
技术标签:
【中文标题】如何正确使用 vue js Web 组件内部的插槽并应用样式【英文标题】:How to properly use slot inside of vue js web component and apply styles 【发布时间】:2019-08-05 18:54:45 【问题描述】:我遇到了一个问题,即 Web 组件中的插槽实现未按预期运行。我对 Web 组件、自定义元素和插槽的理解是,插槽中呈现的元素应该从文档继承它们的样式,而不是 Shadow DOM,但是插槽中的元素实际上是被添加到 Shadow DOM因此忽略全局样式。我创建了以下示例来说明我遇到的问题。
共享用户界面
这是一个使用 cli (--target wc --name shared-ui ./src/components/*.vue
) 编译成 web 组件的 Vue 应用程序
<template>
<div :class="[$style.collapsableComponent]">
<div :class="[$style.collapsableHeader]" @click="onHeaderClick" :title="title">
<span> title </span>
</div>
<div :class="[$style.collapsableBody]" v-if="expanded">
<slot name="body-content"></slot>
</div>
</div>
</template>
<script lang="ts">
import Vue, Component, Prop from 'vue-property-decorator'
@Component()
export default class CollapsableComponent extends Vue
@Prop( default: "" )
title!: string;
@Prop(default: false)
startExpanded!: boolean;
private expanded: boolean = false;
constructor()
super();
this.expanded = this.startExpanded;
get isVisible(): boolean
return this.expanded;
onHeaderClick(): void
this.toggle();
public toggle(expand?: boolean): void
if(expand === undefined)
this.expanded = !this.expanded;
else
this.expanded = expand;
this.$emit(this.expanded? 'expand' : 'collapse');
public expand()
this.expanded = true;
public collapse()
this.expanded = false;
</script>
<style module>
:host
display: block;
.collapsableComponent
background-color: white;
.collapsableHeader
border: 1px solid grey;
background: grey;
height: 35px;
color: black;
border-radius: 15px 15px 0 0;
text-align: left;
font-weight: bold;
line-height: 35px;
font-size: 0.9rem;
padding-left: 1em;
.collapsableBody
border: 1px solid black;
border-top: 0;
border-radius: 0 0 10px 10px;
padding: 1em;
</style>
shared-ui-consumer
这是一个 vue 应用程序,它使用标准脚本包含文件导入 shared-ui web 组件。
应用程序.vue<template>
<div id="app">
<shared-ui title="Test">
<span class="testClass" slot="body-content">
Here is some text
</span>
</shared-ui>
</div>
</template>
<script lang="ts">
import 'vue'
import Component, Vue from 'vue-property-decorator';
@Component( )
export default class App extends Vue
</script>
<style lang="scss">
#app
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
.testClass
color: red;
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;
new Vue(
render: h => h(App),
).$mount('#app');
在此示例中,我希望容器内的内容具有红色文本,但是因为 Vue 将元素克隆到 Shadow DOM 中,所以 .testClass 样式被忽略并且文本以黑色填充呈现。
如何将 .testClass 应用于我的 Web 组件内的元素?
【问题讨论】:
我对 Web 组件、自定义元素和插槽的理解是,插槽中呈现的元素应该从文档而不是 Shadow DOM 继承其样式我认为你错了,无论是否开槽,内容都在 shadowDOM 中。如果您可以使用全局 CSS 为 SLOT 设置样式,那么 shadowDOM 的概念就消失了。 我不认为你是正确的。在jsfiddle built using standard web components 上查看以下示例。在示例中,如果您在开发工具中打开 DOM 资源管理器,您将看到插槽中的项目位于 shadow DOM 之外,并且被引用/链接到位于 shadow DOM 之外的元素。该插槽应该创建一个单独的 DOM 树并允许您将它们一起显示,请参阅here。 这是一张显示element living outside the shadow dom的图片。 您可以设置开槽 content 的样式,但不能设置全局 CSS 中的槽。这是一个操场/小提琴:jsfiddle.net/dannye/L8b0txgo 是的,这就是我遇到的问题。没有应用开槽内容的样式,因为它们实际上没有开槽而是呈现在阴影根中 【参考方案1】:好的,所以我设法找到了一种解决方法,它使用本机插槽并将子组件正确呈现在 DOM 中的正确位置。
在挂载的事件中连接下一个刻度,用新的插槽替换插槽容器的 innerhtml。您可以花哨并为命名插槽做一些很酷的替换等等,但这应该足以说明解决方法。
共享用户界面
这是一个使用 cli (--target wc --name shared-ui ./src/components/*.vue
) 编译成 web 组件的 Vue 应用程序
<template>
<div :class="[$style.collapsableComponent]">
<div :class="[$style.collapsableHeader]" @click="onHeaderClick" :title="title">
<span> title </span>
</div>
<div ref="slotContainer" :class="[$style.collapsableBody]" v-if="expanded">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import Vue, Component, Prop from 'vue-property-decorator'
@Component()
export default class CollapsableComponent extends Vue
@Prop( default: "" )
title!: string;
@Prop(default: false)
startExpanded!: boolean;
private expanded: boolean = false;
constructor()
super();
this.expanded = this.startExpanded;
get isVisible(): boolean
return this.expanded;
onHeaderClick(): void
this.toggle();
//This is where the magic is wired up
mounted(): void
this.$nextTick().then(this.fixSlot.bind(this));
// This is where the magic happens
fixSlot(): void
// remove all the innerHTML that vue has place where the slot should be
this.$refs.slotContainer.innerHTML = '';
// replace it with a new slot, if you are using named slot you can just add attributes to the slot
this.$refs.slotContainer.append(document.createElement('slot'));
public toggle(expand?: boolean): void
if(expand === undefined)
this.expanded = !this.expanded;
else
this.expanded = expand;
this.$emit(this.expanded? 'expand' : 'collapse');
public expand()
this.expanded = true;
public collapse()
this.expanded = false;
</script>
<style module>
:host
display: block;
.collapsableComponent
background-color: white;
.collapsableHeader
border: 1px solid grey;
background: grey;
height: 35px;
color: black;
border-radius: 15px 15px 0 0;
text-align: left;
font-weight: bold;
line-height: 35px;
font-size: 0.9rem;
padding-left: 1em;
.collapsableBody
border: 1px solid black;
border-top: 0;
border-radius: 0 0 10px 10px;
padding: 1em;
</style>
shared-ui-consumer
这是一个 vue 应用程序,它使用标准脚本包含文件导入 shared-ui Web 组件。
应用程序.vue<template>
<div id="app">
<shared-ui title="Test">
<span class="testClass" slot="body-content">
Here is some text
</span>
</shared-ui>
</div>
</template>
<script lang="ts">
import 'vue'
import Component, Vue from 'vue-property-decorator';
@Component( )
export default class App extends Vue
</script>
<style lang="scss">
#app
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
.testClass
color: red;
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;
new Vue(
render: h => h(App),
).$mount('#app');
【讨论】:
以上是关于如何正确使用 vue js Web 组件内部的插槽并应用样式的主要内容,如果未能解决你的问题,请参考以下文章