Кинетическая прокрутка элементов c Drag-and-Drop в виде плагина к jQuery. Подключается в полпинка, простая логика и ничего лишнего, только физика и матан.
Абстрактная величина (или правило), отображает изменение скорости за одну миллисекунду.
Абстрактная величина, отображающая смещение в пикселях за одну миллисекунду.
Тут я сам запутался, проще опытным путем выяснить, какое направление (-1 | 1
) нужно задавать, в том или ином случае.
Понятия из физики, с помощью которых удобно описать процессы, протекающие в плагине. Можно условно считать, что прокручиваемый элемент обладает массой и на него действуют силы, в данном контексте некоторые понятия из физики приобретают смысл и хорошо описывают модель.
-// template.jade
-// Прокручиваемый элемент завернутый в контейнер, ограничивающий его по высоте
.wrapper
.longScrollableElement
-// Кнопки при зажатии которых, будет активироваться прокрутка элемента
.buttonScrollUp
.buttonScrollDown
# app.coffee
# Подключаем плагин к элементу
$(".longScrollableElement").kineticScroll()
# Обработчик для кнопки прокрутки элемента вверх
$(".buttonScrollUp").on "mousedown touchstart", (event) ->
event.stopPropagation() # Блокируем всплытие события
event.preventDefault() # Блокируем стандартный обработчик
# Вызываем метод прокрутки с постоянной скоростю
$(".longScrollableElement").kineticScroll("buttonScroll", -1) # Вверх
# Обработчик для кнопки прокрутки элемента вниз
$(".buttonScrollDown").on "mousedown touchstart", (event) ->
event.stopPropagation() # Блокируем всплытие события
event.preventDefault() # Блокируем стандартный обработчик
# Вызываем метод прокрутки с постоянной скоростю
$(".longScrollableElement").kineticScroll("buttonScroll", 1) # Вниз
После чего мы получаем следующий функционал:
- При "протаскивании" элемента курсором, он будет прокручиваться в след за курсором;
- При отпускании курсора в момент, когда он движется, элементу будет передана кинетическая энергия курсора, и элемент продолжит двигаться по инерции;
- При попытке подвигать мышью элемент в момент движения по инерции, он мгновенно начнет следовать за мышью;
- При вращении колеса мыши, элемент будет плавно прокручиваться, за счет эффекта передачи импульса, хотя тут может возникать дерганье анимация, при быстрой прокрутке колесиком, так как скорость будет стремиться к постоянной, но из-за особенности процесса прокрутки колесика, события будут не достаточно часто генерироваться);
- При попытке проскролить колесиком в момент инерционого движения, элементу будет задан импульс от колесика;
- При зажатии кнопок промоток, будет происходить анимация прокрутки с постоянной скорость;
- При отжатии кнопок промоток, промотка продолжится по инерции, по заданной скорости (в данном случае скорости промотки);
- При клике на кнопки промоток, будет задан импульс промотки элемента, и запустится анимация движения;
- При работе с кнопками промотки, в момент когда элемент движется по инерции, эффекты кнопок немедлено переопределят текущую анимацю;
- При достижении границы прокрутки во время движения по инерции, прокрутка сразу прекратится, без эффектов и замедления (это не положительное свойство, а просто факт).
$(selector).kineticScroll([firstArg[, otherArgs...]])
firstArg
Object | String
- передавая объект можно задать свойства для плагина, передавая строку можно вызвать метод.otherArgs...
- аргументы к методам
Только при вызове фукнции в первый раз для элемента произойдет инициализация плагина, при последующих вызовах будут происходить действия уже над имеющимся объектом, ссылка на объект будет хранится до уничтожения ссылки на сам элемент.
Использование: $(selector).kineticScroll(<methodName>[, args...])
.
Активирует прокрутку на элементе, просто включает прослушку событий, и после активации становятся доступны другие методы.
Деактивирует прокрутку на элементе.
Параметры:
direction
-1 | 1
- направление прокруткиvelocity
Number
- начальная скорость прокрутки[static=false]
Boolean
- прокручивать с постоянной скоростью[callback]
Function
- вызовется, после остановки прокрутки
Заставляет прокручиваться элемент по инерции с заданой начальной скоростью. Каждый новый кадр просчитывается с помощью requestAnimationFrame
, в цикле просчета учитываются граничные условия (скорость, края), и события из-вне (перехват анимации с помощью, курсора, колесика, кнопок и т.д) для остановки анимации. Анимация для одного элемента гарантировано рассчитывается в одном цикле, это делает прокрутку отзывчивой и предотвращает ошибки. В рассчете смещения во время анимации производятся операции, только над кинетическими свойствами системы, с учетом разницы времени с момента последнего кадра.
Тут следует пояснить, благодаря тому, что движение основано лишь на физических свойствах системы, будет проблематично проссчитать точное место остановки, или запланировать его. Так как для этого, по ходу движения, придется корректировать скорость, для схождения к нужной точке, а корректировать ее потребуется на основе начального значения скорости, которое может задавать пользователь. А далее нужно будет составлять многочлен, который в зависимости от скорости и граничных условий, будет сводить систему к заданной точке, что влечет много матана, особенно если захочится, что бы замедление было не линейным.
Останавливает анимацию прокрутки.
Параметры:
direction
-1 | 1
- направление прокрутки
Прокрутика при зажатии кнопки какого-либо контролла, начальная скорость задается через свойства инициализации плагина. Перестает просчитываться при отпускании кнопки мыши в окне (onMouseup
у window
). При вызове метода активируется метод scroll
с постояной скоростью прокрутки, при отпускании кнопки мыши вызвается метод scroll
, который заставляет двигаться элемент по инерции.
Свойства можно передать в объекте вызывая $(selector).kineticScroll(<options>)
.
Функция может принимать два параметра:
velocity
Number
- мгновенная скорость в момент вызова функцииlimit
Number
- Количество пикселей оставшихся до границы прокрутки, с учетом направления прокрутки (сначала была идея сделать плавное торможения на граничных условиях)
Функция высчитывает мгновенное ускорение при заданных параметрах, далее в процессе расчета анимации к скорости добавляется значение производной от ускорения по времени: deltaVelocity = acceleration(velocity, limit) / deltaTime
, а к значению смещения прокрутки добавляется производная от скорости по времени (с учетом направления): delatOffset = deltaVelocity / delataTime
. Как можно заметить, по умолчанию ускорение принимает отрицательное значения, что будет вести к замедлению прокрутки и остановке (так сказать действие силы сопротивления), расчет анимации в данном случае закончится, как только скорость достигнет 0
. Если ускорение будет равняться 0
, то прокрутка будет происходить с постоянной начальной скоростью, и остановится при достижения границы элемента. Если ускорение будет больше 0
, то скорость движения будет возрастать, и остановится только на границе.
Функция может принимать два параметра:
deltaOffset
<Number>
- дельта смещенияdeltaTime
<Number>
- дельта времени
Функция вызывается для определения начальной скорости прокрутки. Замер разниц во времени производится на основе движения курсора, вычисляется разница в значении координаты Y
у курсора, при условии, что последнее измерение произошло не ранее чем 15
миллисекунд назад (учитывая, что отрисовка происходит примерно раз в 16.6 мс (60fps), а считывание координат зависит от чуствительности мыши, значение в 15
мс позволяет не плохо избавится от погрешности измерений, и позволяет исключить лаги, когда начальная скорость становится очень большой или даже бесконечной).
Смещение курсора в пикселях (по двум координатам), когда прокрутка не активируется. Для предотвращения случайной прокрутки на маленьких значениях смещения курсора.
Смещение курсора по координате Y
домнажается на это значение, и получается итоговое значение смещения прокрутки. В общем, чем больше это значение, тем больше будет прокручиваться элемент при меньшем смещении курсора, для значения равного 1
, прокрутка при перетаскивании будет следовать точь-в-точь за курсором.
Смещение мыши по координате Y
домнажается на это значение, при замере смещения для определения начальной скорости. В общем, чем больше это значения тем выше будет начальная скорость в инерционной прокрутке и наоборот, при значении равном 1
начальная скорость будет примерно соответствовать скорости движения курсора в момент отжатия клавиши мыши.
Начальная скорость движения, при прокрутке колеса мыши. Если Boolean(wheelVelocity) === false
, то события прокрутки колеса мыши слушаться не будут.
Задает свойства начальной скорости при прокрутке с помощью метода buttonScroll
. Свойства объекта:
moveVelocity
- задает скорость прокрутки пока кнопка зажата;endVelocity
- задает начальную скорость прокрутки при отжатии кнопки.
- Перенести в репозиторий на github;
- Сделать страницу с примером, где можно будет посмотреть работу плагина;
- Добавить сборщик и скомпилированную версию в репозиторий;
- Полифил для
requestAnimationFrame
; - Добавить возможность подключать через
CommonJS
илиAMD
; - Создать npm пакет;
- Touch events;
- Тесты.