How to prevent document scrolling but allow scrolling inside div elements on websites for iOS and Android?

后端 未结 11 1459
南笙
南笙 2020-12-04 10:43

I created a website with jQueryMobile for iOS and Android.

I don\'t want the document itself to scroll. Instead, just an area (a

element) sh
相关标签:
11条回答
  • 2020-12-04 11:27

    I was looking for a solution that did not require calling out specific areas that should scroll. Piecing together a few resources, here is what worked for me:

        // Detects if element has scroll bar
        $.fn.hasScrollBar = function() {
            return this.get(0).scrollHeight > this.outerHeight();
        }
    
        $(document).on("touchstart", function(e) {
            var $scroller;
            var $target = $(e.target);
    
            // Get which element could have scroll bars
            if($target.hasScrollBar()) {
                $scroller = $target;
            } else {
                $scroller = $target
                    .parents()
                    .filter(function() {
                        return $(this).hasScrollBar();
                    })
                    .first()
                ;
            }
    
            // Prevent if nothing is scrollable
            if(!$scroller.length) {
                e.preventDefault();
            } else {
                var top = $scroller[0].scrollTop;
                var totalScroll = $scroller[0].scrollHeight;
                var currentScroll = top + $scroller[0].offsetHeight;
    
                // If at container edge, add a pixel to prevent outer scrolling
                if(top === 0) {
                    $scroller[0].scrollTop = 1;
                } else if(currentScroll === totalScroll) {
                    $scroller[0].scrollTop = top - 1;
                }
            }
        });
    

    This code requires jQuery.

    Sources:

    • this post
    • https://github.com/luster-io/prevent-overscroll
    • How can I check if a scrollbar is visible?
    • Jquery check if any parent div have scroll bar

    Update

    I needed a vanilla JavaScript version of this, so the following is a modified version. I implemented a margin-checker and something that explicitly allows input/textareas to be clickable (I was running into issues with this on the project I used it on...it may not be necessary for your project). Keep in mind this is ES6 code.

    const preventScrolling = e => {
        const shouldAllowEvent = element => {
            // Must be an element that is not the document or body
            if(!element || element === document || element === document.body) {
                return false;
            }
    
            // Allow any input or textfield events
            if(['INPUT', 'TEXTAREA'].indexOf(element.tagName) !== -1) {
                return true;
            }
    
            // Get margin and outerHeight for final check
            const styles = window.getComputedStyle(element);
            const margin = parseFloat(styles['marginTop']) +
                parseFloat(styles['marginBottom']);
            const outerHeight = Math.ceil(element.offsetHeight + margin);
    
            return (element.scrollHeight > outerHeight) && (margin >= 0);
        };
    
        let target = e.target;
    
        // Get first element to allow event or stop
        while(target !== null) {
            if(shouldAllowEvent(target)) {
                break;
            }
    
            target = target.parentNode;
        }
    
        // Prevent if no elements
        if(!target) {
            e.preventDefault();
        } else {
            const top = target.scrollTop;
            const totalScroll = target.scrollHeight;
            const currentScroll = top + target.offsetHeight;
    
            // If at container edge, add a pixel to prevent outer scrolling
            if(top === 0) {
                target.scrollTop = 1;
            } else if(currentScroll === totalScroll) {
                target.scrollTop = top - 1;
            }
        }
    };
    
    document.addEventListener('touchstart', preventScrolling);
    document.addEventListener('mousedown', preventScrolling);
    
    0 讨论(0)
  • 2020-12-04 11:29

    How about this CSS only solution:

    https://jsfiddle.net/Volker_E/jwGBy/24/

    body gets position: fixed; and every other element you wish an overflow: scroll;. Works on mobile Chrome (WebKit)/Firefox 19/Opera 12.

    You'll also see my various attempts towards a jQuery solution. But as soon as you're binding touchmove/touchstart to document, it hinders scrolling in the child div no matter if unbinded or not.

    Disclaimer: Solutions to this problem are in many ways basically not very nice UX-wise! You'll never know how big the viewport of your visitors exactly is or which font-size they are using (client user-agent style like), therefore it could easily be, that important content is hidden to them in your document.

    0 讨论(0)
  • 2020-12-04 11:30

    Finally, I got it to work. Really simple:

    var $layer = $("#layer");
    $layer.bind('touchstart', function (ev) {
        var $this = $(this);
        var layer = $layer.get(0);
    
        if ($this.scrollTop() === 0) $this.scrollTop(1);
        var scrollTop = layer.scrollTop;
        var scrollHeight = layer.scrollHeight;
        var offsetHeight = layer.offsetHeight;
        var contentHeight = scrollHeight - offsetHeight;
        if (contentHeight == scrollTop) $this.scrollTop(scrollTop-1);
    });
    
    0 讨论(0)
  • 2020-12-04 11:33

    First position the innerScroller wherever you want on the screen and then fix outerScroller by setting it css to 'hidden'. When you want to restore it you can set it back to 'auto' or 'scroll', whichever you used previously.

    0 讨论(0)
  • 2020-12-04 11:34

    This is what worked for me for Android and IOS devices.

    Imagine that we have a div class="backdrop"> element that we don't want to be scrolled, ever. But we want to be able to scroll over an element that is on top of this backdrop.

    function handleTouchMove(event) {
        const [backdrop] = document.getElementsByClassName('backdrop');
        const isScrollingBackdrop = backdrop === event.target;
    
        isScrollingBackdrop ? event.preventDefault() : event.stopPropagation();
    }
    
    window.addEventListener('touchmove', handleTouchMove, { passive: false });
    

    So, we listen to the touchmove event, if we're scrolling over the backdrop, we prevent it. If we're scrolling over something else, we allow it but stop its propagation so it doesn't scroll also the backdrop.

    Of course this is pretty basic and can be re-worked and expanded a lot, but this is what fixed my issue in a VueJs2 project.

    Hope it helps! ;)

    0 讨论(0)
提交回复
热议问题