Задача данного плагина — скроллинг контента посредством касания и перетягивания.
Используемые события — 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)
stepmex
30.10.2016 13:41+1Немного замечаний:
1) События mouse… отсутствуют на «онлитач» устройствах.
2) при навешивании событий используйте префиксы, тогда при удалении не придётся передавать функции, и можно будет отключать все просто передав их через пробел.
3) двигать элементы лучше через transform.
4) чисто моя вредность, в объектах необязательно экранировать ключи, только лишний мусор.
Carduelis
30.10.2016 13:43Если отпустить кнопку мыши за пределами этого блока, то затем по возвращении, контент будет скроллиться просто по перемещению мыши без зажатых кнопок. Понимаю по какой причине это получилось, но надо пофиксить.
rafaylik
30.10.2016 13:55Уточню, если отпустить кнопку за пределами блока контента, срабатывает mouseOut, а сам баг воспроизводится только на jsFiddle, если отпустить кнопку за пределами Result фрейма. Есть предложения как это исправить?
P.S. в условиях размещения на веб-странице работает корректно.
rafaylik
30.10.2016 13:59Ответственные события:
$(document).on('mouseup',mouseUp) $(document).on('contextmenu',mouseUp); $(window).on('blur',mouseUp);
justboris
30.10.2016 17:53+2
movl
01.11.2016 02:49+1Делал что-то похожее, но так и не доделал. Правда большое отличие в том, что нужно было сделать прокрутку с кинетическим эффектом, так сказать. Готовые решения не понравились, в итоге сделал свое, но не на всех платформах работает корректно, и для тачскрина доработки требуются. Может кому полезно будет, там же описание и примеры есть.
medaved
Было бы не плохо, реализовать ещё классический «скроллинг» колесом мыши. И ещё момент сомнительный, не знаю баг это или «фича», но текст не возможно выделить целиком мышью, «Ctrl+A» работают.
rafaylik
Выделение блокируется с момента начала касания на контенте до момента отпускания касания. Это обусловлено тем, что при перетягивании контента текст выделяется, что не есть красиво.
Кликните за пределами блока и нажмите ctrl+A.