Comment empêcher le défilement de documents mais permettre le défilement à l'intérieur des éléments div sur les sites web pour iOS et Android?

j'ai créé un site Web avec jQueryMobile pour iOS et Android.

Je ne veux pas que le document lui-même défile. Au lieu de cela, juste une zone (un élément <div> ) devrait être scrollable (via la propriété css overflow-y:scroll ).

donc j'ai désactivé le défilement de document via:

$(document).bind("touchstart", function(e){
    e.preventDefault();
});

$(document).bind("touchmove", function(e){
    e.preventDefault();
});

mais cela désactivera également le défilement pour tous les autres éléments du document, peu importe si overflow:scroll est défini ou non.

comment résoudre ce problème?

30
demandé sur Timo 2013-02-28 14:04:11

9 réponses

Que Diriez - vous de cette CSS only solution:

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

body obtient position: fixed; et tous les autres éléments que vous souhaitez un overflow: scroll; . Fonctionne sur mobile Chrome (WebKit)/Firefox 19/Opera 12.

vous verrez aussi mes différentes tentatives vers une solution jQuery. Mais dès que vous liez touchmove / touchstart au document, il entrave le défilement dans le div d'enfant peu importe si non lié ou non.

avertissement: les Solutions à ce problème sont à bien des égards fondamentalement pas très agréable UX-Sage! Vous ne saurez jamais quelle est la taille exacte du viewport de vos visiteurs ou quelle taille de police ils utilisent (style client user-agent), donc il pourrait facilement être, que le contenu important est caché pour eux dans votre document.

25
répondu Volker E. 2018-07-02 15:46:36

finalement, je l'ai fait marcher. Très 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);
});
11
répondu Timo 2017-11-23 08:52:09

peut-être ai-je mal compris la question, mais si j'ai raison:

vous ne voulez pas pouvoir faire défiler sauf un certain élément donc vous:

$(document).bind("touchmove", function(e){
    e.preventDefault();
});

empêcher tout ce qui se trouve dans le document.


pourquoi ne pas simplement arrêter l'événement bouillonnant sur l'élément où vous souhaitez faire défiler? (PS: vous n'avez pas à empêcher touchstart -> si vous utilisez touch start pour sélectionner des éléments à la place des clics qui sont empêchés aussi bien, le mouvement de toucher est seulement nécessaire parce qu'alors il est réellement tracer le mouvement)

$('#element').on('touchmove', function (e) {
     e.stopPropagation();
});

maintenant sur l'élément CSS

#element {
   overflow-y: scroll; // (vertical) 
   overflow-x: hidden; // (horizontal)
}

Si vous êtes sur un appareil mobile, vous pouvez même aller plus loin. Vous pouvez forcer le défilement accéléré du matériel (bien que tous les navigateurs mobiles ne le supportent pas);

Browser Overflow scroll:

Android Browser Yes
Blackberry Browser  Yes
Chrome for Mobile   Yes
Firefox Mobile  Yes
IE Mobile           Yes
Opera Mini          No
Opera Mobile    Kinda
Safari          Yes

#element.nativescroll {
    -webkit-overflow-scrolling: touch;
}

normal:

<div id="element"></div>

feel native:

<div id="element" class="nativescroll"></div>
10
répondu Danillo Felixdaal 2014-05-17 20:11:16

je cherchais une solution qui ne nécessitait pas d'appeler des zones spécifiques qui devraient défiler. En regroupant quelques ressources, voici ce qui a fonctionné pour moi:

    // 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;
            }
        }
    });

ce code exige jQuery.

Sources:


mise à jour

j'ai eu besoin d'une version JavaScript vanille de ceci, donc ce qui suit est une version modifiée. J'ai implémenté un correcteur de marge et quelque chose qui permet explicitement de cliquer sur input/textareas (j'ai rencontré des problèmes avec ce le projet, j'ai utilisé...il peut ne pas être nécessaire pour votre projet). Gardez à l'esprit que c'est du code ES6.

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);
3
répondu David Sinclair 2017-05-23 12:02:35

Voici une solution que j'utilise:

$scrollElement est l'élément scroll, $scrollMask est un div avec le style position: fixed; top: 0; bottom: 0; . Le z-index de $scrollMask est plus petit que $scrollElement.

$scrollElement.on('touchmove touchstart', function (e) {
    e.stopPropagation();
});
$scrollMask.on('touchmove', function(e) {
    e.stopPropagation();
    e.preventDefault();
});
2
répondu John Xiao 2014-08-13 07:44:47

dans mon cas, j'ai un corps scrollable et un menu flottant scrollable dessus. Les deux doivent être contrôlables, mais j'ai dû empêcher le corps de défiler lorsque "floating menu" (position:fixe) a reçu des événements tactiles et défilait et il a atteint le haut ou le bas. Par défaut, le navigateur a commencé à faire défiler le corps.

j'ai vraiment aimé jimmont la réponse de , mais malheureusement il ne fonctionne pas bien sur tous les appareils et navigateurs, surtout avec un rapide et long glissement de doigt.

j'ai fini par utiliser MOMENTUM SCROLLING USING JQUERY (hnldesign.nl) sur le menu flottant, ce qui empêche le défilement par défaut du navigateur et anime ensuite le défilement lui-même. J'inclus ce code ici pour l'exhaustivité:

/**
 * jQuery inertial Scroller v1.5
 * (c)2013 hnldesign.nl
 * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
 **/
/*jslint browser: true*/
/*global $, jQuery*/

/* SETTINGS */
var i_v = {
    i_touchlistener     : '.inertialScroll',         // element to monitor for touches, set to null to use document. Otherwise use quotes. Eg. '.myElement'. Note: if the finger leaves this listener while still touching, movement is stopped.
    i_scrollElement     : '.inertialScroll',         // element (class) to be scrolled on touch movement
    i_duration          : window.innerHeight * 1.5, // (ms) duration of the inertial scrolling simulation. Devices with larger screens take longer durations (phone vs tablet is around 500ms vs 1500ms). This is a fixed value and does not influence speed and amount of momentum.
    i_speedLimit        : 1.2,                      // set maximum speed. Higher values will allow faster scroll (which comes down to a bigger offset for the duration of the momentum scroll) note: touch motion determines actual speed, this is just a limit.
    i_handleY           : true,                     // should scroller handle vertical movement on element?
    i_handleX           : true,                     // should scroller handle horizontal movement on element?
    i_moveThreshold     : 100,                      // (ms) determines if a swipe occurred: time between last updated movement @ touchmove and time @ touchend, if smaller than this value, trigger inertial scrolling
    i_offsetThreshold   : 30,                       // (px) determines, together with i_offsetThreshold if a swipe occurred: if calculated offset is above this threshold
    i_startThreshold    : 5,                        // (px) how many pixels finger needs to move before a direction (horizontal or vertical) is chosen. This will make the direction detection more accurate, but can introduce a delay when starting the swipe if set too high
    i_acceleration      : 0.5,                      // increase the multiplier by this value, each time the user swipes again when still scrolling. The multiplier is used to multiply the offset. Set to 0 to disable.
    i_accelerationT     : 250                       // (ms) time between successive swipes that determines if the multiplier is increased (if lower than this value)
};
/* stop editing here */

//set some required vars
i_v.i_time  = {};
i_v.i_elem  = null;
i_v.i_elemH = null;
i_v.i_elemW = null;
i_v.multiplier = 1;

// Define easing function. This is based on a quartic 'out' curve. You can generate your own at http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
if ($.easing.hnlinertial === undefined) {
    $.easing.hnlinertial = function (x, t, b, c, d) {
        "use strict";
        var ts = (t /= d) * t, tc = ts * t;
        return b + c * (-1 * ts * ts + 4 * tc + -6 * ts + 4 * t);
    };
}

$(i_v.i_touchlistener || document)
    .on('touchstart touchmove touchend', function (e) {
        "use strict";
        //prevent default scrolling
        e.preventDefault();
        //store timeStamp for this event
        i_v.i_time[e.type]  = e.timeStamp;
    })
    .on('touchstart', function (e) {
        "use strict";
        this.tarElem = $(e.target);
        this.elemNew = this.tarElem.closest(i_v.i_scrollElement).length > 0 ? this.tarElem.closest(i_v.i_scrollElement) : $(i_v.i_scrollElement).eq(0);
        //dupecheck, optimizes code a bit for when the element selected is still the same as last time
        this.sameElement = i_v.i_elem ? i_v.i_elem[0] == this.elemNew[0] : false;
        //no need to redo these if element is unchanged
        if (!this.sameElement) {
            //set the element to scroll
            i_v.i_elem = this.elemNew;
            //get dimensions
            i_v.i_elemH = i_v.i_elem.innerHeight();
            i_v.i_elemW = i_v.i_elem.innerWidth();
            //check element for applicable overflows and reevaluate settings
            this.i_scrollableY      = !!((i_v.i_elemH < i_v.i_elem.prop('scrollHeight') && i_v.i_handleY));
            this.i_scrollableX    = !!((i_v.i_elemW < i_v.i_elem.prop('scrollWidth') && i_v.i_handleX));
        }
        //get coordinates of touch event
        this.pageY      = e.originalEvent.touches[0].pageY;
        this.pageX      = e.originalEvent.touches[0].pageX;
        if (i_v.i_elem.is(':animated') && (i_v.i_time.touchstart - i_v.i_time.touchend) < i_v.i_accelerationT) {
            //user swiped while still animating, increase the multiplier for the offset
            i_v.multiplier += i_v.i_acceleration;
        } else {
            //else reset multiplier
            i_v.multiplier = 1;
        }
        i_v.i_elem
            //stop any animations still running on element (this enables 'tap to stop')
            .stop(true, false)
            //store current scroll positions of element
            .data('scrollTop', i_v.i_elem.scrollTop())
            .data('scrollLeft', i_v.i_elem.scrollLeft());
    })
    .on('touchmove', function (e) {
        "use strict";
        //check if startThreshold is met
        this.go = (Math.abs(this.pageX - e.originalEvent.touches[0].pageX) > i_v.i_startThreshold || Math.abs(this.pageY - e.originalEvent.touches[0].pageY) > i_v.i_startThreshold);
    })
    .on('touchmove touchend', function (e) {
        "use strict";
        //check if startThreshold is met
        if (this.go) {
            //set animpar1 to be array
            this.animPar1 = {};
            //handle events
            switch (e.type) {
            case 'touchmove':
                this.vertical       = Math.abs(this.pageX - e.originalEvent.touches[0].pageX) < Math.abs(this.pageY - e.originalEvent.touches[0].pageY); //find out in which direction we are scrolling
                this.distance       = this.vertical ? this.pageY - e.originalEvent.touches[0].pageY : this.pageX - e.originalEvent.touches[0].pageX; //determine distance between touches
                this.acc            = Math.abs(this.distance / (i_v.i_time.touchmove - i_v.i_time.touchstart)); //calculate acceleration during movement (crucial)
                //determine which property to animate, reset animProp first for when no criteria is matched
                this.animProp       = null;
                if (this.vertical && this.i_scrollableY) { this.animProp = 'scrollTop'; } else if (!this.vertical && this.i_scrollableX) { this.animProp = 'scrollLeft'; }
                //set animation parameters
                if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance; }
                this.animPar2       = { duration: 0 };
                break;
            case 'touchend':
                this.touchTime      = i_v.i_time.touchend - i_v.i_time.touchmove; //calculate touchtime: the time between release and last movement
                this.i_maxOffset    = (this.vertical ? i_v.i_elemH : i_v.i_elemW) * i_v.i_speedLimit; //(re)calculate max offset
                //calculate the offset (the extra pixels for the momentum effect
                this.offset         = Math.pow(this.acc, 2) * (this.vertical ? i_v.i_elemH : i_v.i_elemW);
                this.offset         = (this.offset > this.i_maxOffset) ? this.i_maxOffset : this.offset;
                this.offset         = (this.distance < 0) ? -i_v.multiplier * this.offset : i_v.multiplier * this.offset;
                //if the touchtime is low enough, the offset is not null and the offset is above the offsetThreshold, (re)set the animation parameters to include momentum
                if ((this.touchTime < i_v.i_moveThreshold) && this.offset !== 0 && Math.abs(this.offset) > (i_v.i_offsetThreshold)) {
                    if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance + this.offset; }
                    this.animPar2   = { duration: i_v.i_duration, easing : 'hnlinertial', complete: function () {
                        //reset multiplier
                        i_v.multiplier = 1;
                    }};
                }
                break;
            }

            // run the animation on the element
            if ((this.i_scrollableY || this.i_scrollableX) && this.animProp) {
                i_v.i_elem.stop(true, false).animate(this.animPar1, this.animPar2);
            }
        }
    });

une autre observation: j'ai également essayé diverses combinaisons de E. stopPropagation () sur menu div et E. preventDefault () sur window/body à l'événement touchmove, mais sans succès, j'ai seulement réussi à empêcher le défilement que je voulais et pas de défilement, je ne voulais pas. J'ai aussi essayé d'avoir un div sur tout le document, avec un index en z entre document et menu, visible seulement entre touchstart et touchend, mais il n'a pas reçu l'événement touchmove (parce qu'il était sous menu div).

2
répondu Miha Pirnat 2017-05-23 12:34:11

Voici une solution qui utilise jQuery pour les événements.

var stuff = {};
$('#scroller').on('touchstart',stuff,function(e){
  e.data.max = this.scrollHeight - this.offsetHeight;
  e.data.y = e.originalEvent.pageY;
}).on('touchmove',stuff,function(e){
  var dy = e.data.y - e.originalEvent.pageY;
  // if scrolling up and at the top, or down and at the bottom
  if((dy < 0 && this.scrollTop < 1)||(dy > 0 && this.scrollTop >= e.data.max)){
    e.preventDefault();
  };
});
0
répondu jimmont 2013-07-31 19:19:56

positionnez D'abord l'innerScroller où vous voulez à l'écran et fixez ensuite l'outerScroller en mettant css à 'hidden'. Lorsque vous souhaitez restaurer, vous pouvez la définir à 'auto' ou 'défilement', celui que vous avez utilisé précédemment.

0
répondu Seraj Ahmad 2015-05-27 18:13:18

Voici mon application qui fonctionne sur les appareils tactiles et les ordinateurs portables.

function ScrollManager() {
    let startYCoord;

    function getScrollDiff(event) {
        let delta = 0;

        switch (event.type) {
            case 'mousewheel':
                delta = event.wheelDelta ? event.wheelDelta : -1 * event.deltaY;
                break;
            case 'touchstart':
                startYCoord = event.touches[0].clientY;
                break;
            case 'touchmove': {
                const yCoord = event.touches[0].clientY;

                delta = yCoord - startYCoord;
                startYCoord = yCoord;
                break;
            }
        }

        return delta;
    }

    function getScrollDirection(event) {
        return getScrollDiff(event) >= 0 ? 'UP' : 'DOWN';
    }

    function blockScrollOutside(targetElement, event) {
        const { target } = event;
        const isScrollAllowed = targetElement.contains(target);
        const isTouchStart = event.type === 'touchstart';

        let doScrollBlock = !isTouchStart;

        if (isScrollAllowed) {
            const isScrollingUp = getScrollDirection(event) === 'UP';
            const elementHeight = targetElement.scrollHeight - targetElement.offsetHeight;

            doScrollBlock =
                doScrollBlock &&
                ((isScrollingUp && targetElement.scrollTop <= 0) ||
                    (!isScrollingUp && targetElement.scrollTop >= elementHeight));
        }

        if (doScrollBlock) {
            event.preventDefault();
        }
    }

    return {
        blockScrollOutside,
        getScrollDirection,
    };
}

const scrollManager = ScrollManager();
const testBlock = document.body.querySelector('.test');

function handleScroll(event) {
  scrollManager.blockScrollOutside(testBlock, event);
}

window.addEventListener('scroll', handleScroll);
window.addEventListener('mousewheel', handleScroll);
window.addEventListener('touchstart', handleScroll);
window.addEventListener('touchmove', handleScroll);
.main {
   border: 1px solid red;
   height: 200vh;
 }
 
 .test {
   border: 1px solid green;
   height: 300px;
   width: 300px;
   overflow-y: auto;
   position: absolute;
   top: 100px;
   left: 50%;
 }
 
 .content {
   height: 100vh;
 }
<div class="main">
  <div class="test">
    <div class="content"></div>
  </div>
</div>
0
répondu Yekver 2018-02-28 16:32:53