iOS Safari - Як відключити overscroll, але дозволити прокручувати divs для прокрутки нормально?


100

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

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

Це чудово працює, щоб вимкнути overscroll, але в моєму додатку є кілька прокручуваних дівок, і наведений вище код запобігає їх прокрученню .

Я націлююсь лише на iOS 5 і вище, щоб уникнути нерозважливих рішень, таких як iScroll. Натомість я використовую цей CSS для моїх прокручуваних дівів:

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

Це працює без скрипту перекручування документа, але не вирішує проблему прокрутки діва.

Без плагіна jQuery, чи можна скористатися виправленням overscroll, але вилучити мої диски $ ('. Scrolllable')?

Редагувати:

Я знайшов щось гідне рішення:

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

Вікно перегляду все ще переміщується, коли ви прокручуєте повз початок або кінець розділу. Я також хотів би знайти спосіб відключити це.


спробував і ваш остаточний, але теж не спрацював
Сантьяго Ребелла

Мені вдалося утримати перегляд вікна перегляду, коли ви прокручуєте повз кінець діва, чітко фіксуючи подію прокрутки на батьківському ділі, що прокручується, і не дозволяючи йому фактично прокручувати. Якщо ви використовуєте jquery mobile, це має сенс зробити це на рівні сторінки так: $ ('div [data-role = "page"]') on ('прокрутка', функція (e) {e.preventDefault ();});
Крістофер Джонсон


Я знайшов цей скрипт, який вирішив цю проблему! :) github.com/lazd/iNoBounce
Jan Šafránek

Чому ви знову надсилаєте посилання, якщо хтось над вашою публікацією розмістив її на 7 місяців раніше?
Денні

Відповіді:


84

Це вирішує проблему, коли ви прокручуєте повз початок або кінець розділу

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. Чи існує рішення для цього?
Тимо

2
Працював чудово - це, безумовно, краще, ніж просто орієнтуватися .scrollableбезпосередньо (саме це я спочатку намагався вирішити цю проблему). Якщо ви JavaScript noob і хочете, щоб простий код видалити ці обробники десь за лінією, ці дві лінії для мене чудово працюють! $(document).off('touchmove'); І $('body').off('touchmove touchstart', '.scrollable');
Девін

Це прекрасно працювало для мене. Дуже дякую, ти врятував мені години!
marcgg

1
Це не працює, якщо в розділі недостатньо вмісту для прокрутки. Хтось поставив окреме запитання, який відповів, що тут: stackoverflow.com/q/16437182/40352
Кріс

Як я можу дозволити більше одного ".scrollable" класу? це добре працює з одним, але мені потрібно зробити ще один діг, який можна прокручувати. Дякую!
MeV

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. Чи існує рішення для цього?
Тимо

1
Здається, це ідеально підходить для перетягування назад, але перетягування вниз все одно перемістить сафарі.
Абадаба

1
Дивовижне рішення ... Дякую велике :)
Аамір Шах

Це працювало для мене. Дякую! На вирішення цього питання я витрачаю більше 1,5 днів.
Ахінта Саміндіка

Це дивовижно, працювало чудово і врятувало мене подальший стрес, намагаючись розробити рішення. Дякую Кубе!
Леонард

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

3
Дякую ... Здається, це має працювати, але це не так. Крім того, чи не повинно бути "прокручуватися", а не ".scrollable" (з крапкою)?
Джефф

1
Здається, що це найбільш глибоко вкладений елемент, який отримує сенсорну подію, тому вам, можливо, доведеться перевірити всіх своїх батьків, щоб побачити, чи перебуваєте ви в діві, який прокручується.
Крістофер Джонсон

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. Це спрацювало, але лише у тому випадку, якщо ви вже в кінці розділу прокрутки. Коли прокрутка триває (наприклад, ви прокручували дуже швидко), вона не припиняється, як тільки буде досягнуто кінця прокручуваного поділу.
Кін Сейдж

4

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

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

в той час як браузери настільних пристроїв спостерігають переповнення: приховано - 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();
});

Це дуже корисно, і мінімальний підхід, саме те, що мені було потрібно. Встановлення положення фіксованим, зверху: 0; зліва: 0; ширина: 100%; були елементами, яких я бракував. Це також корисно для вилітних меню.
бданін

3

Я трохи працюю навколо без жодного запиту. Не perfert, але працює добре (особливо якщо у вас є scroll-x у scoll-y) https://github.com/pinadesign/overscroll/

Не соромтеся брати участь та вдосконалювати її


1
Мав те саме питання, що і Джефф, спробував кожну відповідь, твій працював. Дякую!
Домінік Шрайбер

Прийнята відповідь працювала для мене лише тоді, коли в div з .scrollable було достатньо вмісту, щоб викликати його переповнення. Якщо воно не переповнилося, ефект "відскоку" все ще існував. Однак це прекрасно працює, дякую!
Адам Маршалл

1

Це рішення не вимагає, щоб ви клали прокручуваний клас на всі свої прокручувані диви, тому більш загальним. Прокрутка дозволена на всіх елементах, які є або є дочірніми елементами INPUT, що містять вміст, і переливають прокрутку або авто.

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

$.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();
        }
    });
}

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>

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

Це чудово працює для відключення розкрутки для спливаючих вікон або меню, і не змушує бари браузера відображатися як при використанні позиції: фіксовано. АЛЕ - потрібно зберегти позицію прокрутки перед встановленням фіксованої висоти та відновити її, приховуючи спливаюче вікно, інакше браузер прокрутить доверху.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.