微信小程序锁滚三轮排查:Canvas 越界才是橡皮筋的真凶
一个小程序页面上线了「禁止整页下拉橡皮筋」的需求。参考项目里其他三个已经稳定的同类页面,把锁滚 4 件套原封不动抄过来,结果:iOS 上手指一划,橡皮筋照样弹出来。
排查花了三轮,最终定位到的根因和"少了哪个配置"毫无关系,而是一个被推到屏幕外的 canvas 元素。
现象
参考页面(三个):4 件套配置,完全锁住,iOS 下拉无响应。
问题页面:同样的 4 件套,iOS 仍然可以触发橡皮筋回弹。
锁滚 4 件套如下:
// page.json
{ "disableScroll": true }/* page-wrap */
.page-wrap {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* scroll 容器 */
.page-scroll {
flex: 1;
min-height: 0;
}
/* 内容区 */
.page-scroll-content {
min-height: 100%;
}根因
Round 1:照抄 4 件套,失败
第一轮认为是"抄漏了某一件",逐项核对,没找到差异,失败。
Round 2:错位归因,加 page-meta,仍然失败
注意到参考页面中有一个用了 <page-meta> 组件,而问题页面没有。于是把 page-meta 当作第 5 件套加进去——用户实测,仍然能下拉。
这是典型的错位归因。三个参考页面都没有用 page-meta 也能锁住,说明 page-meta 根本不是必要条件。此时应该做的是:找 broken 页独有的特征,而不是找 broken 页缺失但参考页也缺失的东西。
Round 3:3 路 diff,找到 broken 独有特征
做了真正的 1:1 对比——broken 页 vs 三个 working 页的完整子树 diff。
差异一眼看出:broken 页的 .page-wrap 里有一个用于离线核销码生成的 <canvas type="2d">,定位写法是:
canvas#writeOffQrcode {
position: absolute;
top: 150%;
left: 150%;
}用意是把 canvas 推到屏幕外,视觉上不可见。但问题在于:
iOS WebKit 在计算页面 scroll box 高度时,会把
position:absolute子元素的 bounding rect 纳入计算,无论该元素是否超出视口。top: 150%意味着这个 canvas 把.page-wrap的 content height 撑到了 250vh,远超 100vh——CSSoverflow:hidden锁住的只是视觉溢出,scroll box 本身已经可以滚动,手指拖拽时就触发了橡皮筋。
正解
撤掉 page-meta(它不是根因,加了反而引入干扰),把需要藏起来的 canvas 从 page-wrap 子树里物理移出,改用 position:fixed 配合负坐标真正脱离文档流:
<!-- 把 canvas 提升到 page-wrap 的兄弟节点 -->
<view class="page-wrap">
<!-- 正常页面内容 -->
</view>
<canvas
type="2d"
id="writeOffQrcode"
class="offscreen-canvas"
/>.offscreen-canvas {
position: fixed;
top: -9999rpx;
left: -9999rpx;
width: 200rpx;
height: 200rpx;
z-index: -1;
pointer-events: none;
}canvas / map / video / textarea / input 这类 native 组件,用 position:absolute + top/left: 150% 推到屏外,DOM 仍在父容器子树内,iOS WebKit 依然把它的 rect 计入滚动高度。
正确藏法有两种:
position: fixed + top/left: -9999rpx + z-index: -1 + pointer-events: none- 把节点提升为 page-wrap 的兄弟节点,物理脱出 content tree
遇到「抄了 sibling 还是不行」的问题,别急着叠加配置,先做 broken vs 3 个 working sibling 的完整子树 diff,列出 broken 页独有的元素和样式。两者的交集才是真嫌疑,而不是「broken 缺少但 working 也缺少的东西」。
一句话外卖
微信小程序里,absolute 定位的 native 组件(canvas/map/video)推到屏幕外并不能脱离 iOS WebKit 的 scroll height 计算——页面锁滚失效时先检查子树里有没有越界的 native 节点,而不是叠加 page-meta。