31

The Problem:

I know this question has been asked a dozen of times, but none of the solutions on those questions worked for me.

I want the body element on iOS 13 safari to not scroll. This means no scrolling, and no elastic bounce (overflow-scrolling) effect.

I have two elements next to each other on which I have set overflow: scroll;, those should scroll, just the body around them shouldn't.

All the solutions I've tried just don't work in progressive webapps that have the following tag inside their head and are saved to the homescreen.

<meta name="apple-mobile-web-app-capable" content="yes">

Solutions I've tried:

try 1: setting overflow hidden on body and/or html. Didn't work for iOS 13 safari.

https://stackoverflow.com/a/18037511/10551293

html {  
    position: relative;
    overflow: hidden;
    height: 100%;
}

body {
    position: relative;
    overflow: hidden;
    height: 100%;
}

does nothing in iOS 13 safari but works in macOS safari and Firefox.

try 2: setting position fixed on the body. Doesn't work for me because when the user scrolls, the body doesn't but the scrolling still prevents my two inner elements from scrolling while the overflow-bounce is animating.

https://stackoverflow.com/a/47874599/10551293

body {
    position: fixed;
}

only puts the body over the scrolling of the page. The scrolling (overflow-scrolling) happens through the fixed body...

try 3: preventing the default on touch moved. Didn't work (is an older solution...).

https://stackoverflow.com/a/49853392/10551293

document.addEventListener("touchmove", function (e) {
    e.preventDefault();
}, { passive: false });

does nothing as I can tell. Not in safari nor in Firefox.

try 4: preventing the default on scrolling of the window and setting the scroll position back to 0. Is not viable because of buggy animations.

window.addEventListener("scroll", (e) => {
    e.preventDefault();
    window.scrollTo(0, 0);
});

sets the scroll position back to 0 but the overflow-scrolling still applies which ends up in a buggy behaviour.


A snippet that demonstrates it:

To test it yourself, save the snippet below as an html file, and save it to the homescreen on an iPad (or iPad simulator). The body suddenly becomes scrollable when saved to the homescreen.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <meta name="apple-mobile-web-app-capable" content="yes">
</head>
<body>
    <style>
        body, html {
            position: relative;
            overflow: hidden;
            height: 100%;
            width: 100%;
        }
        body {
            margin: 0;
            display: flex;
            flex-direction: column;
        }

        nav, footer {
            width: 100%;
            height: 5rem;
            background: blue;
            flex-shrink: 0;
        }

        main {
            display: flex;
            height: 0;
            flex-grow: 1;
            padding: 2rem;
        }

        section {
            width: 50%;
            overflow: scroll;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        div {
            flex-shrink: 0;
            width: 25%;
            height: 18rem;
            margin: 1rem;
            background: red;
        }
    </style>

    <nav></nav>

    <main>
        <section>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </section>
        
        <section>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </section>
    </main>

    <footer></footer>
</body>
</html>

None of them worked in an acceptable way for me, now how do I do this so it works properly in iOS 13 safari (when saved as a PWA to the home screen)?

swift-lynx
  • 3,219
  • 3
  • 26
  • 45
  • I think you need to set height fixed to make overflow hidden work, like this: `html { position: relative; overflow: hidden; height: 90vh; }` – Anshul Gupta Dec 05 '19 at 11:58
  • Didn't work either, I've tried that too... – swift-lynx Dec 05 '19 at 12:59
  • Can you add a fiddle or something for preview of elements? I'm not able to images your elements – Anshul Gupta Dec 05 '19 at 13:07
  • 1
    It wasn't clear in my question, it only doesn't work when the page is saved as a web app to the home screen and if it has the following meta tag: – swift-lynx Dec 05 '19 at 13:24
  • And I added a code snippet. – swift-lynx Dec 05 '19 at 13:31
  • Try this answer https://stackoverflow.com/questions/29894997/prevent-ios-bounce-without-disabling-scroll-ability or this plugin: https://github.com/lazd/iNoBounce – Anshul Gupta Dec 06 '19 at 07:42
  • @swiftlynx did you find a acceptable solution or a workaround? I have the same problem... – Niklas Grewe Jun 19 '20 at 15:23
  • @Niklas Grewe I combined try 2 and try 4 from my question. The fixed body shows no overflow scrolling and the scroll reset prevents the long animation of the overflow scrolling in the background. It's really ugly but it kinda works. – swift-lynx Jun 26 '20 at 06:27

9 Answers9

13

I combined try 2 and try 4 from the question. The fixed body shows no overflow scrolling and the scroll reset prevents the long animation of the overflow scrolling in the background. It's really ugly but it kinda works.

body {
  position: fixed;
}
window.addEventListener("scroll", (e) => {
  e.preventDefault();
  window.scrollTo(0, 0);
});
swift-lynx
  • 3,219
  • 3
  • 26
  • 45
7
function unlockScroll () {
    const scrollY = this.body.style.top;
    document.body.style.position = '';
    document.body.style.top = '';
    document.body.style.left = '';
    document.body.style.right = '';
    window.scrollTo(0, parseInt(scrollY || '0') * -1);
};

function lockScroll () {
    document.body.style.position = 'fixed';
    document.body.style.top = `-${window.scrollY}px`;
    document.body.style.left = '0';
    document.body.style.right = '0';
};
Blallo
  • 450
  • 3
  • 11
  • This locks the scrolling on my two inner elements too. – swift-lynx Dec 11 '19 at 12:31
  • I confirm this answer – zEn feeLo Jul 17 '20 at 14:34
  • @swiftlynx did you put overflow on your inner elements? – Blallo Jul 22 '20 at 10:47
  • This works on both Android 10 (Chrome) and iOS 13.7 (Safari). Thanks a billion! – Miros Oct 01 '20 at 19:39
  • For everyone who still needs this, i made an improved version available on github with MIT license and even an npm package: https://github.com/8lall0/body-lock. This takes into account vertical and horizontal overflow, and keeps the scrollbars to prevent the "jump" that a page can have if you remove the scrollbar, plus it adds a class to body if you need additional styling when you lock the body. – Blallo Oct 18 '21 at 10:07
5

Just add a touch-action:none to the body in CSS:

body{

touch-action:none;

}

Disembleergon
  • 187
  • 4
  • 5
3

In my case (app requires dragging to arrange elements) setting touch-action to none worked to prevent scrolling when dragging certain elements.

e.g.

draggableElement.css('touch-action', 'none') // disable actions
draggableElement.css('touch-action', 'auto') // restore actions
Georgi Yankov
  • 411
  • 2
  • 9
0

https://github.com/willmcpo/body-scroll-lock

I am unable to test at the moment, but this seems worth a try.

Raine Revere
  • 30,985
  • 5
  • 40
  • 52
0

I had a similar issue before and so far using the below approach has worked best:

  • first, enable scrolling on your content container by applying -webkit-overflow-scrolling: touch

  • second, apply the following rules:

/* this part makes sure there is nowhere left to scroll */

html {
    position: static;
    overflow-y: hidden;
    height: 100%;
    max-height: 100%;
}

/* all properties don't necessarily need to be applied on both elements,
this is only used to override any existing code */

body {
    overflow: hidden;
    height: 100%;
    max-height: 100%;
}
Petrolea
  • 105
  • 8
0
window.addEventListener('touchend', _ => {
    window.scrollTo(0,0)
});

This will snap the body back to 0,0 after the user lets go of the body, allowing the user to immediately scroll down without any weird animations besides snapping back right away. I tried it with a smooth scroll animation but it doesn't always animate fast enough. This will prevent the screen lock that occurs when the body scrolls from the bounce elastic scroll on that iPhone and is only triggered when the user lets go of the pull.

StarPlayrX
  • 33
  • 3
0

This worked for me https://stackoverflow.com/a/41601290/10763156

please, add -webkit-overflow-scrolling: touch; to the #overlay element.

And add please this javascript code at the end of the body tag:


(function () {
    var _overlay = document.getElementById('overlay');
    var _clientY = null; // remember Y position on touch start

    _overlay.addEventListener('touchstart', function (event) {
        if (event.targetTouches.length === 1) {
            // detect single touch
            _clientY = event.targetTouches[0].clientY;
        }
    }, false);

    _overlay.addEventListener('touchmove', function (event) {
        if (event.targetTouches.length === 1) {
            // detect single touch
            disableRubberBand(event);
        }
    }, false);

    function disableRubberBand(event) {
        var clientY = event.targetTouches[0].clientY - _clientY;

        if (_overlay.scrollTop === 0 && clientY > 0) {
            // element is at the top of its scroll
            event.preventDefault();
        }

        if (isOverlayTotallyScrolled() && clientY < 0) {
            //element is at the top of its scroll
            event.preventDefault();
        }
    }

    function isOverlayTotallyScrolled() {
        // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
        return _overlay.scrollHeight - _overlay.scrollTop <= _overlay.clientHeight;
    }
}())


nbl7
  • 511
  • 4
  • 14
0

For me worked:

import { useEffect } from "react";

export const useOverscrollHandler = () => {
  useEffect(() => {
    const onScroll = (e: any) => {
      e.preventDefault();
      window.scrollTo(0, 0);
    };

    window.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
  }, []);
};