绝对定位和粘性定位无法同时使用的替代方案

需求

需求是在一个子容器的右上方放一个菜单栏,且要求在容器滚动时,这个菜单栏始终在顶部出现,效果如图。

1628134370-需求效果.gif

一开始的想法是,使用绝对定义 postition: absolute 让菜单栏位于右上方,然后再用粘性定位 position: sticky 进行粘性布局。结果发现这两种设置不能同时应用在一个元素上,于是思考其他的方案。

最终实现的在线 demo:https://codepen.io/F-star/pen/PomddPm

方案选择

因为是要定位到右上方,于是我就想,用 float: right 或许也能实现效果,也这样做了。但我忘了一个很重要的 float: right 的副作用,就是它会造成 文字环绕,直到测试人员测试时才发现了这个问题。如下图:

1628134367-文字环绕.png

直接使用元素浮动的方案不可行,我就想或许可以多加一个父元素。父元素使用绝对定位,然后子元素(菜单栏)使用粘性定位。试了下发现是可行的,作为最终的方案。

实现原理

需要给菜单栏 .bar 提供一个父元素 .bar-wrap,这个父元素需要将元素的宽高设置为和容器一样大。对父元素设置绝对定位 position: absolute;,然后通过 width: 100%; height:100%top: 0; left:0; right: 0; bottom: 0; 的方式,让父元素的宽高和容器元素的宽高相同。

宽度要和容器高度相同,是为了让子元素使用右浮动能够正确定位到右上角;高度要和容器相同,是因为使用粘性定位的元素只能在它的父元素内发生粘性效果,无法在父元素的外部出现。高度如果只是和菜单栏的高度相同,是无法出现粘性效果的。

最后我们需要使用 pointer-events: none; 让父元素不可点击,就像不存在一样,实现 点击穿透。否则容器下的其他内容将无法点击。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.bar-wrap {
  /* 核心样式 */
  position: absolute; 
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;

  z-index: 120;
  pointer-events: none;
}

然后就是给菜单栏元素设置 右浮动粘性定位,实现需求的效果。因为菜单栏元素的父元素设置的点击穿透会影响子元素,所以这里需要设置 pointer-events: auto; 来取消点击穿透。

1
2
3
4
5
6
7
8
.bar {
  /* 核心样式 */
  position: sticky;
  top: -20px; /* 容器的 padding-top 的负值 */
  float: right;
  z-index: 88;
  pointer-events: auto;
}

结尾

实现上并不复杂,简而言之,就是在容器上加一个可以点击穿透的透明遮罩,遮罩下的菜单元素设置 右浮动粘性,并设置为不可点击穿透,最终实现了想要的效果。

在线 demo:https://codepen.io/F-star/pen/PomddPm