IOS Safari - Как отключить прокрутку, но разрешить прокручиваемым divs нормально прокручиваться?

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

document.body.addEventListener('touchmove',function(e){
      e.preventDefault();
  });

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

Я ориентируюсь на iOS 5 и выше, только поэтому избегаю хакерских решений, таких как iScroll. Вместо этого я использую этот CSS для моих прокручиваемых div:

.scrollable {
    -webkit-overflow-scrolling: touch;
    overflow-y:auto;
}

Это работает без прокрутки документа script, но не решает проблему прокрутки div.

Без плагина jQuery есть ли способ использовать исправление переполнения, но освободить мои div (?. прокручиваемые)?

EDIT:

Я нашел что-то подходящее решение:

 // Disable overscroll / viewport moving on everything but scrollable divs
 $('body').on('touchmove', function (e) {
         if (!$('.scrollable').has($(e.target)).length) e.preventDefault();
 });

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

Ответ 1

Это решает проблему, когда вы прокручиваете начало или конец div

var selScrollable = '.scrollable';
// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});
// Uses body because jQuery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart', selScrollable, function(e) {
  if (e.currentTarget.scrollTop === 0) {
    e.currentTarget.scrollTop = 1;
  } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
    e.currentTarget.scrollTop -= 1;
  }
});
// Stops preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove', selScrollable, function(e) {
  e.stopPropagation();
});

Обратите внимание, что это не сработает, если вы хотите заблокировать прокрутку всей страницы, когда у div нет переполнения. Чтобы заблокировать это, используйте следующий обработчик событий, а не тот, который находится выше (адаптирован из этого вопроса):

$('body').on('touchmove', selScrollable, function(e) {
    // Only block default if internal div contents are large enough to scroll
    // Warning: scrollHeight support is not universal. (https://stackoverflow.com/a/15033226/40352)
    if($(this)[0].scrollHeight > $(this).innerHeight()) {
        e.stopPropagation();
    }
});

Ответ 2

Используя Tyler Dodge отличный ответ, сохраняли отставание от моего iPad, поэтому я добавил код дросселирования, теперь он довольно плавный. При прокрутке иногда происходит небольшое пропущение.

// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});

var scrolling = false;

// Uses body because jquery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart','.scrollable',function(e) {

    // Only execute the below code once at a time
    if (!scrolling) {
        scrolling = true;   
        if (e.currentTarget.scrollTop === 0) {
          e.currentTarget.scrollTop = 1;
        } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
          e.currentTarget.scrollTop -= 1;
        }
        scrolling = false;
    }
});

// Prevents preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove','.scrollable',function(e) {
  e.stopPropagation();
});

Кроме того, добавление следующих CSS исправляет некоторые ошибки рендеринга (source):

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}

Ответ 3

Сначала запретите действия по умолчанию для всего документа как обычно:

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

Затем остановите свой класс элементов от распространения до уровня документа. Это останавливает его от выполнения вышеперечисленной функции и, следовательно, e.preventDefault() не запускается:

$('.scrollable').bind('touchmove', function(e){
  e.stopPropagation();
});

Эта система кажется более естественной и менее интенсивной, чем вычисление класса при всех движениях касания. Используйте .on(), а не .bind() для динамически генерируемых элементов.

Также рассмотрите эти метатеги, чтобы предотвратить случайные события, когда вы используете свой прокручиваемый div:

<meta content='True' name='HandheldFriendly' />
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' name='viewport' />
<meta name="viewport" content="width=device-width" />

Ответ 4

Можете ли вы добавить немного больше логики в свой код отключения, чтобы убедиться, что целевой элемент не является тем, что вы хотели бы прокрутить? Что-то вроде этого:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });

Ответ 5

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

var touchStartEvent;
$('.scrollable').on({
    touchstart: function(e) {
        touchStartEvent = e;
    },
    touchmove: function(e) {
        if ((e.originalEvent.pageY > touchStartEvent.originalEvent.pageY && this.scrollTop == 0) ||
            (e.originalEvent.pageY < touchStartEvent.originalEvent.pageY && this.scrollTop + this.offsetHeight >= this.scrollHeight))
            e.preventDefault();
    }
});

Ответ 6

Лучшим решением для этого является css/html: сделайте div, чтобы обернуть ваши элементы, если у вас его нет, и установите его неподвижным и переполненным. Необязательно, установите высоту и ширину на 100%, если вы хотите, чтобы она заполнила весь экран и ничего, кроме всего экрана

#wrapper{
  height: 100%;
  width: 100%;
  position: fixed;
  overflow: hidden;
}
<div id="wrapper">
  <p>All</p>
  <p>Your</p>
  <p>Elements</p>
</div>

Ответ 7

Я искал способ предотвратить прокрутку всего тела, когда появляется всплывающее окно с прокручиваемой областью (всплывающее окно "корзины покупок" с прокручиваемым видом вашей корзины).

Я написал гораздо более элегантное решение, использующее минимальный javascript, чтобы просто переключать класс "noscroll" на ваше тело, когда у вас есть всплывающее окно или div, которые вы хотите прокрутить (а не "перемотать" весь объект страницы).

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

.noscroll {
    overflow: hidden;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
}

и этот jquery:

/* fade in/out cart popup, add/remove .noscroll from body */
$('a.cart').click(function() {
    $('nav > ul.cart').fadeToggle(100, 'linear');
    if ($('nav > ul.cart').is(":visible")) {
        $('body').toggleClass('noscroll');
    } else {
        $('body').removeClass('noscroll');
    }
});

/* close all popup menus when you click the page... */
$('body').click(function () {
    $('nav > ul').fadeOut(100, 'linear');
    $('body').removeClass('noscroll');
});

/* ... but prevent clicks in the popup from closing the popup */
$('nav > ul').click(function(event){
    event.stopPropagation();
});

Ответ 8

Я работаю на небольшом рабочем столе без jquery. Не perfert, но работает отлично (особенно если у вас есть scroll-x в scoll-y) https://github.com/pinadesign/overscroll/

Не стесняйтесь участвовать и улучшать его

Ответ 9

Это решение не требует, чтобы вы поместили прокручиваемый класс во все ваши прокручиваемые div, поэтому он более общий. Прокрутка разрешена для всех элементов, которые являются или являются дочерними элементами INPUT contenteditables и переполнения или autos.

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

$.expr[':'].scrollable = function(obj) {
    var $el = $(obj);
    var tagName = $el.prop("tagName");
    return (tagName !== 'BODY' && tagName !== 'HTML') && (tagName === 'INPUT' || $el.is("[contentEditable='true']") || $el.css("overflow").match(/auto|scroll/));
};
function preventBodyScroll() {
    function isScrollAllowed($target) {
        if ($target.data("isScrollAllowed") !== undefined) {
            return $target.data("isScrollAllowed");
        }
        var scrollAllowed = $target.closest(":scrollable").length > 0;
        $target.data("isScrollAllowed",scrollAllowed);
        return scrollAllowed;
    }
    $('body').bind('touchmove', function (ev) {
        if (!isScrollAllowed($(ev.target))) {
            ev.preventDefault();
        }
    });
}

Ответ 10

Отключение всех событий "touchmove" может показаться хорошей идеей, как только вам понадобятся другие прокручиваемые элементы на странице, это вызовет проблемы. Кроме того, если вы отключите только события "touchmove" для определенных элементов (например, тело, если вы хотите, чтобы страница была не прокручиваемой), как только она будет активирована где-либо еще, IOS вызовет неудержимое распространение в Chrome, когда URL-адрес бар.

Пока я не могу объяснить это поведение, похоже, единственный способ предотвратить, по-видимому, установить положение тела на fixed. Единственная проблема заключается в том, что вы потеряете позицию документа - это особенно раздражает, например, в модалах. Один из способов его решения - использовать эти простые функции VanillaJS:

function disableDocumentScrolling() {
    if (document.documentElement.style.position != 'fixed') {
        // Get the top vertical offset.
        var topVerticalOffset = (typeof window.pageYOffset != 'undefined') ?
            window.pageYOffset : (document.documentElement.scrollTop ? 
            document.documentElement.scrollTop : 0);
        // Set the document to fixed position (this is the only way around IOS' overscroll "feature").
        document.documentElement.style.position = 'fixed';
        // Set back the offset position by user negative margin on the fixed document.
        document.documentElement.style.marginTop = '-' + topVerticalOffset + 'px';
    }
}

function enableDocumentScrolling() {
    if (document.documentElement.style.position == 'fixed') {
        // Remove the fixed position on the document.
        document.documentElement.style.position = null;
        // Calculate back the original position of the non-fixed document.
        var scrollPosition = -1 * parseFloat(document.documentElement.style.marginTop);
        // Remove fixed document negative margin.
        document.documentElement.style.marginTop = null;
        // Scroll to the original position of the non-fixed document.
        window.scrollTo(0, scrollPosition);
    }
}

Используя это решение, вы можете иметь фиксированный документ, и любой другой элемент вашей страницы может переполняться с помощью простого CSS (например, overflow: scroll;). Нет необходимости в специальных классах или чем-то еще.

Ответ 11

Здесь zepto-совместимое решение

    if (!$(e.target).hasClass('scrollable') && !$(e.target).closest('.scrollable').length > 0) {
       console.log('prevented scroll');
       e.preventDefault();
       window.scroll(0,0);
       return false;
    }

Ответ 12

этот работает для меня (простой javascript)

var fixScroll = function (className, border) {  // className = class of scrollElement(s), border: borderTop + borderBottom, due to offsetHeight
var reg = new RegExp(className,"i"); var off = +border + 1;
function _testClass(e) { var o = e.target; while (!reg.test(o.className)) if (!o || o==document) return false; else o = o.parentNode; return o;}
document.ontouchmove  = function(e) { var o = _testClass(e); if (o) { e.stopPropagation(); if (o.scrollTop == 0) { o.scrollTop += 1; e.preventDefault();}}}
document.ontouchstart = function(e) { var o = _testClass(e); if (o && o.scrollHeight >= o.scrollTop + o.offsetHeight - off) o.scrollTop -= off;}
}

fixScroll("fixscroll",2); // assuming I have a 1px border in my DIV

HTML:

<div class="fixscroll" style="border:1px gray solid">content</div>

Ответ 13

Попробуйте это. Он будет работать идеально.

$('body.overflow-hidden').delegate('#skrollr-body','touchmove',function(e){
    e.preventDefault();
    console.log('Stop skrollrbody');
}).delegate('.mfp-auto-cursor .mfp-content','touchmove',function(e){
    e.stopPropagation();
    console.log('Scroll scroll');
});

Ответ 14

У меня была удивительная удача с простым:

body {
    height: 100vh;
}

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