微信小程序自定义组件没法受外部flex影响
在
微信小程序
场景中,自定义组件
内容无法接受外部flex布局影响,包括外部设置的 align-items、justify-content 等属性,以及自身设置的flex:1
等flex相关的属性,均无法生效。(注:自定义组件已提前设置 styleIsolation: 'apply-shared' 使组件能受外部样式影响)
通过此文,你能了解到:
- 微信小程序自定义组件无法受外部flex影响的原因;
- 微信小程序自定义组件设置组件根节点样式的方法;
- css样式中
display: contents
详解。
问题描述:
在一个应用场景中,需要给一个元素设置了flex布局,称它为父元素节点,显示如下图一和图二中的灰色区域,它是页面里的第一个view节点(见图三);
子元素则是图一中的红色区域,也就是图三页面节点树里的自定义组件layout-child
。
在此flex布局中,我们给子元素又设置了 flex:1
样式后,子元素应该会填充满父元素内剩余空间。
也就是如下图一所示,自定义组件(红色区域)应该根据外部父元素(灰色区域)内部的剩余宽度来自动撑开宽度。红色区域应该填充满(除去8像素的内填充区域)灰色区域的内部才对,但是实际上红色区域的宽度为0,也就是如下图二所示,样式 flex:1
未生效。
显示效果如下
图一:
图二:
页面wxml节点树(图三):
相关代码如下:
<!-- 页面wxml内容 -->
<view class="w-100p flex bg-cccccc">
<layout-child flex1>
<view class="bg-red w-100p h-100"></view>
</layout-child>
</view>
<!-- 组件layout-child wxml内容 -->
<view class="inline-flex flex-1">
<slot />
</view>
<!-- 全局样式 wxss -->
.w-100p { width: 100%; }
.flex { display: flex; }
.h-100 { height: 100rpx; }
.pt-8 { padding-top: 8rpx; }
.pb-8 { padding-bottom: 8rpx; }
.pl-8 { padding-left: 8rpx; }
.pr-8 { padding-right: 8rpx; }
.bg-cccccc { background: #ccc; }
.border-box { box-sizing: border-box; }
.bg-red { background: red; }
.inline-flex { display: inline-flex; }
.flex-1 { flex: 1; }
相关原理:
关于flex布局,首先我们需要知道的是,一个元素被设置了flex布局,那么默认情况下,其内部的子元素就会被赋予成内联flex块元素,即被隐性设置成了 display: inline-flex
。
在这种情况下,这个元素所被添加的各种flex布局属性,都能控制其自身以及其内部子元素的排列方式,同时能通过给子元素添加 flex: 1
等之类的样式控制子元素占据父元素的剩余空间。
但需要注意的是,该元素设置的flex布局无法正常作用到孙级元素,也就是没法影响到其子元素的子元素上面,它只能传递一层,没法向下传递两层。
问题分析:
根据上面的原理,我们可以推测,在小程序自定义组件的渲染机制中,是不是独立拥有一层自己的页面结构节点,导致我们在组件外面设置的flex布局无法影响到组件内?
而如图三所示,我们可以看到,我们自定义的组件在wxml节点树上面确实额外存在一层dom结构,它是我们的自定义组件节点。
由此可以得知,小程序自定义组件默认是独自占用了一层页面节点的。
就是这一层自定义组件自带的节点阻碍了属性继承,导致我们小程序自定义组件内部的结构(也就是上面图三中绿色框选的 <view class="inline-flex flex-1">
部分)未能将宽度拉伸满父元素内部剩余宽度。
所以我们即使在外部父元素设置了flex布局属性,自定义组件内的 flex:1 也无法正常生效,无法完成预期的效果。
那么,我们需要探寻的就是怎么消除小程序自定义组件这一层dom结构的影响?
解决方法:
1、将小程序自定义组件自带的节点虚拟化
在小程序自定义组件中,其options里面存在一个属性virtualHost,可以控制将该组件的节点虚拟化,即设置 virtualHost: true ,这样就能将该自定义组件默认的一层节点去除,使外部设置的flex布局能直接作用于该组件内部的节点。
<!-- 组件layout-child js内容 -->
Component({
options: {
styleIsolation: 'apply-shared', // 使该组件能受外部样式影响
virtualHost: true, // 虚拟化组件节点
...
},
...
});
如下图所示,在小程序控制台的wxml显示界面,该自定义组件 layout-child 的节点直接被移除了。
(注: 如果后续有需要获取该组件实例,请谨慎使用此方法,设置了virtualHost 的组件节点有反馈无法被 selectComponent
和 getRelationNodes
选中的情况出现)
2、在小程序自定义组件中通过:host
设置组件节点样式为display: contents
首先,需要知道的是,在组件的wxss样式文件中,是可以通过 :host
去直接对组件节点设置一些自定义的样式。
同时,在css中,存在一个 display: contents
属性,能作用在设置该属性的元素节点上。
它的效果是把这个元素的盒子属性(宽、高、背景色、内外边距等)在树型结构中移去,但其节点和内容会保留,元素的子元素与伪元素也仍然存在,元素内的文字照常显示。
同时元素本身设置的一些可继承的属性,比如字体颜色、字体大小、行高等,会被内部的子元素继承。
具体我们可以想象这个元素的本身不再在可视区域内显示,而只有内容留下的场景。
因此,在小程序自定义组件wxss中,添加以下代码,即可:
<!-- 组件layout-child wxss内容 -->
:host {
display: contents;
}
通过该方法设置以后,跟上面第1种方法相比,显示效果一致,区别在于,页面节点树里的组件节点还是正常显示出来的,不会直接被移除。