Hello, jQuery, again!

Задача данного плагина — скроллинг контента посредством касания и перетягивания.

Используемые событияmousedown/move/up. По умолчанию эта цепочка событий выделяет содержимое в пределах касания.

Реализация — до боли знакомое overflow:hidden, обертывание содержимого элемента и перемещение контента внутри.

Нерешенные проблемы
1. Иногда, на грани между пересечением нижнего края и пятикратным замедлением, контент сдвигается вверх на определенный интервал. Примечательно, что при обратном движении (в момент пересечения этой черты) контент возвращается на свое место (сдвигается вниз).

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

Испытательный стенд на jsFiddle (52 строки некомментированного кода).

Начнем с разметки. У нас есть блок с контентом:

<div id="content">
    <h2>Touch and drag content</h2>
    <p>Lorem ipsum dolor sit amet...</p>
</div>

#content {
    position:absolute;
    left:50px;
    top:50px;
    width:500px;
    height:350px;
    text-align:justify;
}

Далее — подробное комментирование каждого действия.

// оборачиваем плагин в анонимную функцию
(function($){
// пишем функцию с именем нашего плагина
$.fn.touchanddrag = function(){
    // оборачиваем содержимое нашего элемента в дочерний элемент, который и будем перемещать
    // исходный же элемент сохраняет свою разметку и свойства, заданные ему в css
    this.wrapInner('<div>');
    // исходный элемент теперь стал контейнером, а новый элемент - обертка для его данных
    // то есть исходный элемент (box) - родитель, а новый элемент (data) - дочерний
    var box = this,
        data = this.children();
    // прячем полосу прокрутки
    box.css({overflow:'hidden'});
    // позиционируем элемент data
    data.css({position:'absolute',cursor:'default'});
    // событие касания на элементе
    data.mousedown(function(e){
        // высота элементов для дальнейших вычислений
        var hgtBox = box.height(),
            hgtData = data.height();
        // проверяем, достаточно ли контента для прокрутки
        if (hgtData>hgtBox) {
            // позиция касания
            var posTap = e.pageY,
                // позиция элемента data относительно элемента box
                posData = data.position().top,
                posShift,
                // событие скольжения в пределах документа
                mouseMove = function(e){
                    // проверяем, нажата ли еще кнопка мыши
                    if (e.which==1){
                        // расстояние, пройденное относительно первого касания
                        posShift = e.pageY-posTap;
                        // если прокрутили контент выше верхнего края
                        if (data.position().top>0){
                            // перемещаем контент, но в 5 раз медленнее
                            // фрагмент имитации кинетической прокрутки
                            data.css({top:(posData+posShift)/5});
                        // если прокрутили контент ниже нижнего края
                        } else if ((data.position().top+hgtData)<hgtBox){
                            // замедляем перемещение в пять раз
                            data.css({top:(hgtBox-hgtData)+(posShift/5)});
                        // прокрутка контента в пределах видимости родителя
                        } else {
                            // добавляем разницу к предыдущим координатам
                            data.css({top:posData+posShift});
                        }
                    } else {
                        mouseUp();
                    }
                },
                // событие отпускания
                mouseUp = function(){
                    // отменяем мониторинг перетаскивания и блокировку выделения
                    $(document).off('mousemove',mouseMove).off('mouseup',mouseUp);
                    $(document).off('mousedown',selection);
                    // возвращаем вид курсора
                    data.css({cursor:'default'});
                    // если после прокрутки контент оказался выше верхнего края						
                    if (data.position().top>0){
                        // плавно возвращаем его в крайнюю верхнюю позицию
                        // фрагмент имитации кинетической прокрутки
                        data.animate({top:0},250);
                    // если после прокрутки контент оказался ниже нижнего края
                    } else if ((data.position().top+hgtData)<hgtBox) {
                        // плавно возвращаем его в крайнюю нижнюю позицию
                        data.animate({top:hgtBox-hgtData},250);
                    }
                },
                // снятие выделения при перетаскивании контента
                selection = function(){
                    if (window.getSelection){window.getSelection().removeAllRanges()}
                    else {document.selection.empty()}
                    return false;
                };
            // меняем вид курсора на время перетаскивания
            data.css({cursor:'move'});
            // инициализируем мониторинг перетаскивания и блокировку выделения
            $(document).on('mousedown',selection).on('mousemove',mouseMove);
            $(document).on('mouseup',mouseUp).on('contextmenu',mouseUp);
            $(window).on('blur',mouseUp);
        }
    });
    return this;
};
})(jQuery);

Вызываем плагин:

$('#content').touchanddrag();

UPD 1 — исправлен баг, при котором контент продолжал скроллиться при отпускании кнопки мыши за пределами фрейма. Добавлена проверка

if (e.which==1)

UPD 2 — в css объектах убрал экранирование ключей

data.css({cursor:'move'})
Поделиться с друзьями
-->

Комментарии (13)


  1. medaved
    30.10.2016 12:37

    Было бы не плохо, реализовать ещё классический «скроллинг» колесом мыши. И ещё момент сомнительный, не знаю баг это или «фича», но текст не возможно выделить целиком мышью, «Ctrl+A» работают.


    1. rafaylik
      30.10.2016 12:40

      Выделение блокируется с момента начала касания на контенте до момента отпускания касания. Это обусловлено тем, что при перетягивании контента текст выделяется, что не есть красиво.
      Кликните за пределами блока и нажмите ctrl+A.


  1. stepmex
    30.10.2016 13:41
    +1

    Немного замечаний:
    1) События mouse… отсутствуют на «онлитач» устройствах.
    2) при навешивании событий используйте префиксы, тогда при удалении не придётся передавать функции, и можно будет отключать все просто передав их через пробел.
    3) двигать элементы лучше через transform.
    4) чисто моя вредность, в объектах необязательно экранировать ключи, только лишний мусор.


    1. rafaylik
      30.10.2016 16:31

      Спасибо за ответ, экранирование ключей действительно лишнее, убрал.
      Можно подробней, чем transform лучше, дело в производительности?


      1. ionicman
        30.10.2016 17:37
        +1

        Дело в использовании GPU под это дело. Быстрее + плавнее :)


  1. Carduelis
    30.10.2016 13:43

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


    1. rafaylik
      30.10.2016 13:55

      Уточню, если отпустить кнопку за пределами блока контента, срабатывает mouseOut, а сам баг воспроизводится только на jsFiddle, если отпустить кнопку за пределами Result фрейма. Есть предложения как это исправить?
      P.S. в условиях размещения на веб-странице работает корректно.


      1. rafaylik
        30.10.2016 14:07

        Прошу прощения, не то написал — срабатывает mouseup


      1. serginho
        30.10.2016 14:12

        Нужно при каждом вызове mousemove проверять, нажата ли соответствующая клавиша мыши, и если не нажата, вызывать mouseup


        1. rafaylik
          30.10.2016 14:36

          Спасибо, пофиксил через e.which==1


    1. rafaylik
      30.10.2016 13:59

      Ответственные события:

      $(document).on('mouseup',mouseUp)
      $(document).on('contextmenu',mouseUp);
      $(window).on('blur',mouseUp);


  1. justboris
    30.10.2016 17:53
    +2

    Все уже написано до вас:
    https://github.com/asvd/dragscroll


    Обновленный JsFiddle


  1. movl
    01.11.2016 02:49
    +1

    Делал что-то похожее, но так и не доделал. Правда большое отличие в том, что нужно было сделать прокрутку с кинетическим эффектом, так сказать. Готовые решения не понравились, в итоге сделал свое, но не на всех платформах работает корректно, и для тачскрина доработки требуются. Может кому полезно будет, там же описание и примеры есть.


    https://gist.github.com/ovcharik/5798a847ed592b5b3ae5