Javascript iOS - css/js - 覆盖滚动但防止身体滚动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37713970/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 20:40:11  来源:igfitidea点击:

iOS - css/js - Overlay scroll but prevent body scroll

javascripthtmlcss

提问by jcmitch

I know there have been a few questions similar to this but they either don't work for my use case or the accepted answers have a flaw that doesn't work for me. So...

我知道有一些与此类似的问题,但它们要么不适用于我的用例,要么接受的答案存在对我不起作用的缺陷。所以...

I have a page with a list of elements. Clicking on an element in the list will open an overlay with details about that element. I need that overlay to be scrollable but I don't want the rest of the page under the overlay to scroll so that once the overlay is closed you are in the same position (also the overlay is slightly transparent so it is annoying to the user to see the page scrolling below, also why I can't save the scrollY and reset on close).

我有一个包含元素列表的页面。单击列表中的元素将打开包含有关该元素的详细信息的叠加层。我需要该叠加层是可滚动的,但我不希望叠加层下的其余页面滚动,这样一旦叠加层关闭,您就会处于相同的位置(而且叠加层略微透明,因此对用户来说很烦人)看到下面滚动的页面,也是为什么我不能保存 scrollY 并在关闭时重置)。

Right now I have the working everywhere except iOS. This is basically what I have:

现在我可以在除 iOS 之外的任何地方工作。这基本上是我所拥有的:

<html>
   <body>
      <ul id="list">
         <li>something 1</li>
         <li>something 2</li>
         <li>something 3</li>
         <li>something 4</li>
         <li>something 5</li>
      </ul>
      <div id="overlay"></div>
   </body>
</html>

CSS:

CSS:

body.hidden {
   overflow: hidden;
}
#overlay {
   opacity: 0;
   top: -100vh;
}
#overlay.open {
   opacity: 1;
   overflow-y: scroll;
   overflow-x: hidden;
   top: 0;
}

Then in my click hander I toggle the hiddenclass on body, the openclass on #overlay, and populate the #overlayelement with my content. Like I said this works fine everywhere except for iOS.

然后在我的点击处理程序中,我打开hiddenbody,打开open#overlay,并#overlay用我的内容填充元素。就像我说的,这在除 iOS 之外的任何地方都可以正常工作。

Solutions I have seen other places say I need to use position:fixedand height:100%on the bodyand/or htmltags. The problem with this solution is that you lose your scroll position and when you close the overlay you're back at the top of the page. Some of these lists can be really long so that isn't an option for me.

解决方案我看到其他地方说我需要在和/或标签上使用position:fixed和。此解决方案的问题在于您会丢失滚动位置,而当您关闭覆盖层时,您又回到了页面顶部。其中一些列表可能很长,所以这对我来说不是一个选择。height:100%bodyhtml

I can't prevent scrolling completely with preventDefaulton body or something because I need the overlay content to be scrollable.

我无法完全阻止preventDefault在身体或其他东西上滚动,因为我需要覆盖内容是可滚动的。

Any other suggestions?

还有其他建议吗?

采纳答案by jerrylow

There is no way around this right now. As of iOS 9.3 there's still no good way to prevent the scroll on the body. The best method that I currently implement on all sites that require it is to lock the html and the body's height and overflow.

目前没有办法解决这个问题。从 iOS 9.3 开始,仍然没有好的方法来防止身体滚动。我目前在所有需要它的网站上实施的最佳方法是锁定 html 和正文的高度和溢出。

html, body {
  height: 100%;
  overflow: hidden;
}

This is the best way to prevent iOS scroll on the content behind the overlay/modal.

这是防止 iOS 在覆盖层/模态后面的内容上滚动的最佳方法。

Then to preserve the scroll position I shift the content behind up to look like its retaining it then when the modal closes restore the body's position.

然后为了保留滚动位置,我将内容向上移动以看起来像保留它然后当模态关闭时恢复主体的位置。

I do this with a lock and unlock function in jQuery

我用 jQuery 中的锁定和解锁功能来做到这一点

var $docEl = $('html, body'),
  $wrap = $('.content'),
  $.scrollTop;

$.lockBody = function() {
  if(window.pageYOffset) {
    scrollTop = window.pageYOffset;

    $wrap.css({
      top: - (scrollTop)
    });
  }

  $docEl.css({
    height: "100%",
    overflow: "hidden"
  });
}

$.unlockBody = function() {
  $docEl.css({
    height: "",
    overflow: ""
  });

  $wrap.css({
    top: ''
  });

  window.scrollTo(0, scrollTop);
  window.setTimeout(function () {
    scrollTop = null;
  }, 0);
}

When you piece all these together you get http://codepen.io/jerrylow/pen/yJeyoGif you want to test it on your phone here's just the result: http://jerrylow.com/demo/ios-body-lock/

当你把所有这些拼凑在一起时,你会得到http://codepen.io/jerrylow/pen/yJeyoG如果你想在手机上测试它,结果就是:http: //jerrylow.com/demo/ios-body-lock /

回答by Justus Romijn

Why does the page scroll when I'm scrolling on the modal?

为什么当我在模态上滚动时页面会滚动?

If you have the css property -webkit-overflow-scrolling: touch;enabled on the element behind the modal, some native code kicks in that seems to listen for touchmove events which we are unable to capture.

如果您-webkit-overflow-scrolling: touch;在模态后面的元素上启用了 css 属性,则一些本机代码会开始监听我们无法捕获的 touchmove 事件。

So what now?

所以现在怎么办?

I've fixed this for my application by adding a class to negate the css property when the modal is visible. This is a fully working example.

我通过添加一个类来在模态可见时否定 css 属性,为我的应用程序修复了这个问题。这是一个完全有效的例子。

let pageEl = document.querySelector(".page");
let modalEl = document.querySelector(".modal");

function openModal(e){
  e.preventDefault();
  pageEl.classList.add("page--has-modal");
  modalEl.classList.remove("hidden");
  window.addEventListener("wheel", preventScroll);
  window.addEventListener("touchmove", preventScroll);
}
function closeModal(e){
  e.preventDefault();
  pageEl.classList.remove("page--has-modal");
  modalEl.classList.add("hidden");
  
  window.removeEventListener("wheel", preventScroll);
  window.removeEventListener("touchmove", preventScroll);
}

window.addEventListener("click", function(){
  console.log(modalEl.scrollHeight);
  console.log(modalEl.clientHeight);
});

function preventScroll(e){
  if (!isDescendant(modalEl, e.target)){
    e.preventDefault();
    return false;
  }
  
  let modalTop = modalEl.scrollTop === 0;
  let modalBottom = modalEl.scrollTop === (modalEl.scrollHeight -      modalEl.clientHeight);
  
  if (modalTop && e.deltaY < 0){
    e.preventDefault();
  } else if (modalBottom && e.deltaY > 0){
    e.preventDefault();
  }
}

function isDescendant(parent, child) {
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}
.page { 
  -webkit-overflow-scrolling: touch; 
}
.page--has-modal { 
  -webkit-overflow-scrolling: auto;  
}

.modal {
  position: absolute;
  top: 50px;
  left: 50px;
  right: 50px;
  bottom: 50px;
  background: #c0c0c0;
  padding: 50px;
  text-align: center;
  overflow: auto;
  -webkit-overflow-scrolling: auto; 
}
.hidden {
  display: none;
}
<div class="page">
<button onclick="openModal(event);">Open modal</button>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer consequat sapien a lectus gravida euismod. Sed vitae nisl non odio viverra accumsan. Curabitur nisi neque, egestas sed, vulputate sit amet, luctus vitae, dolor. Cras lacus massa, sagittis ut, volutpat consequat, interdum a, nulla. Vivamus rhoncus molestie nulla. Ut porttitor turpis sit amet turpis. Nam suscipit, justo quis ullamcorper sagittis, mauris diam dictum elit, suscipit blandit ligula ante sit amet mauris. Integer id arcu. Aenean scelerisque. Sed a purus. Pellentesque nec nisl eget metus varius tempor. Curabitur tincidunt iaculis lectus. Aliquam molestie velit id urna. Suspendisse in ante ac nunc commodo placerat.</p>

<p>Morbi gravida posuere est. Fusce id augue. Sed facilisis, felis quis ornare consequat, neque risus faucibus dui, quis ullamcorper tellus lacus vitae felis. Phasellus ac dolor. Integer ante diam, consectetuer in, tempor vitae, volutpat in, enim. Integer diam felis, semper at, iaculis ut, suscipit quis, dolor. Vestibulum semper, velit et tincidunt vehicula, nisl risus eleifend ipsum, vel consectetuer enim dolor id magna. Praesent hendrerit urna ac lacus. Maecenas porttitor ipsum sed orci. In ac odio vel lorem tincidunt pellentesque. Nam tempor pulvinar turpis. Nunc in leo in libero ultricies interdum. Proin ut urna. Donec ultricies nunc dapibus justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent vulputate, lectus pulvinar nonummy eleifend, sapien urna posuere metus, vel auctor risus odio eu augue. Cras vitae dolor. Phasellus dolor. Etiam enim. Donec erat felis, tincidunt quis, luctus in, faucibus at, est.</p>
<div class="modal hidden">
Hi there!
<button onclick="closeModal(event);">Close me</button>
<p>Morbi gravida posuere est. Fusce id augue. Sed facilisis, felis quis ornare consequat, neque risus faucibus dui, quis ullamcorper tellus lacus vitae felis. Phasellus ac dolor. Integer ante diam, consectetuer in, tempor vitae, volutpat in, enim. Integer diam felis, semper at, iaculis ut, suscipit quis, dolor. Vestibulum semper, velit et tincidunt vehicula, nisl risus eleifend ipsum, vel consectetuer enim dolor id magna. Praesent hendrerit urna ac lacus. Maecenas porttitor ipsum sed orci. In ac odio vel lorem tincidunt pellentesque. Nam tempor pulvinar turpis. Nunc in leo in libero ultricies interdum. Proin ut urna. Donec ultricies nunc dapibus justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent vulputate, lectus pulvinar nonummy eleifend, sapien urna posuere metus, vel auctor risus odio eu augue. Cras vitae dolor. Phasellus dolor. Etiam enim. Donec erat felis, tincidunt quis, luctus in, faucibus at, est.</p>
</div>
</div>

回答by Stuart Johnston

We faced this exact problem - and finally solved it using:

我们遇到了这个确切的问题 - 最后使用以下方法解决了它:

https://github.com/lazd/iNoBounce

https://github.com/lazd/iNoBounce

One gotcha was immediately after the script loads we had to call iNoBounce.disable()as it was starting up enabled and thus preventing any scrolling behaviour.

一个问题是在脚本加载后我们必须立即调用,iNoBounce.disable()因为它正在启动并因此阻止任何滚动行为。

回答by Tony Tang

The best solution I found which also prevents background scrolling while you scroll to the top or end of the overlay (fixed element) using vanilla javascript:

我发现的最佳解决方案还可以防止在使用 vanilla javascript 滚动到叠加层(固定元素)的顶部或末端时背景滚动:

// "fixed-element" is the class of the overlay (fixed element) what has "position: fixed"
// Call disableScroll() and enableScroll() to toggle

var freeze = function(e) {
  if (!document.getElementsByClassName("fixed-element")[0].contains(e.target)) {
    e.preventDefault();
  }
}

var disableScroll = function() {
  document.body.style.overflow = "hidden"; // Or toggle using class: document.body.className += "overflow-hidden-class";

  // Only accept touchmove from fixed-element
  document.addEventListener('touchmove', freeze, false);

  // Prevent background scrolling
  document.getElementsByClassName("fixed-element")[0].addEventListener("touchmove", function(e) {
    var top = this.scrollTop,
      totalScroll = this.scrollHeight,
      currentScroll = top + this.offsetHeight;

    if (top === 0 && currentScroll === totalScroll) {
      e.preventDefault();
    } else if (top === 0) {
      this.scrollTop = 1;
    } else if (currentScroll === totalScroll) {
      this.scrollTop = top - 1;
    }
  });
}

var enableScroll = function() {
  document.removeEventListener("touchmove", freeze);
  document.body.style.overflow = "";
}

Benefits:
1. Does not make body "fixed" while open overlay (fixed element), so the page doesn't scroll to top.
2. Prevents background scrolling with the fixed element.

优点:
1. 打开叠加层(固定元素)时不会使主体“固定”,因此页面不会滚动到顶部。
2. 防止使用固定元素进行背景滚动。

See Gist

要点

回答by user3025020

it seems iOS will only scroll the body once the overlay reaches min or max scrolling. So, set the scrollTop of the overlay to 1 instead of zero, and detect the onscroll event (which on iOS is fired after scrolling ends) and if at max (app.scrollHeight - app.scrollTop - app.clientHeight < 1) set it to one pixel shorter. For example

似乎 iOS 只会在覆盖达到最小或最大滚动时滚动正文。因此,将覆盖层的 scrollTop 设置为 1 而不是零,并检测 onscroll 事件(在滚动结束后在 iOS 上触发),如果在最大值 (app.scrollHeight - app.scrollTop - app.clientHeight < 1) 设置它缩短一像素。例如

    var overlay = document.getElementById('overlay');

    function onScroll() {
        if (overlay.scrollTop < 1) {
            overlay.scrollTop = 1;
        } else if (overlay.scrollHeight - overlay.scrollTop - overlay.clientHeight < 1)                         {
            overlay.scrollTop = overlay.scrollTop - 1;
        }
    }


    overlay.addEventListener('scroll', onScroll);

You might want to add a check and only attach the event if running in iOS.

如果在 iOS 中运行,您可能想要添加检查并仅附加事件。

回答by daffinm

I found this question whilst looking for a solution to my very similar problem. Perhaps the solution I found to mine will shed some light here.

我在寻找解决我非常相似问题的方法时发现了这个问题。也许我找到的解决方案会在这里有所启发。

In my case the problem was how to prevent a scrollable container from scrolling when using a scrollable widget in that container (e.g. an HTML5 slider rotated vertically using css transform). The scrollable container is defined as 'overflow-y: scroll' using an id selector in CSS.

在我的情况下,问题是如何防止可滚动容器在该容器中使用可滚动小部件时滚动(例如,使用 css 转换垂直旋转的 HTML5 滑块)。使用 CSS 中的 id 选择器将可滚动容器定义为“overflow-y: scroll”。

I tried first using a .scroll-lock class that had 'overflow-y: hidden' and I toggled this on/off on the scrollable container with touchstart and touchend event listeners on all scrollable widgets. It didn't work. Attempts to use the widgets resulted in container scrolling. Then I tried all manner of javascript solutions until I found the ones here, and got closer to solving it.

我首先尝试使用具有 'overflow-y: hidden' 的 .scroll-lock 类,然后我在所有可滚动小部件上使用 touchstart 和 touchend 事件侦听器在可滚动容器上打开/关闭它。它没有用。尝试使用小部件导致容器滚动。然后我尝试了各种 javascript 解决方案,直到我在这里找到了解决方案,并接近解决它。

In my case, the problem was specificity rules. Class selectors are trumped by id selectors, so my toggled class could not override the default overflow setting on the container that was applied by id. When I applied 'overflow: hidden' using the style attribute directly on the container div everything worked fine.

就我而言,问题是特殊性规则。类选择器被 id 选择器打败,所以我的切换类无法覆盖由 id 应用的容器上的默认溢出设置。当我直接在容器 div 上使用样式属性应用“溢出:隐藏”时,一切正常。

const $scrollableWidget = $('.vertical-slider-container');
$scrollableWidget.on('touchstart',function (e) {
    console.log("Disabling scroll on content area");
    $(`#content`).css("overflow-y", "hidden");
});
$scrollableWidget.on('touchend',function (e) {
    console.log("Re-enabling scroll on content area");
    $(`#content`).removeAttr("style");
});

For full details see this fiddle. (Use a mobile browser to try it out.)

有关完整详细信息,请参阅此小提琴。(使用移动浏览器尝试一下。)

https://jsfiddle.net/daffinm/fwgnm7hs/12/

https://jsfiddle.net/daffinm/fwgnm7hs/12/

Hope this helps someone. (I spent far too long solving this.)

希望这可以帮助某人。(我花了太长时间解决这个问题。)

回答by taras-d

I had similar problem. When opening modal window - overlay is scrollable on iOS devices.

我有类似的问题。打开模态窗口时 - 叠加层可在 iOS 设备上滚动。

After playing with css and touch events, I decided to do next (on iOS devices only):

在玩过 css 和 touch 事件之后,我决定下一步(仅在 iOS 设备上):

  • before opening modal - save scroll position to variable and make body fixed
  • after closing modal - make body unfixed and restore scroll position
  • 在打开模态之前 - 将滚动位置保存到变量并使主体固定
  • 关闭模态后 - 使身体不固定并恢复滚动位置