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

100

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

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

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

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

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

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

Есть ли способ использовать исправление overscroll без плагина jQuery, но исключить мои $ ('. Scrollable') divs?

РЕДАКТИРОВАТЬ:

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

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

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

Джефф
источник
попробовал и ваш последний, но тоже не сработало
Сантьяго Ребелла
Мне удалось удержать окно просмотра от перемещения при прокрутке за конец div, явно зафиксировав событие прокрутки в родительском элементе прокручиваемого div и не позволяя ему фактически прокручиваться. Если вы используете jquery mobile, имеет смысл сделать это на уровне страницы следующим образом: $ ('div [data-role = "page"]'). On ('scroll', function (e) {e.preventDefault ();});
Кристофер Джонсон
github.com/lazd/iNoBounce творит чудеса
Дэвид Андерхилл
Я нашел этот сценарий, который решает эту проблему! :) github.com/lazd/iNoBounce
Ян Шафранек,
Зачем вам публиковать ссылку снова, если кто-то над вашим постом разместил ее 7 месяцев назад?
Денни

Ответы:

84

Это решает проблему, когда вы прокручиваете начало или конец 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();
    }
});
Тайлер Додж
источник
Это не сработает, если внутри прокручиваемой области есть iframe, и пользователь начинает прокрутку в этом iframe. Есть ли обходной путь для этого?
Timo
2
Отлично сработало - это определенно лучше, чем просто таргетинг .scrollableнапрямую (это то, что я изначально пробовал при решении этой проблемы). Если вы новичок в JavaScript и хотите простой код для удаления этих обработчиков где-нибудь в будущем, эти две строки отлично подходят для меня! $(document).off('touchmove'); И $('body').off('touchmove touchstart', '.scrollable');
Девин
У меня это сработало отлично. Большое спасибо, вы сэкономили мне часы!
marcgg
1
Это не сработает, если в div недостаточно содержимого для прокрутки. Кто-то задал отдельный вопрос, который ответил, что здесь: stackoverflow.com/q/16437182/40352
Крис,
Как я могу разрешить более одного класса «.scrollable»? он отлично работает с одним, но мне нужно сделать прокручиваемым и другой div. Спасибо!
МэВ
23

Использование отличного ответа Тайлера Доджа продолжало отставать на моем 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 исправляет некоторые сбои рендеринга ( источник ):

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}
Куба Холуй
источник
Это не сработает, если внутри прокручиваемой области есть iframe, и пользователь начинает прокрутку в этом iframe. Есть ли обходной путь для этого?
Timo
1
Кажется, идеально подходит для перетаскивания назад, но перетаскивание вниз по-прежнему перемещает сафари.
Abadaba
1
Отличное решение ... Большое спасибо :)
Aamir Shah
Это сработало для меня. Спасибо! На решение этой проблемы я трачу больше 1,5 дней.
Achintha Samindika 05
Это потрясающе, отлично сработало и избавило меня от лишнего стресса, пытаясь найти решение. Спасибо, Куба!
Леонард
12

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

$(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" />
Джонатан Тонг
источник
7

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

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });
Разработчик: Joe
источник
3
Спасибо ... Похоже, это должно работать, но это не так. Кроме того, разве он не должен быть «прокручиваемым», а не «.scrollable» (с точкой)?
Джефф
1
Похоже, что это самый глубоко вложенный элемент, который получает событие касания, поэтому вам может потребоваться проверить всех своих родителей, чтобы увидеть, находитесь ли вы в прокручиваемом div.
Кристофер Джонсон,
3
Зачем использовать document.body.addEventListener, если используется jQuery? Это по какой-то причине?
fnagel
7

Лучшее решение для этого - 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>

Элиас Фиксен
источник
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();
    }
});
Йохан
источник
Мне пришлось проверить e.originalEvent.touches [0] .pageY вместо e.originalEvent.pageY. Это сработало, но только если вы уже находитесь в конце прокручиваемого div. Когда прокрутка выполняется (например, вы очень быстро прокручиваете), она не останавливается после достижения конца прокручиваемого div.
Keen Sage
4

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

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

в то время как настольные браузеры наблюдают overflow: hidden - 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();
});
Nrutas
источник
Это очень полезно и минимальный подход, именно то, что мне нужно. Установка положения на фиксированное, с верхним: 0; слева: 0; ширина: 100%; были элементы, которых мне не хватало. Это также полезно для всплывающих меню.
bdanin 07
3

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

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

PiñaDesign
источник
1
У меня была та же проблема, что и у Джеффа, пробовал каждый ответ, ваш сработал. Спасибо!
Dominik Schreiber
Принятый ответ работал у меня только тогда, когда у div с .scrollable было достаточно содержимого, чтобы вызвать его переполнение. Если он не переполнялся, эффект «отскока» все еще существовал. Однако это работает отлично, спасибо!
Адам Маршалл,
1

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

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

$.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();
        }
    });
}
jcbdrn
источник
1

Хотя отключение всех событий «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;). Никаких специальных занятий или чего-то еще.

Николя Бувретт
источник
0

Вот решение, совместимое с zepto

    if (!$(e.target).hasClass('scrollable') && !$(e.target).closest('.scrollable').length > 0) {
       console.log('prevented scroll');
       e.preventDefault();
       window.scroll(0,0);
       return false;
    }
Кристофер Джонсон
источник
0

этот работает для меня (простой 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>
RJS
источник
0

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

$('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');
});
Шринивасан Тирупати
источник
0

Мне повезло с простым:

body {
    height: 100vh;
}

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

Лани Дурчанек
источник