How to disable body scrolling when modal is open IOS only
Asked Answered
C

5

9

IOS only / iPhone X / iPhone 7 etc.

Even jquery modal libraries don't work! https://jquerymodal.com/ - Open the modal on your iPhone and you will be able to scroll the body.

I am finding it very hard to find a solution that stops the body scrolling without making the page jump to the top each time the modal is opened (which is just as bad experience as the page scrolling)

It seems this is a massive problem lots of people experiencing this. As you can see here:

I have hunted the internet with no luck, has anyone a solution?!

Conventional answered 20/2, 2019 at 12:56 Comment(0)
H
15

I've created the following solution, which works on iOS 12!

Although the embedded demo below uses Bootstrap 4, the same solution works equally well with Bootstrap 3 since none of the modal class or event names are different.

Step 1: Use fixed positioning to freeze the body in place when the modal is open

When a Bootstrap modal is opened, a class called .modal-open is added to the body. Add the following additional styles to this class:

body {
    &.modal-open {
        bottom: 0;
        left: 0;
        position: fixed;
        right: 0;
        top: 0;
    }
}

Now whenever a modal is opened, the body will be fixed in place and sized to the same dimensions as the viewport itself. This completely prevents scrolling because there's nowhere and nothing to scroll to!

But: this also means that opening a modal will cause the page to jump to the top, because the body no longer extends past the bottom edge of the viewport (assuming the page content is taller).

Step 2: Simulate the previous scroll distance while the modal is open

Bootstrap exposes events that fire when a modal is opened or closed. We can use these to solve the "jump to the top" issue by pulling the top of the body up when a modal is opened, so that it looks like the scroll position hasn't changed:

$(function() {
    var $window = $(window),
        $body = $("body"),
        $modal = $(".modal"),
        scrollDistance = 0;

    $modal.on("show.bs.modal", function() {
        // Get the scroll distance at the time the modal was opened
        scrollDistance = $window.scrollTop();

        // Pull the top of the body up by that amount
        $body.css("top", scrollDistance * -1);
    });
});

However, the page will still jump to the top when the modal is closed because the scrollTop value of the window is still 0.

Step 3: Reset everything when the modal is closed

Now we just need to hook into the event that fires when the modal is closed and put everything back how it was:

  • Remove the fixed positioning and negative top value on the body
  • Set the window's scroll position back to what it was originally
$modal.on("hidden.bs.modal", function() {
    // Remove the negative top value on the body
    $body.css("top", "");

    // Set the window's scroll position back to what it was before the modal was opened
    $window.scrollTop(scrollDistance);  
});

There's no need to manually remove the fixed positioning on the body, because this is set through the .modal-open class, which Bootstrap removes when the modal is closed.


Demo

Put it all together, and now you can prevent background scrolling on iOS while a modal is open without losing your scroll position!

Open the following link on an iOS device: https://daguy.github.io/ios-modal-fix/

Hildebrand answered 21/2, 2019 at 3:45 Comment(10)
I have tried this code on my iPhone with this codepen: codepen.io/bkdigital/pen/YBbpvN - It does not seem to be working, please try it on your iPhone device? You can only see the the top of the page, the body is being cut off wether modal open or not.Conventional
Do you have a website where you have used this solution? @daGuyConventional
@Conventional – it looks like Codepen itself interferes with it somehow. It works perfectly on my iPhone XR running iOS 12 on a standalone page: daguy.github.io/ios-modal-fixHildebrand
There are buttons to open the modal at the top and bottom of the page so you can see how it works at different scroll positions.Hildebrand
I have tested out your link there and its working perfect! Bloody code pen :P I will implement this today TYVM!Conventional
This works great for not losing body scroll positions but unfortunately it still bugs out and sticks to the body though, disallowing the users to scroll the modal in question, making for a miserable experience to the user. This is really ridiculous that IOS doesn't listen.Commination
You can make this even better by adding a scroll event to the body when modals are 'open', Simply scrollTop to scrollDistance on every 'body' scroll. This solves 100% of all scroll issues, including the getting "stuck" which happens a lot with 'modal' and 'body' scrolling events happening at the same time.Commination
@Commination there shouldn't be any scroll event occurring on the body, because the body should be the exact same size as the viewport. The .modal overlay (which contains the modal window) should scroll if the modal's content is taller than the body/viewport. You can make the scrolling smoother by adding -webkit-overflow-scrolling: touch; to the modal overlay. I updated my demo page with a really tall modal and the scrolling works fine on my iPhone XR.Hildebrand
If you're stacking modal boxes -- I know it's a no-no -- then you need to keep track of how many are loaded. Only when the last modal is unloaded do you not add back the tag (that is automatically removed when you close any of your modals.).Guesswarp
FYI, this same solution also works with the new offcanvas element in Bootstrap 5, which still has the same issue, even on iOS 15. The only difference is that the offcanvas element doesn’t automatically add/remove a class on the body when it’s toggled, so you have to use the built-in events to do that yourself.Hildebrand
I
1

export default class BackgroundScroll {
  private scrollPosition = 0;
  disable = (isDisable) => {
    if (isDisable) {
      this.scrollPosition = window.pageYOffset;
      document.body.classList.add('block-body-scroll');
      document.body.style.top = `-${this.scrollPosition}px`;
      if (this.checkIos()) {
        document.body.classList.add('ios');
      }
    } else {
      document.body.classList.remove('block-body-scroll');
      window.scrollTo(0, this.scrollPosition);
    }
  };

  private checkIos(): boolean {
    return (
      ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(
        navigator.platform
      ) ||
      (navigator.userAgent.includes('Mac') && 'ontouchend' in document)
    );
  }
}
body.block-body-scroll {
  overflow: hidden;
  width: 100%;
  &.ios {
    position: fixed;
  }
}

We only handle position: fixed for ios. Because position: fixed will affect other browsers as Firefox. This is the code I handle on typescript, but pure JS or anything else basically the same as mine.

Imperturbation answered 4/6, 2021 at 3:48 Comment(1)
This is the best solution. Others didn't work for me.Counterproposal
G
0

After lots of trial an error, I came up with:

/* Default (phone) */
body.modal-open {
    left: 0;
    position: fixed;
    right: 0;
    /* Prevent scrolling of body */
    overflow-y: hidden !important;
}

/* Small and above e.g. not a phone */
@media (min-width: 576px) {
    body.modal-open {
        /* Keep scrollbar */
        overflow-y: scroll !important;
    }
}

In default mode (phone), I just remove the vertical scrollbar. Since it's on the phone, it sits on top, and has no width. Without overflow-y, the phone can't scroll.

But on the desktop, I like permanent horizontal scroll, so I force it on during the modal to prevent the screen from shifting by the 17px width.

If on the desktop (full width scrollbar) you get a shift if the user shrinks browser to less than 576px, but I can live with that.

Also -- I allow scroll within the modal. This way the modal doesn't run off the bottom of the screen on mobile.

Guesswarp answered 29/1, 2022 at 21:21 Comment(0)
M
0

I was able to make use of a workaround in React.

  1. Hide scrollbar

useEffect(() => {
    const scrollbarElement = document.documentElement;
    if (visible) {
      //removing scrollbar from html for Modal
      scrollbarElement.setAttribute("style", "overflow: hidden;");
    }
    return () => {
      //reverting scrollbar change on component unmount
      scrollbarElement.setAttribute("style", "overflow: unset;");
    };
  }, []);
  1. Handle Safari Mobile

The above code will work for all browsers in desktop and mobile except Safari mobile in IOS. In safari, even though the scrollbar is hidden, the page is still scrollable.

I used the below to disable touch, which will not initiate scrolling on touch.

//add this style below to container or the backdrop of modal. 
.modalContainer {
  touch-action: none;
}
Myalgia answered 15/2, 2024 at 6:19 Comment(0)
I
-3

Umm I see there are few topics already on SO. Try this maybe?

@supports (-webkit-overflow-scrolling: touch) {
  /* CSS specific to iOS devices */ 
}

@supports not (-webkit-overflow-scrolling: touch) {
  /* CSS for other than iOS devices */ 
}

CSS media query target only iOS devices

Ileenileitis answered 20/2, 2019 at 15:18 Comment(1)
I don't need IOS specific styles hacks, I need to stop the body scrolling while a modal is open on IOS.Conventional

© 2022 - 2025 — McMap. All rights reserved.