iOS微信小程序scroll-view中使用fixed布局无法穿透的问题

5,273 阅读4分钟

问题描述

最近在做电商小程序,测试小姐姐上报了一个bug上来,如图:

image

中间的模态框期望是覆盖全屏的,但是实际上下方按钮区域没有覆盖到,而且这个问题只在iOS真机上存在。

我的布局大概是这样的

<view class="container" >
  <scroll-view scroll-y="true" class="scroll">
    <modal-component></modal-component>
  </scroll-view>
  <view class="bottom">
    <button>立即购买</button>
    <button>我来开团</button>
  </view>
</view>

对应的样式:

.container{
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100vh;
}
.scroll{
  flex:1;
}
.bottom{
  display: flex;
  height: 88px;
  width: 100%;
}

其中modal-component是一个自定义组件,里边封装了一个fixed布局的模态框

大概是这样的:

<view>
  <view class="modal">
    <view class="modal-mask"></view>
    <view class="modal-content"></view>
  </view>
  <button bindtap="bindtap">点击显示模态框</button>
</view>

对应的样式:

.modal {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 99999;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
​
.modal-mask {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, .6);
}
​
.modal-content {
  width: 200rpx;
  height: 600rpx;
  background-color: #fff;
}

这一切在开发者工具或者安卓手机上都表现很好,符合预期,fixed定位的模态框脱离了文档流,占满了整个viewport,但是在iOS却得到了最开始那张图里的效果。

嗯……万恶的iOS……

但是没办法,既然是前端,适配问题总是会遇到的,再不情愿也只能撸起袖子加油干。

问题排查

通过搜索微信小程序官方社区,我发现遇到这个问题的人不只我一个,比如:

image-20220102235411727

19年的问题,到现在两年多了,期间微信官方只站出来说了一句:这是正常现象!

然后……然后就没然后了……

image-20220102235533048

可以看到底下评论区大家也是对此愤怒又无奈:

image-20220102235716377

既然官方推不动,看来还是只能我们自己来想办法,还好官方也不是什么都没有做,好歹给了一个关键词

-webkit-overflow-scrolling: touch

顺藤摸瓜,面向搜索引擎编程向来是我们的拿手好戏,顺利在这个css属性引发的其他问题(真是罪孽深重啊)的一篇文章中找到了线索:

image-20220103012654689

也就是说,因为微信小程序官方在scroll-view上设置了-webkit-overflow-scrolling: touch,导致浏览器单独创建了一个UIScrollView, 内部的fixed定位元素,是基于这个UIScrollView来定位的,而不是根节点的Viewport。

既然导致问题的原因找到了,那么接下来就是选择解决方案了。

解决方案

要解决这个问题,有4个可能的思路:

  1. 调整整体布局,让scroll-view成为最外部容器,底部按钮也放到scroll-view内部,使用fixed定位到底部
  2. 把fixed元素放到scroll-view外面,通过父子组件通信,把显示模态框的逻辑放到父级
  3. 找到微信官方加给scroll-view的-webkit-overflow-scrolling: touch, 把值改成auto
  4. 弃用scroll-view,使用view配合overflow-y: auto来达成滚动效果

我们一个一个来分析。

  1. 调整布局并不复杂,底部按钮使用fixed定位以后,也只需要在列表底部通过padding等方式有一个占位就行,影响不大,可以考虑
  2. 整个页面中,需要内聚弹窗逻辑的组件不止一个,如果都放到父级,会导致父级逻辑过于臃肿,首先排除
  3. 改动最小,代价是需要牺牲一点滚动性能,也就是说把iOS用户的体验降到和Android用户一样的水平,可以接收,首选
  4. 页面中有滚动锚点相关的逻辑,普通的view没有bindscroll,无法实时获取滚动高度,如果用其他方式间接获取,开发成本和效果都不理想,也排除

经过分析,我首选3,也就是干掉-webkit-overflow-scrolling: touch,毕竟能少些点代码还是少写点代码white less do more是我的一贯宗旨,于是我们打开微信小程序的调试功能,看看scroll-view把这个该死的属性到底是加哪儿了。

image-20220103014750344

然后找到这个webview,并且打开对应的调试控制台

document.getElementsByTagName('webview')[0].showDevTools(true)

顺利找到这个万恶之源(在开发者工具中这个属性显示无效,只有iOS真机上才行)

image-20220103015129490

接下来的事情就简单了

.scroll .wx-scroll-view {
    -webkit-overflow-scrolling: auto;
}

保存,运行,完美。

参考资料

scroll-view包含的自定义组件中fixed元素层级问题?

IOS scroll-view中的自定义组件fixed问题

深入研究-webkit-overflow-scrolling:touch及ios滚动

IOS系统浏览器惯性滚动“itiaosh-webkit-overflow-scrolling:touch”的影响

层叠上下文——MDN