模态框打开时阻止页面滚动

Avatar of Brad Wu
Brad Wu 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

如果您之前听过这个,请阻止我。您打开一个模态框,在其中滚动,关闭它,然后发现页面位置与打开模态框时不同。

这是因为模态框与页面上的任何其他元素一样,都是页面上的元素。它可能会保持在原位(假设这是它的预期行为),但页面其余部分将继续正常运行。

查看 CodePen 上 Geoff Graham 的示例
在模态对话框显示时避免 Safari 可滚动
@geoffgraham
CodePen 上。

有时这不是问题,例如与视口高度完全相同的屏幕。但是,对于其他任何情况,我们都在谈论“滚动之城”。好消息是,我们可以通过一些 CSS(和 JavaScript)技巧来阻止这种情况。

让我们从简单的事情开始

通过将整个 body 的高度设置为视口的高度并隐藏模态框打开时的垂直溢出,我们可以大幅减少打开模态框时页面滚动 的问题。

body.modal-open {
  height: 100vh;
  overflow-y: hidden;
}

这很好,但是如果我们在打开模态框之前已经通过 <body> 元素滚动过,我们会得到一点水平重排。视口宽度会扩展大约 15 个像素,这正好是滚动条的宽度。

查看 CodePen 上 Geoff Graham 的示例
在模态对话框显示时避免 Safari 可滚动
@geoffgraham
CodePen 上。

让我们稍微调整一下 body 的右侧填充以避免这种情况。

body {
  height: 100vh;
  overflow-y: hidden;
  padding-right: 15px; /* Avoid width reflow */
}

请注意,模态框需要比视口高度短才能使其正常工作。否则,body 上的滚动条将是必要的。

很好,那么移动设备呢?

此解决方案在桌面和 Android 移动设备上运行良好。也就是说,iOS 版 Safari 需要更多关注,因为当点击并移动触摸屏时,模态框打开时 body 仍然可以滚动。

我们可以将 body 设置为固定位置作为解决方法。

body {
  position: fixed;
}

现在可以了!触摸屏幕时,body 不会响应。但是,这里仍然存在一个“小”问题。假设模态框触发器在页面下方,我们点击将其打开。很好!但是现在我们自动滚动到屏幕顶部,这与我们试图解决的滚动行为一样令人困惑。

查看 CodePen 上 Geoff Graham 的示例
在模态对话框显示时避免 Safari 可滚动
@geoffgraham
CodePen 上。

糟糕!

这就是为什么我们必须转向 JavaScript 的原因

我们可以使用 JavaScript 来避免触摸事件冒泡。我们都知道,当模态框打开时应该有一个背景层。不幸的是,stopPropagation 在 iOS 中的触摸操作中有点尴尬。但 preventDefault 运行良好。这意味着我们必须在模态框中包含的每个 DOM 节点中添加事件监听器,而不仅仅是在背景或模态框层上。好消息是,许多 JavaScript 库可以做到这一点,包括久经考验的 jQuery。

哦,还有一件事:如果我们需要在模态框中滚动怎么办?我们仍然必须触发触摸事件的响应,但当到达模态框的顶部或底部时,我们仍然需要阻止冒泡。似乎非常复杂,所以我们还没有完全摆脱困境。

让我们增强固定 body 方法

这就是我们一直在使用的

body {
  position: fixed;
}

如果我们知道滚动位置的顶部并将其添加到我们的 CSS 中,那么 body 将不会滚动回屏幕顶部,因此问题得到解决。我们可以使用 JavaScript 来计算滚动顶部,并将该值添加到 body 样式中。

// When the modal is shown, we want a fixed body
document.body.style.position = 'fixed';
document.body.style.top = `-${window.scrollY}px`;

// When the modal is hidden, we want to remain at the top of the scroll position
document.body.style.position = '';
document.body.style.top = '';

这可以工作,但是模态框关闭后仍然存在一点泄漏。具体来说,似乎当模态框打开并且 body 设置为固定时,页面已经失去了其滚动位置。所以我们必须检索位置。让我们修改我们的 JavaScript 以考虑这一点。

// When the modal is hidden...
const scrollY = document.body.style.top;
document.body.style.position = '';
document.body.style.top = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);

就这样!模态框打开时 body 不会再滚动,并且在模态框打开和关闭时都会保持滚动位置。万岁!

查看 CodePen 上 Geoff Graham 的示例
在模态对话框显示时避免 Safari 可滚动
@geoffgraham
CodePen 上。