отключение быстрой прокрутки только для html, но сохранение для элементов с переполнением: прокрутка

Я создаю полноэкранное веб-приложение, в котором будут некоторые модули/виджеты, использующие новые функции iOS 5 overflow:scroll. Я хочу отключить этот «прыгающий» эффект при прокрутке html/body (поскольку он полноэкранный), но сохранить этот эффект только для прокручиваемых элементов.

чтобы сгладить эффекты прокручиваемых элементов, у меня есть:

html, body { overflow: hidden; }

.scrollable {
    overflow: scroll;
    -webkit-overflow-scrolling: touch;
}

а затем следующий скрипт, отключающий эффект сенсорной прокрутки:

$(document).bind('touchmove', function (e) { 
    if (e.target === document.documentElement) {
        e.preventDefault(); 
    }
});

хотя это, похоже, вообще не работает, потому что при прокрутке элемента до самого нижнего или верхнего края он также прокручивает documentElement.

Есть ли способ отключить этот эффект только для html-элемента body?

Вот хороший пример того, как это влияет на функциональность:

http://dl.dropbox.com/u/1928164/ios5/index.html


person zanona    schedule 08.11.2011    source источник
comment
Я не думаю, что ваш обработчик событий когда-либо попадет в preventDefault(), потому что target — это элемент, вызвавший событие, который никогда не может быть document.documentElement (‹html›). Таким образом, вы видите такое же поведение, как если бы у вас не было обработчика событий.   -  person ThinkingStiff    schedule 11.11.2011
comment
это было бы currentTarget боюсь. target может вернуть что угодно внутри него, вы можете попробовать;)   -  person zanona    schedule 11.11.2011
comment
currentTarget возвращает элемент с прослушивателем, а не элемент, вызвавший событие.   -  person ThinkingStiff    schedule 11.11.2011
comment
ах, я вижу вашу точку зрения, это правильно. Я пробовал с телом тоже без успеха. Я поиграю с кодом в вашем ответе и дам вам знать, как все пойдет, спасибо, чувак.   -  person zanona    schedule 11.11.2011
comment
Я как раз писал эту демонстрацию перед вашим последним комментарием: jsfiddle.net/ThinkingStiff/3TkuF   -  person ThinkingStiff    schedule 11.11.2011


Ответы (3)


Жаль, что -webkit-overflow-scrolling не справляется с этим лучше. Вам нужно отслеживать позицию y, чтобы заставить ее работать. Я добавляю класс scroll ко всему, что хочу прокручивать на своей странице, например к элементам <ul>. Оберните <div> вокруг <ul>, чтобы заполнить область просмотра overflow-y: auto. Не ставьте overflow или height на <ul>. <ul> будет увеличиваться до высоты своего содержимого, а <div> на самом деле выполняет прокрутку. -webkit-overflow-scrolling наследуется, поэтому размещайте его так высоко в DOM, как хотите.

Демонстрация: http://jsfiddle.net/ThinkingStiff/FDqH7/

Скрипт:

var swipeY = 0;

function onTouchMove( event ) {

    var scroll = event.target.closestByClassName( 'scroll' );

    if ( scroll ) {

        var top = scroll.positionTop - scroll.parentNode.positionTop,
            heightDifference = ( 0 - scroll.offsetHeight + scroll.parentNode.offsetHeight );

        if( ( top >= 0 ) && ( event.touches[0].screenY > swipeY ) ) { 
            event.preventDefault(); //at top, swiping down
        } else if( ( top <= heightDifference ) && ( event.touches[0].screenY < swipeY ) ) { 
            event.preventDefault(); //at bottom, swiping up
        };

    } else {
        event.preventDefault();
    };

};

function onTouchStart( event ) {

    swipeY = event.touches[0].screenY;

};

Element.prototype.closestByClassName = function ( className ) {

    return this.className && this.className.split( ' ' ).indexOf( className ) > -1
        ? this
        : ( this.parentNode.closestByClassName && this.parentNode.closestByClassName( className ) );

};

window.Object.defineProperty( Element.prototype, 'positionTop', {

    get: function () { 
        return this.offsetTop - this.parentNode.scrollTop;
    }

} );

document.getElementById( 'viewport' ).addEventListener( 'touchmove', onTouchMove, false );
document.getElementById( 'viewport' ).addEventListener( 'touchstart', onTouchStart, false );

HTML:

<div id="viewport">
<div id="scroll-view">
    <ul class="scroll">
        <li>scroll scroll scroll scroll scroll </li>
        <li>scroll scroll scroll scroll scroll </li>
        <li>scroll scroll scroll scroll scroll </li>

        . . .

    </ul>
</div>
</div>

CSS:

#viewport {
    border: 1px solid black;
    height: 460px;
    width: 320px;
    -webkit-overflow-scrolling: touch;
}

#scroll-view {
    height: 100%;
    overflow-y: auto;
    width: 100%;
}
person ThinkingStiff    schedule 10.11.2011
comment
Я попробовал ваш код с #viewport в качестве абсолютного позиционированного div с overflow: auto и большим количеством lorem ipsun внутри, и в настоящее время происходит то, что скрипт отключил прокрутку viewport, оставив бодрую прокрутку в теле . - person zanona; 11.11.2011
comment
Поместите div внутри #viewport и поместите текст внутри него. Не устанавливайте переполнение для внутреннего div, но поместите на него прокрутку класса, если вы используете приведенный выше код. Настройки тура для #viewport верны для переполнения и положения. - person ThinkingStiff; 12.11.2011
comment
@ludicco Я обновил свой ответ и создал демо, чтобы сделать его более понятным. Я успешно использую это в течение нескольких месяцев. - person ThinkingStiff; 27.12.2011

Вот аналогичный ответ ThinkingStiff, за исключением того, что он не требует вашей структуры html. Он ищет любые элементы с переполненным содержимым и разрешает прокрутку только тогда, когда пользователь взаимодействует с ними.

Недостатки:

  • Как только пользователь достигает верхнего или нижнего предела прокручиваемого узла, отскок внутри этого узла не произойдет (но произойдет, если вы щелкнете снизу/сверху пределов). Это, вероятно, означает, что он не удовлетворяет вашим требованиям по запросу для обновления :(

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

Кофейный скрипт:

# Vertical scrolling behavior overrides.
#
# This disables vertical scrolling on the page for touch devices, unless the user is scrolling
# within an overflowed node.  This requires some finessing of the touch events.
#
# **NOTE:** This code ends up disabling bounce behavior if the user tries to scroll on a node that
# is already at its upper or lower limit.
window$   = $(window)
initialY  = null
nodeStack = []

# When a user begins a (potential) drag, we jot down positional and node information.
#
# The assumption is that page content isn't going to move for the duration of the drag, and that
# it would also be awkward if the drag were to change/stop part way through due to DOM
# modifications.
window$.bind 'touchstart', (evt) ->
  initialY  = evt.originalEvent.pageY
  nodeStack = $(evt.target).parents().andSelf().filter(':not(body, html)').get().reverse()
  nodeStack = nodeStack.map (node) -> $(node)

window$.bind 'touchend touchcancel', (evt) ->
  initialY  = null
  nodeStack = []

# We override the `touchmove` event so that we only allow scrolls in allowable directions,
# depending on where the user first began the drag.
window$.bind 'touchmove', (evt) ->
  return evt.preventDefault() if initialY == null
  # A positive direction indicates that the user is dragging their finger down, thus wanting the
  # content to scroll up.
  direction = evt.originalEvent.pageY - initialY

  for node$ in nodeStack
    nodeHeight    = node$.height()
    # For some reason, the node's scrollHeight is off by 2 pixels in all cases.  This may require
    # tweaking depending on your DOM.  Concerning.
    scrollHeight  = node$[0].scrollHeight - 2
    nodeScrollTop = node$.scrollTop()

    # If we have a scrollable element, we want to only allow drags under certain circumstances:
    if scrollHeight > nodeHeight
      # * The user is dragging the content up, and the element is already scrolled down a bit.
      return if direction > 0 and nodeScrollTop > 0
      # * And the reverse: the user is dragging the content down, and the element is up a bit.
      return if direction < 0 and nodeScrollTop < scrollHeight - nodeHeight

  # Otherwise, the default behavior is to disable dragging.
  evt.preventDefault()
person Nevir    schedule 18.02.2012
comment
CoffeeScript (он компилируется в JavaScript — его просто намного удобнее писать): coffeescript.org - person Nevir; 19.02.2012
comment
Решение проблемы 2px — функция jquery height() не учитывает отступы. Измените nodeHeight = node$.height() на nodeHeight = node$.outerHeight() и ваша проблема решена :) - person Yuval Karmi; 26.05.2012
comment
Проклятия! Спасибо за совет :) - person Nevir; 26.05.2012

Для меня оказалось, что единственным эффективным решением было использование скрипта joelambert/ScrollFix, который отлично работал без лагает, На самом деле я уже использую его в одном из своих проектов.

Вы также можете узнать об этом более подробно на его сообщение в блоге. Извините за других пользователей, которые ответили, но мне действительно не помогли эти ответы.

person zanona    schedule 26.08.2012