我一直在寻找一个“灯箱”类型的解决方案,允许这一点,但还没有找到一个(请建议,如果你知道任何)。

我试图重现的行为就像你在Pinterest上点击图片时看到的一样。覆盖层是可滚动的(整个覆盖层向上移动,就像一页在一页的顶部),但覆盖层后面的主体是固定的。

我试图用CSS创建这个(即一个div覆盖在整个页面和身体溢出:隐藏),但它不阻止div是可滚动的。

如何保持主体/页面从滚动,但保持滚动在全屏容器内?


当前回答

理论

看看pinterest网站的当前实现(未来可能会改变),当你打开覆盖时,一个noscroll类被应用到body元素(设置溢出:隐藏),使body不再可滚动。

动态创建的覆盖或已经注入到页面中并通过display: block可见-这没有什么区别-具有position: fixed和overflow-y: scroll,顶部,左侧,右侧和底部属性设置为0:这种风格使覆盖填充整个视口(但现在我们是在2022年,所以你可以使用inset: 0代替)。

覆盖层内的div位于position: static,因此垂直滚动条与该元素相关。这将产生一个可滚动但固定的覆盖。

当你关闭覆盖时,你必须隐藏它(使用display: none),你甚至可以通过javascript删除节点(或者只是里面的内容,这取决于你,但也取决于内容的性质)。

最后一步是删除应用于主体的noscroll类(这样溢出属性就会回到它之前的值)


Code

Codepen例子

(它通过改变overlay的aria-hidden属性来显示和隐藏它,并增加其可访问性)。

标记 (打开按钮)

<button type="button" class="open-overlay">OPEN LAYER</button>

(覆盖和关闭按钮)

<section class="overlay" aria-hidden="true" tabindex="-1">
  <div>
    <h2>Hello, I'm the overlayer</h2>
    ...   
    <button type="button" class="close-overlay">CLOSE LAYER</button>
  </div>
</section>

CSS

.noscroll { 
  overflow: hidden;
}

.overlay { 
   position: fixed; 
   overflow-y: scroll;
   inset: 0; }

[aria-hidden="true"]  { display: none; }
[aria-hidden="false"] { display: block; }

Javascript (vanilla-JS)

var body = document.body,
    overlay = document.querySelector('.overlay'),
    overlayBtts = document.querySelectorAll('button[class$="overlay"]'),
    openingBtt;
    
[].forEach.call(overlayBtts, function(btt) {

  btt.addEventListener('click', function() { 
     
     /* Detect the button class name */
     var overlayOpen = this.className === 'open-overlay';
     
     /* storing a reference to the opening button */
     if (overlayOpen) {
        openingBtt = this;
     }
     
     /* Toggle the aria-hidden state on the overlay and the 
        no-scroll class on the body */
     overlay.setAttribute('aria-hidden', !overlayOpen);
     body.classList.toggle('noscroll', overlayOpen);
     
     /* On some mobile browser when the overlay was previously
        opened and scrolled, if you open it again it doesn't 
        reset its scrollTop property */
     overlay.scrollTop = 0;
     
      /* forcing focus for Assistive technologies but note:
    - if your modal has just a phrase and a button move the
      focus on the button
    - if your modal has a long text inside (e.g. a privacy
      policy) move the focus on the first heading inside 
      the modal
    - otherwise just focus the modal.

    When you close the overlay restore the focus on the 
    button that opened the modal.
    */
    if (overlayOpen) {
       overlay.focus();
    }
    else {
       openingBtt.focus();
       openingBtt = null;
    }

  }, false);

});

/* detect Escape key when the overlay is open */
document.body.addEventListener('keyup', (ev) => {
   if (ev.key === "Escape" && overlay.getAttribute('aria-hidden') === 'false') {
      overlay.setAttribute('aria-hidden', 'true');
      body.classList.toggle('noscroll', false);
      openingBtt.focus();
      openingBtt = null;
   }
})

最后,这是另一个例子,在这个例子中,覆盖通过应用于不透明属性的CSS转换以淡入效果打开。此外,还应用右填充来避免滚动条消失时底层文本的回流。

代码示例(淡出)

CSS

.noscroll { overflow: hidden; }

@media (min-device-width: 1025px) {
    /* not strictly necessary, just an experiment for 
       this specific example and couldn't be necessary 
       at all on some browser */
    .noscroll { 
        padding-right: 15px;
    }
}

.overlay { 
     position: fixed; 
     overflow-y: scroll;
     inset: 0;
}

[aria-hidden="true"] {    
    transition: opacity 1s, z-index 0s 1s;
    width: 100vw;
    z-index: -1; 
    opacity: 0;  
}

[aria-hidden="false"] {  
    transition: opacity 1s;
    width: 100%;
    z-index: 1;  
    opacity: 1; 
}

其他回答

理论

看看pinterest网站的当前实现(未来可能会改变),当你打开覆盖时,一个noscroll类被应用到body元素(设置溢出:隐藏),使body不再可滚动。

动态创建的覆盖或已经注入到页面中并通过display: block可见-这没有什么区别-具有position: fixed和overflow-y: scroll,顶部,左侧,右侧和底部属性设置为0:这种风格使覆盖填充整个视口(但现在我们是在2022年,所以你可以使用inset: 0代替)。

覆盖层内的div位于position: static,因此垂直滚动条与该元素相关。这将产生一个可滚动但固定的覆盖。

当你关闭覆盖时,你必须隐藏它(使用display: none),你甚至可以通过javascript删除节点(或者只是里面的内容,这取决于你,但也取决于内容的性质)。

最后一步是删除应用于主体的noscroll类(这样溢出属性就会回到它之前的值)


Code

Codepen例子

(它通过改变overlay的aria-hidden属性来显示和隐藏它,并增加其可访问性)。

标记 (打开按钮)

<button type="button" class="open-overlay">OPEN LAYER</button>

(覆盖和关闭按钮)

<section class="overlay" aria-hidden="true" tabindex="-1">
  <div>
    <h2>Hello, I'm the overlayer</h2>
    ...   
    <button type="button" class="close-overlay">CLOSE LAYER</button>
  </div>
</section>

CSS

.noscroll { 
  overflow: hidden;
}

.overlay { 
   position: fixed; 
   overflow-y: scroll;
   inset: 0; }

[aria-hidden="true"]  { display: none; }
[aria-hidden="false"] { display: block; }

Javascript (vanilla-JS)

var body = document.body,
    overlay = document.querySelector('.overlay'),
    overlayBtts = document.querySelectorAll('button[class$="overlay"]'),
    openingBtt;
    
[].forEach.call(overlayBtts, function(btt) {

  btt.addEventListener('click', function() { 
     
     /* Detect the button class name */
     var overlayOpen = this.className === 'open-overlay';
     
     /* storing a reference to the opening button */
     if (overlayOpen) {
        openingBtt = this;
     }
     
     /* Toggle the aria-hidden state on the overlay and the 
        no-scroll class on the body */
     overlay.setAttribute('aria-hidden', !overlayOpen);
     body.classList.toggle('noscroll', overlayOpen);
     
     /* On some mobile browser when the overlay was previously
        opened and scrolled, if you open it again it doesn't 
        reset its scrollTop property */
     overlay.scrollTop = 0;
     
      /* forcing focus for Assistive technologies but note:
    - if your modal has just a phrase and a button move the
      focus on the button
    - if your modal has a long text inside (e.g. a privacy
      policy) move the focus on the first heading inside 
      the modal
    - otherwise just focus the modal.

    When you close the overlay restore the focus on the 
    button that opened the modal.
    */
    if (overlayOpen) {
       overlay.focus();
    }
    else {
       openingBtt.focus();
       openingBtt = null;
    }

  }, false);

});

/* detect Escape key when the overlay is open */
document.body.addEventListener('keyup', (ev) => {
   if (ev.key === "Escape" && overlay.getAttribute('aria-hidden') === 'false') {
      overlay.setAttribute('aria-hidden', 'true');
      body.classList.toggle('noscroll', false);
      openingBtt.focus();
      openingBtt = null;
   }
})

最后,这是另一个例子,在这个例子中,覆盖通过应用于不透明属性的CSS转换以淡入效果打开。此外,还应用右填充来避免滚动条消失时底层文本的回流。

代码示例(淡出)

CSS

.noscroll { overflow: hidden; }

@media (min-device-width: 1025px) {
    /* not strictly necessary, just an experiment for 
       this specific example and couldn't be necessary 
       at all on some browser */
    .noscroll { 
        padding-right: 15px;
    }
}

.overlay { 
     position: fixed; 
     overflow-y: scroll;
     inset: 0;
}

[aria-hidden="true"] {    
    transition: opacity 1s, z-index 0s 1s;
    width: 100vw;
    z-index: -1; 
    opacity: 0;  
}

[aria-hidden="false"] {  
    transition: opacity 1s;
    width: 100%;
    z-index: 1;  
    opacity: 1; 
}

React功能组件的一个解决方案是使用useEffect钩子。

下面是下面的代码示例(请注意useEffect定义):

import {useEffect, useRef} from "react";

export default function PopoverMenu({className, handleClose, children}) {
  const selfRef = useRef(undefined);

  useEffect(() => {
    const isPopoverOpenned = selfRef.current?.style.display !== "none";
    const focusedElement = document?.activeElement;
    const scrollPosition = {x: window.scrollX, y: window.scrollY};
    if (isPopoverOpenned) {
      preventDocBodyScrolling();
    } else {
      restoreDocBodyScrolling();
    }

    function preventDocBodyScrolling() {
      const width = document.body.clientWidth;
      const hasVerticalScrollBar = (window.innerWidth > document.documentElement.clientWidth);
      document.body.style.overflowX = "hidden";
      document.body.style.overflowY = hasVerticalScrollBar ? "scroll" : "";
      document.body.style.width = `${width}px`;
      document.body.style.position = "fixed";

    }

    function restoreDocBodyScrolling() {
      document.body.style.overflowX = "";
      document.body.style.overflowY = "";
      document.body.style.width = "";
      document.body.style.position = "";
      focusedElement?.focus();
      window.scrollTo(scrollPosition.x, scrollPosition.y);
    }


    return () => {
      restoreDocBodyScrolling(); // cleanup on unmount
    };
  }, []);

  return (
    <>
      <div
        className="backdrop"
        onClick={() => handleClose && handleClose()}
      />
      <div
        className={`pop-over-menu${className ? (` ${className}`) : ""}`}
        ref={selfRef}
      >
        <button
          className="pop-over-menu--close-button" type="button"
          onClick={() => handleClose && handleClose()}
        >
          X
        </button>
        {children}
      </div>
    </>
  );
}

最初发表于另一个相关的Stackoverflow问题:https://stackoverflow.com/a/69016517/14131330

所选的答案是正确的,但有一些局限性:

用你的手指进行超硬的“投掷”仍然会在后台滚动<body> 在模态中点击<input>打开虚拟键盘将引导所有未来滚动到<body>

我没有解决第一个问题,但想对第二个问题有所了解。令人困惑的是,Bootstrap曾经记录了键盘问题,但他们声称已经修复,并引用http://output.jsbin.com/cacido/quiet作为修复的例子。

事实上,这个例子在我的iOS测试中运行良好。但是,将其升级到最新的Bootstrap (v4)会破坏它。

为了弄清楚它们之间的区别,我减少了一个测试用例,使其不再依赖于Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG。

决定因素很奇怪。避免键盘问题似乎要求没有在包含模态的根目录<div>上设置background-color,并且模态的内容必须嵌套在另一个<div>中,可以设置background-color。

要测试它,在Codepen示例中取消注释下面的行:

.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  display: none;
  overflow: hidden;
  -webkit-overflow-scrolling: touch;
  /* UNCOMMENT TO BREAK */
/*   background-color: white; */
}

我发现这个问题试图解决我在Ipad和Iphone上的页面问题-当我显示固定div为弹出图像时,身体正在滚动。

有些答案很好,但没有一个解决了我的问题。我找到了下面这篇由Christoffer Pettersson撰写的博文。解决方案提出了帮助问题,我与iOS设备,它帮助我的滚动背景问题。

关于iOS Safari的橡皮筋滚动,我了解到了6件事

正如建议的那样,我包括博客文章的主要观点,以防链接过时。

为了禁止用户在“菜单打开”时滚动后台页面,可以通过应用一些JavaScript和CSS类来控制应该允许滚动或不允许滚动哪些元素。

基于这个Stackoverflow答案,您可以控制禁用滚动的元素不应该 当touchmove事件被触发时,执行默认的滚动操作。”

 document.ontouchmove = function ( event ) {

    var isTouchMoveAllowed = true, target = event.target;

    while ( target !== null ) {
        if ( target.classList && target.classList.contains( 'disable-scrolling' ) ) {
            isTouchMoveAllowed = false;
            break;
        }
        target = target.parentNode;
    }

    if ( !isTouchMoveAllowed ) {
        event.preventDefault();
    }
};

然后把禁用滚动类放到页面div上:

<div class="page disable-scrolling">

CSS

.noScroll {
    overflow: hidden;
}

Javascript

<script>
    function toggleNav() {
        document.body.classList.toggle("noScroll");
    }
</script>

按钮

<button onclick="toggleNav()">
 Toggle Nav
</button>