Просто отключить прокрутку, а не скрыть это?

Я пытаюсь отключить полосу прокрутки html/body родительского, пока я использую лайтбокс. Главное слово здесь - отключить. Я не хочу скрывать это с overflow: hidden;,

Причина этого в том, что overflow: hidden заставляет сайт прыгать и занимать область, где был свиток.

Я хочу знать, возможно ли отключить полосу прокрутки, пока она еще отображается.

27 ответов

Решение

Если страница под оверлеем может быть "зафиксирована" вверху, при открытии оверлея вы можете установить

body { position: fixed; overflow-y:scroll }

вы все равно должны увидеть правую полосу прокрутки, но содержимое не прокручивается. Когда вы закрываете оверлей, просто возвращаете эти свойства

body { position: static; overflow-y:auto }

Я просто предложил этот способ только потому, что вам не нужно менять какое-либо событие прокрутки

Обновить

Вы также можете сделать небольшое улучшение: если вы получите document.documentElement.scrollTop свойство через javascript непосредственно перед открытием слоя, вы можете динамически назначить это значение как top свойство элемента body: при таком подходе страница будет стоять на своем месте, независимо от того, находитесь ли вы сверху или уже прокрутили.

Css

.noscroll { position: fixed; overflow-y:scroll }

JS

$('body').css('top', -(document.documentElement.scrollTop) + 'px')
         .addClass('noscroll');

Четыре небольших дополнения к выбранному решению:

  1. Примените noscroll к html, а не к телу, чтобы избежать двойных полос прокрутки в IE
  2. Чтобы проверить, есть ли на самом деле полоса прокрутки, перед добавлением класса 'noscroll'. В противном случае сайт также будет перепрыгивать при нажатии новой полосы прокрутки без прокрутки.
  3. Чтобы сохранить любой возможный scrollTop, чтобы вся страница не возвращалась наверх (как обновление Фабрицио, но вам нужно получить значение перед добавлением класса 'noscroll')
  4. Не все браузеры обрабатывают scrollTop так же, как описано на http://help.dottoro.com/ljnvjiow.php

Полное решение, которое работает для большинства браузеров:

CSS

html.noscroll {
    position: fixed; 
    overflow-y: scroll;
    width: 100%;
}

Отключить прокрутку

if ($(document).height() > $(window).height()) {
     var scrollTop = ($('html').scrollTop()) ? $('html').scrollTop() : $('body').scrollTop(); // Works for Chrome, Firefox, IE...
     $('html').addClass('noscroll').css('top',-scrollTop);         
}

Включить прокрутку

var scrollTop = parseInt($('html').css('top'));
$('html').removeClass('noscroll');
$('html,body').scrollTop(-scrollTop);

Спасибо Фабрицио и Деяну за то, что они поставили меня на правильный путь, и Бродинго за решение с двойной полосой прокрутки

С включенным jQuery:


запрещать

$.fn.disableScroll = function() {
    window.oldScrollPos = $(window).scrollTop();

    $(window).on('scroll.scrolldisabler',function ( event ) {
       $(window).scrollTop( window.oldScrollPos );
       event.preventDefault();
    });
};

включить

$.fn.enableScroll = function() {
    $(window).off('scroll.scrolldisabler');
};

использование

//disable
$("#selector").disableScroll();
//enable
$("#selector").enableScroll();

Я ОП

С помощью ответа от fcalderan я смог сформировать решение. Я оставляю свое решение здесь, поскольку оно дает ясность в том, как его использовать, и добавляет очень важные детали, width: 100%;

Я добавляю этот класс

body.noscroll
{
    position: fixed; 
    overflow-y: scroll;
    width: 100%;
}

это сработало для меня, и я использовал Fancyapp.

Это сработало очень хорошо для меня....

// disable scrolling
$('body').bind('mousewheel touchmove', lockScroll);

// enable scrolling
$('body').unbind('mousewheel touchmove', lockScroll);


// lock window scrolling
function lockScroll(e) {
    e.preventDefault();
}

просто оберните эти две строки кода тем, что решит, когда вы собираетесь заблокировать прокрутку.

например

$('button').on('click', function() {
     $('body').bind('mousewheel touchmove', lockScroll);
});

Вы не можете отключить событие прокрутки, но вы можете отключить связанные действия, которые приводят к прокрутке, такие как колесико мыши и перемещение пальцем:

$('body').on('mousewheel touchmove', function(e) {
      e.preventDefault();
});

Кроме того, вы можете скрыть полосу прокрутки тела с overflow: hidden и установить поле одновременно, чтобы содержимое не перепрыгивало:

let marginRightPx = 0;
if(window.getComputedStyle) {
    let bodyStyle = window.getComputedStyle(document.body);
    if(bodyStyle) {
        marginRightPx = parseInt(bodyStyle.marginRight, 10);
    }
}

let scrollbarWidthPx = window.innerWidth - document.body.clientWidth;
Object.assign(document.body.style, {
    overflow: 'hidden',
    marginRight: `${marginRightPx + scrollbarWidthPx}px`
});

А затем вы можете добавить отключенную полосу прокрутки на страницу, чтобы заполнить пробел:

textarea {
  overflow-y: scroll;
  overflow-x: hidden;
  width: 11px;
  outline: none;
  resize: none;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  border: 0;
}
<textarea></textarea>

Я сделал именно это для моей собственной реализации лайтбокса. Кажется, до сих пор работает хорошо.

Вот рабочая демонстрация. Вот как вы можете сделать это с помощью чистого JavaScript:

      const { body, documentElement } = document;
let { scrollTop } = document.documentElement;

function disableScroll() {
  scrollTop = documentElement.scrollTop;
  body.style.top = `-${scrollTop}px`;
  body.classList.add("scroll-disabled");
}

function enableScroll() {
  body.classList.remove("scroll-disabled");
  documentElement.scrollTop = scrollTop;
  body.style.removeProperty("top");
}

А это CSS:

      .scroll-disabled {
  position: fixed;
  width: 100%;
  overflow-y: scroll;
}

Мы используем on, чтобы предотвратить его прокрутку, и мы используем overflow-yчтобы показать полосу прокрутки. Нам также нужно установить widthиз-за того, как position: fixedработает.

Мы отслеживаем положение прокрутки и обновляем его при отключении прокрутки, чтобы мы могли правильно позиционировать, используя, когда прокрутка отключена, и восстанавливать позицию прокрутки, когда она включена. В противном случае будет продолжаться переход вверх при отключении или включении прокрутки.

При включении прокрутки мы удаляем topстиль из . Это предотвращает нарушение макета, если у вас другой positionчем staticна body.

Если вы используете scroll-behavior: smoothна html, вам также необходимо изменить enableScrollфункционировать следующим образом:

      function enableScroll() {
  body.classList.remove("scroll-disabled");
  // Set "scroll-behavior" to "auto"
  documentElement.style.scrollBehavior = "auto";
  documentElement.scrollTop = scrollTop;
  // Remove "scroll-behavior: auto" after restoring scroll position
  documentElement.style.removeProperty("scroll-behavior");
  body.style.removeProperty("top");
}

Нам нужно временно установить scroll-behaviorк autoчтобы не было скачков.

Еще одно решение избавиться от скачка содержимого на фиксированном модальном окне при удалении прокрутки тела - нормализовать ширину страницы:

body {width: 100vw; overflow-x: hidden;}

Затем вы можете играть с фиксированным положением или переполнением: скрыто для тела, когда модальное окно открыто. Но он скроет горизонтальные полосы прокрутки - обычно они не нужны на адаптивном веб-сайте.

Это решение, с которым мы пошли. Просто сохраните позицию прокрутки, когда оверлей открыт, прокрутите назад до сохраненной позиции каждый раз, когда пользователь пытался прокрутить страницу, и выключите слушателя, когда оверлей закрыт.

Это немного нервно в IE, но работает как шарм в Firefox/Chrome.

var body = $("body"),
  overlay = $("#overlay"),
  overlayShown = false,
  overlayScrollListener = null,
  overlaySavedScrollTop = 0,
  overlaySavedScrollLeft = 0;

function showOverlay() {
  overlayShown = true;

  // Show overlay
  overlay.addClass("overlay-shown");

  // Save scroll position
  overlaySavedScrollTop = body.scrollTop();
  overlaySavedScrollLeft = body.scrollLeft();

  // Listen for scroll event
  overlayScrollListener = body.scroll(function() {
    // Scroll back to saved position
    body.scrollTop(overlaySavedScrollTop);
    body.scrollLeft(overlaySavedScrollLeft);
  });
}

function hideOverlay() {
  overlayShown = false;

  // Hide overlay
  overlay.removeClass("overlay-shown");

  // Turn scroll listener off
  if (overlayScrollListener) {
    overlayScrollListener.off();
    overlayScrollListener = null;
  }
}

// Click toggles overlay
$(window).click(function() {
  if (!overlayShown) {
    showOverlay();
  } else {
    hideOverlay();
  }
});
/* Required */
html, body { margin: 0; padding: 0; height: 100%; background: #fff; }
html { overflow: hidden; }
body { overflow-y: scroll; }

/* Just for looks */
.spacer { height: 300%; background: orange; background: linear-gradient(#ff0, #f0f); }
.overlay { position: fixed; top: 20px; bottom: 20px; left: 20px; right: 20px; z-index: -1; background: #fff; box-shadow: 0 0 5px rgba(0, 0, 0, .3); overflow: auto; }
.overlay .spacer { background: linear-gradient(#88f, #0ff); }
.overlay-shown { z-index: 1; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<h1>Top of page</h1>
<p>Click to toggle overlay. (This is only scrollable when overlay is <em>not</em> open.)</p>
<div class="spacer"></div>
<h1>Bottom of page</h1>
<div id="overlay" class="overlay">
  <h1>Top of overlay</h1>
  <p>Click to toggle overlay. (Containing page is no longer scrollable, but this is.)</p>
  <div class="spacer"></div>
  <h1>Bottom of overlay</h1>
</div>

Мне нравится придерживаться метода "overflow: hidden" и просто добавить padding-right, равный ширине полосы прокрутки.

Получить функцию ширины полосы прокрутки, по lostsource.

function getScrollbarWidth() {
    var outer = document.createElement("div");
    outer.style.visibility = "hidden";
    outer.style.width = "100px";
    outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps

    document.body.appendChild(outer);

    var widthNoScroll = outer.offsetWidth;
    // force scrollbars
    outer.style.overflow = "scroll";

    // add innerdiv
    var inner = document.createElement("div");
    inner.style.width = "100%";
    outer.appendChild(inner);        

    var widthWithScroll = inner.offsetWidth;

    // remove divs
    outer.parentNode.removeChild(outer);

    return widthNoScroll - widthWithScroll;
}

При отображении наложения добавьте класс "noscroll" в html и добавьте padding-right для body:

$(html).addClass("noscroll");
$(body).css("paddingRight", getScrollbarWidth() + "px");

Когда прячешься, убираешь класс и отступы:

$(html).removeClass("noscroll");
$(body).css("paddingRight", 0);

Стиль Noscroll таков:

.noscroll { overflow: hidden; }

Обратите внимание, что если у вас есть какие-либо элементы с позиции position: fixed, вам также необходимо добавить отступы к этим элементам.

Все модальные / лайтбоксовые системы на основе JavaScript используют переполнение при отображении модального / лайтбокса, тега html или тега body.

Когда лайтбокс отображается, js вызывает переполнение, скрытое в теге html или body. Когда лайтбокс скрыт, некоторые удаляют скрытый, другие нажимают на авто переполнение в тегах html или body.

Разработчики, работающие на Mac, не видят проблемы с полосой прокрутки.

Просто замените скрытое на unset, чтобы контент не проскальзывал под модальностью удаления полосы прокрутки.

Лайтбокс открыть / показать:

<html style="overflow: unset;"></html>

Лайтбокс закрыть / скрыть:

<html style="overflow: auto;"></html>

Вы можете сохранить переполнение: скрытое, но управлять позицией прокрутки вручную:

перед показом следите за фактическим положением прокрутки:

var scroll = [$(document).scrollTop(),$(document).scrollLeft()];
//show your lightbox and then reapply scroll position
$(document).scrollTop(scroll[0]).scrollLeft(scroll[1]);

он должен работать

Я сделал одну функцию, которая решает эту проблему с помощью JS. Этот принцип можно легко расширить и настроить, что для меня является большим профессионалом.

Используя эту функцию js DOM API:

const handleWheelScroll = (element) => (event) => {
  if (!element) {
    throw Error("Element for scroll was not found");
  }
  const { deltaY } = event;
  const { clientHeight, scrollTop, scrollHeight } = element;
  if (deltaY < 0) {
    if (-deltaY > scrollTop) {
      element.scrollBy({
        top: -scrollTop,
        behavior: "smooth",
      });
      event.stopPropagation();
      event.preventDefault();
    }
    return;
  }

  if (deltaY > scrollHeight - clientHeight - scrollTop) {
    element.scrollBy({
      top: scrollHeight - clientHeight - scrollTop,
      behavior: "smooth",
    });
    event.stopPropagation();
    event.preventDefault();
    return;
  }
};

Короче говоря, эта функция остановит распространение событий и поведение по умолчанию, если прокрутка будет прокручивать что-то еще, кроме данного элемента (того, который вы хотите прокрутить).

Затем вы можете зацепить и отцепить это вот так:

const wheelEventHandler = handleWheelScroll(elementToScrollIn);

window.addEventListener("wheel", wheelEventHandler, {
    passive: false,
});

window.removeEventListener("wheel", wheelEventHandler);

Обратите внимание на то, что это функция более высокого порядка, поэтому вам нужно сохранить ссылку на данный экземпляр.

Я подключаю addEventListener часть мыши введите и отцепите removeEventListener в мыши оставьте события в jQuery, но вы можете использовать его как хотите.

У меня была похожая проблема: левое меню, которое, когда оно появляется, предотвращает прокрутку. Как только высота была установлена ​​на 100vh, полоса прокрутки исчезла, и содержимое дернулось вправо.

Поэтому, если вы не против оставить полосу прокрутки включенной (но установить окно на полную высоту, чтобы оно нигде не прокручивалось), тогда другой возможностью является установка крошечного нижнего поля, при котором полосы прокрутки будут отображаться:

body {
    height: 100vh;
    overflow: hidden;
    margin: 0 0 1px;
}

<div id="lightbox"> находится внутри <body> элемент, таким образом, когда вы прокручиваете лайтбокс, вы также прокручиваете тело. Решение состоит в том, чтобы не расширять <body> элемент более 100%, чтобы поместить длинный контент в другой div элемент и добавить полосу прокрутки, если это необходимо div элемент с overflow: auto,

html {
  height: 100%
}
body {
  margin: 0;
  height: 100%
}
#content {
  height: 100%;
  overflow: auto;
}
#lightbox {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
<html>
  <body>
    <div id="content">much content</div>
    <div id="lightbox">lightbox<div>
  </body>
</html>

Теперь, прокручивая лайтбокс (и body также) не имеет никакого эффекта, потому что тело не превышает 100% высоты экрана.

Если страница под оверлеем может быть "зафиксирована" вверху, при открытии оверлея вы можете установить

.disableScroll { position: fixed; overflow-y:scroll }

предоставить этот класс для прокручиваемого тела, вы все равно должны увидеть правую полосу прокрутки, но содержимое не прокручивается.

Чтобы сохранить положение страницы, сделайте это в jquery

$('body').css('top', - ($(window).scrollTop()) + 'px').addClass('disableScroll');

Когда вы закрываете оверлей, просто возвращаете эти свойства

var top = $('body').position().top;
$('body').removeClass('disableScroll').css('top', 0).scrollTop(Math.abs(top));

Я просто предложил этот способ только потому, что вам не нужно менять какое-либо событие прокрутки

Это остановит прыжок области просмотра вверх, сохраняя положение прокрутки и восстанавливая его при включении прокрутки.

CSS

.no-scroll{
  position: fixed; 
  width:100%;
  min-height:100vh;
  top:0;
  left:0;
  overflow-y:scroll!important;
}

JS

var scrollTopPostion = 0;

function scroll_pause(){
  scrollTopPostion = $(window).scrollTop();
  $("body").addClass("no-scroll").css({"top":-1*scrollTopPostion+"px"});
}

function scroll_resume(){
  $("body").removeClass("no-scroll").removeAttr("style");
  $(window).scrollTop(scrollTopPostion);
}

Теперь все, что вам нужно сделать, это вызвать функции

$(document).on("click","#DISABLEelementID",function(){
   scroll_pause();
});

$(document).on("click","#ENABLEelementID",function(){
   scroll_resume();
});

В position: fixed;У решения есть недостаток - при применении этого стиля страница перескакивает вверх. У Angular's Material Dialog есть хорошее решение, в котором они имитируют положение прокрутки, применяя позиционирование кhtml элемент.

Ниже мой измененный алгоритм только для вертикальной прокрутки. Блокировка левой прокрутки выполняется точно так же.

// This class applies the following styles:
// position: fixed;
// overflow-y: scroll;
// width: 100%;
const NO_SCROLL_CLASS = "bp-no-scroll";

const coerceCssPixelValue = value => {
  if (value == null) {
    return "";
  }

  return typeof value === "string" ? value : `${value}px`;
};

export const blockScroll = () => {
  const html = document.documentElement;
  const documentRect = html.getBoundingClientRect();
  const { body } = document;

  // Cache the current scroll position to be restored later.
  const cachedScrollPosition =
    -documentRect.top || body.scrollTop || window.scrollY || document.scrollTop || 0;

  // Cache the current inline `top` value in case the user has set it.
  const cachedHTMLTop = html.style.top || "";

  // Using `html` instead of `body`, because `body` may have a user agent margin,
  // whereas `html` is guaranteed not to have one.
  html.style.top = coerceCssPixelValue(-cachedScrollPosition);

  // Set the magic class.
  html.classList.add(NO_SCROLL_CLASS);

  // Return a function to remove the scroll block.
  return () => {
    const htmlStyle = html.style;
    const bodyStyle = body.style;

    // We will need to seamlessly restore the original scroll position using
    // `window.scroll`. To do that we will change the scroll behavior to `auto`.
    // Here we cache the current scroll behavior to restore it later.
    const previousHtmlScrollBehavior = htmlStyle.scrollBehavior || "";
    const previousBodyScrollBehavior = bodyStyle.scrollBehavior || "";

    // Restore the original inline `top` value.
    htmlStyle.top = cachedHTMLTop;

    // Remove the magic class.
    html.classList.remove(NO_SCROLL_CLASS);

    // Disable user-defined smooth scrolling temporarily while we restore the scroll position.
    htmlStyle.scrollBehavior = bodyStyle.scrollBehavior = "auto";

    // Restore the original scroll position.
    window.scroll({
      top: cachedScrollPosition.top
    });

    // Restore the original scroll behavior.
    htmlStyle.scrollBehavior = previousHtmlScrollBehavior;
    bodyStyle.scrollBehavior = previousBodyScrollBehavior;
  };
};

Логика очень проста, и ее можно упростить еще больше, если вам не нужны определенные крайние случаи. Например, вот что я использую:

export const blockScroll = () => {
  const html = document.documentElement;
  const documentRect = html.getBoundingClientRect();
  const { body } = document;
  const screenHeight = window.innerHeight;

  // Only do the magic if document is scrollable
  if (documentRect.height > screenHeight) {
    const cachedScrollPosition =
      -documentRect.top || body.scrollTop || window.scrollY || document.scrollTop || 0;

    html.style.top = coerceCssPixelValue(-cachedScrollPosition);

    html.classList.add(NO_SCROLL_CLASS);

    return () => {
      html.classList.remove(NO_SCROLL_CLASS);

      window.scroll({
        top: cachedScrollPosition,
        behavior: "auto"
      });
    };
  }
};

Я заметил, что сайт YouTube делает именно это. Итак, немного изучив его, я смог определить, что они используют @polymer/iron-overlay-behavior и, к счастью, его можно довольно ненавязчиво использовать вне веб-компонентов / Polymer:

      import {
    pushScrollLock,
    removeScrollLock,
} from '@polymer/iron-overlay-behavior/iron-scroll-manager';

// lock scroll everywhere except scrollElement
pushScrollLock(scrollElement);

// restore scrolling
removeScrollLock(scrollElement);
  • Позволяет прокручивать выбранный элемент
  • Ни в коем случае не портит укладку
  • Проверено в боях на сайте YouTube

Это похоже на зрелое решение и, безусловно, лучшее, что я смог найти. Пакет немного тяжелый, но я предполагаю, что большая его часть рассыпается при импорте только iron-scroll-manager.

Ваше здоровье

У меня есть некоторые другие фиксированные элементы на странице и настройка bodyх positionк fixedвызвал кучу других проблем, поэтому я сделал это хакерским способом:

      const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;

// on opening modal
document.body.style.overflow = "hidden"
document.body.style.paddingRight = `${scrollbarWidth}px`

// on closing modal
document.body.style.overflow = "unset",
document.body.style.paddingRight = "0px"

Идея состоит в том, чтобы добавить padding-rightс той же шириной, что и полоса прокрутки браузера, чтобы имитировать фальшивую полосу прокрутки и предотвратить сдвиг содержимого.

JavaScript:

      const disableScroll = () => {
  window.scrollTo({
    top: 0,
    behavior: "instant"
  })
}

document.addEventListener("scroll", disableScroll, false)

Реакция + ТС:

      type Props = {
  open: boolean
  onClose: () => void
  disableClickOutside?: boolean
  disabledEscKey?: boolean
  width?: string
  theme?: Theme
  size?: Size
  children?: ReactNode
}

export const Modal = ({
  open,
  children,
  width,
  theme = "light",
  onClose,
  disableClickOutside,
  disabledEscKey,
  size = "md",
}: Props) => {
  const contentRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const { scrollTop } = document.documentElement

    const disableScroll = () => {
      window.scrollTo({
        top: scrollTop,
        behavior: "instant" as ScrollBehavior,
      })
    }

    if (open) {
      contentRef?.current?.focus()
      document.addEventListener("scroll", disableScroll, false)
    } else if (!open) {
      document.removeEventListener("scroll", disableScroll)
    }
    return () => {
      document.removeEventListener("scroll", disableScroll)
    }
  }, [open])

  const handleClickOutside = (event: React.MouseEvent<Element>) => {
    if (disableClickOutside) return
    const clickedElement = event.target as HTMLDivElement
    if (!contentRef?.current?.contains(clickedElement) && open) onClose()
  }

  const handleKeydown = (event: React.KeyboardEvent<Element>) => {
    if (disabledEscKey) return
    if (event.key === "Escape") onClose()
  }

  return (
    <Wrapper
      aria-label="modal-wrapper"
      isOpen={open}
      onKeyDown={(e: React.KeyboardEvent<Element>) => handleKeydown(e)}
      onClick={(e: React.MouseEvent<Element>) => handleClickOutside(e)}
      tabIndex={-1}
    >
      <Content
        theme={theme}
        ref={contentRef}
        width={width}
        size={size}
        tabIndex={1}
      >
        {children}
      </Content>
    </Wrapper>
  )
}

type StyledModalProps = {
  width?: string
  theme?: Theme
  size: Size
}

const Wrapper = styled.div`
  display: ${(props: { isOpen: boolean }) => (props.isOpen ? "flex" : "none")};
  align-items: center;
  justify-content: center;
  z-index: 3;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.3);
  &:focus {
    outline: none;
  }
`
const Content = styled.div`
  display: flex;
  justify-content: center;
  background-color: ${({ theme }: StyledModalProps) =>
    match(theme)
      .with("dark", () => Gray90)
      .otherwise(() => White)};
  padding: 2rem;
  border-radius: 1rem;
  box-shadow: 0px 0px 1rem
    ${({ theme }: StyledModalProps) =>
      match(theme)
        .with("dark", () => "rgba(0, 0, 0, 0.48)")
        .otherwise(() => "rgba(28, 28, 31, 0.12)")};
  width: ${({ width, size }: StyledModalProps) =>
    width ||
    match(size)
      .with("lg", () => "67.5rem")
      .with("md", () => "45rem")
      .with("sm", () => "28.75rem")
      .with("xs", () => "20rem")
      .exhaustive()};
`

Грубым, но рабочим способом будет принудительная прокрутка вверх, таким образом, эффективно отключая прокрутку:

var _stopScroll = false;
window.onload = function(event) {
    document.onscroll = function(ev) {
        if (_stopScroll) {
            document.body.scrollTop = "0px";
        }
    }
};

Когда вы открываете лайтбокс, поднимите флаг и, закрыв его, опустите флаг.

Живой тестовый кейс.

Реакт версия:

  • сохраняет положение прокрутки
  • нет перекомпоновки макета
  • машинопись

https://gist.github.com/airtonix/c8c9af146185646e7451faa0f2ac96b7

используйте его как:

      // app
<BlanketProvider color='red'>
  <YourView />
</BlanketProvider>
      // YourView
...
const { isOpen, setIsOpen } = useBlanket();
return (
  <>
    <Blanket>
      {isOpen && <SomeThingWithHigherZindex />}
    </Blanket>
    <Button onClick={() => setIsOpen(true)}>Do A Thing</Button>
  </>
)

При настройкеisOpenв true мы отслеживаем текущую прокрутку сверху и сохраняем ее.

Мы устанавливаем его только для стилей кузова, если мы открываем, потому что то, что мы делаем дальше, вызоветwindow.scrollYбыть0.

Затем мы проверяем, выше ли документ, чем окно просмотра. Если это так, мы устанавливаем переполнение Y наscrollчтобы убедиться, что полоса прокрутки не исчезает и нет скачка перекомпоновки макета.

Установите тело в фиксированное положение, предотвращая его прокрутку, и убедитесь, что документ находится в правильном положении прокрутки, чтобы противодействоватьposition: fixed;

Вы можете сделать это с помощью Javascript:

// Classic JS
window.onscroll = function(ev) {
  ev.preventDefault();
}

// jQuery
$(window).scroll(function(ev) {
  ev.preventDefault();
}

А затем отключите его, когда ваш лайтбокс закрыт.

Но если ваш лайтбокс содержит полосу прокрутки, вы не сможете прокручивать ее, пока она открыта. Это потому что window содержит как body а также #lightbox, Поэтому вы должны использовать архитектуру, подобную следующей:

<body>
  <div id="global"></div>
  <div id="lightbox"></div>
</body>

А затем применить onscroll событие только на #global,

Я создал репозиторий для решения именно этой проблемы. Ниже я свел решение к сути, но репозиторий с поддержкой горизонтальной прокрутки и другими параметрами настройки можно найти здесь:

Вот ссылка прямо на рабочую демонстрационную страницу: http://alexanderspirgel.com/no-scroll/demo/

Мое решение работает путем сравнения разницы в размерах внешнего элемента с полосой прокрутки и его внутреннего элемента. После расчета размера полосы прокрутки элемент прокрутки можно установить наoverflow: hiddenи можно применить смещение, равное ширине полосы прокрутки, чтобы предотвратить сдвиг содержимого. Кроме того, это решение помещает псевдоэлемент с отключенной полосой прокрутки в новое пространство, созданное после отключения прокрутки.

Сначала настройте HTML так, чтобы внутренний элемент был обернут внешним элементом (внешним и внутренним элементами могут быть и<body>элементы).

      <html>
    <body>
        content here...
    </body>
</html>

Скрипт устанавливает переменные CSS и применяет правильные атрибуты данных HTML, но остальное контролируется CSS. Включите этот CSS, чтобы элементы имели необходимые стили.

      [data-no-scroll-element=outer] {
  --no-scroll--y-scrollbar-width: 0px;
  box-sizing: border-box;
  position: relative;
  padding: 0; /* Padding on outer elements breaks disabled scrollbar placeholder spacing. */
  overflow: auto; /* Accounts for space of internal margins. */
}

[data-no-scroll-element=outer][data-no-scroll-y-state=no-scroll] {
  overflow-y: hidden;
}

[data-no-scroll-element=outer]::after {
  content: "";
  box-sizing: border-box;
  display: none;
  position: sticky;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: hidden;
  pointer-events: none;
}

[data-no-scroll-element=outer][data-no-scroll-y-state=no-scroll]::after {
  display: block;
  overflow-y: scroll;
}

html[data-no-scroll-element=outer]::after {
  height: 100vh; /* If the outer element is <html> the after element must be set to the viewport height to work properly. */
}

[data-no-scroll-element=inner] {
  box-sizing: border-box;
  display: block;
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  border: 0; /* Border on inner elements breaks scrollbar size calculations. */
  margin: 0; /* Margin on inner elements breaks scrollbar size calculations. */
  padding: 0; /* Padding on inner elements breaks scrollbar size calculations. */
  overflow: auto; /* Accounts for space of internal margins. */
}

[data-no-scroll-element=outer][data-no-scroll-y-state=no-scroll] > [data-no-scroll-element=inner] {
  margin-right: var(--no-scroll--y-scrollbar-width, 0px);
}

Включите этот JavaScript для обработки расчета ширины полосы прокрутки и переключения между включением и отключением прокрутки.

      class noScroll {

    static isOuterElementDocumentElement(outerElement) {
        if (outerElement === document.documentElement) {
            return true;
        }
        else {
            return false;
        }
    }

    static getElementYScrollbarWidth(outerElement, innerElement) {
        let size = 0;
        let outerSize = outerElement.offsetWidth;
        if (this.isOuterElementDocumentElement(outerElement)) {
            outerSize = window.innerWidth;
        }
        const innerSize = innerElement.offsetWidth;
        const outerElementComputedStyles = window.getComputedStyle(outerElement);
        const borderLeftWidth = parseInt(outerElementComputedStyles.borderLeftWidth);
        const borderRightWidth = parseInt(outerElementComputedStyles.borderRightWidth);
        size = (outerSize - borderLeftWidth - borderRightWidth) - innerSize;
        if (size < 0) {
            size = 0;
        }
        return size;
    }

    static setYScrollbarWidthCSSVariable(outerElement, innerElement, value) {
        if (typeof value === 'undefined') {
            value = this.getElementYScrollbarWidth(outerElement, innerElement);
        }
        if (typeof value === 'number') {
            value = value.toString() + 'px';
        }
        outerElement.style.setProperty('--no-scroll--y-scrollbar-width', value);
        if (this.isOuterElementDocumentElement(outerElement)) {
            outerElement.style.setProperty('--no-scroll--document-width-offset', value);
        }
    }

    static isScrollEnabled(outerElement) {
        if (outerElement.getAttribute('data-no-scroll-y-state') === 'no-scroll') {
            return false;
        }
        return true;
    }

    static disableScroll(outerElement, innerElement) {
        outerElement.setAttribute('data-no-scroll-element', 'outer');
        innerElement.setAttribute('data-no-scroll-element', 'inner');
        this.setYScrollbarWidthCSSVariable(outerElement, innerElement);
        outerElement.setAttribute('data-no-scroll-y-state', 'no-scroll');
    }

    static enableScroll(outerElement, innerElement) {
        this.setYScrollbarWidthCSSVariable(outerElement, innerElement, 0);
        outerElement.setAttribute('data-no-scroll-y-state', 'scroll');
    }

    static toggleScroll(outerElement, innerElement) {
        const isScrollEnabled = this.isScrollEnabled(outerElement);
        if (isScrollEnabled) {
            this.disableScroll(outerElement, innerElement);
        }
        else {
            this.enableScroll(outerElement, innerElement);
        }
    }

};

Затем прокрутку можно отключить/включить по вашему усмотрению, вызвав такие методы:

      const outerElement = document.querySelector('html');
const innerElement = document.querySelector('body');

// disable
noScroll.disableScroll(outerElement, innerElement);

// enable
noScroll.enableScroll(outerElement, innerElement);

// toggle
noScroll.toggleScroll(outerElement, innerElement);

Если вы отключите прокрутку элемента, ширина веб-сайта изменится. Это вызоветposition: fixedэлементы, обеспечивающие нежелательный «прыжок» в нужном положении. Чтобы решить эту проблему, при отключении прокрутки<html>элемент, переменная CSS, называемая--no-scroll--document-width-offsetсоздается со значением, равным изменению ширины. Вы можете включить эту переменную CSS в положение фиксированных элементов, чтобы предотвратить «переход» следующим образом:

      .fixed-centered {
    position: fixed;
    left: calc((100% - var(--no-scroll--document-width-offset, 0px)) / 2);
    transform: translateX(-50%);
}

Я надеюсь, что это сработает для вас. Опять же, это сокращенная версия моего более полного решения, расположенного здесь: https://github.com/alexspirgel/no-scroll .https://github.com/alexspirgel/no-scroll .

Я решил эту проблему с помощью метода scrollLock, который настраивает прослушиватели событий колесика прокрутки и событий нажатия клавиш, а также метода preventScroll, обрабатывающего эти события. Что-то вроде этого:

      preventScroll = function (e) {
    // prevent scrollwheel events
    e.preventDefault();
    e.stopPropagation();

    // prevent keydown events
    var keys = [32, 33, 34, 35, 37, 38, 39, 40];
    if (keys.includes(e.keyCode)) {
        e.preventDefault();
    }

    return false;
}

scrollLock = function (lock) {
    if (lock) {
        document.querySelector("#container").addEventListener("wheel", preventScroll);
        document.addEventListener("keydown", preventScroll);
    }
    else {
        document.querySelector("#container").removeEventListener("wheel", preventScroll);
        document.querySelector("#container").removeEventListener("keydown", preventScroll);
    }
}
Другие вопросы по тегам