Статьи

Кинетическая прокрутка JavaScript: Часть 2

Во второй части этой серии примеров кинетической прокрутки JavaScript реализован инерционный эффект. Это делается путем отслеживания движения, когда пользователь все еще перетаскивает вид, а затем автоматически прокручивает вид, пока он не остановится в какой-то момент.

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

отслеживание

Затененная область на приведенной выше диаграмме — это когда пользователь все еще вручную управляет списком, он прокручивает вверх, а затем вниз и снова вверх. Как только этот пользователь наконец отпустит свой палец, список начнет входить в режим автоматической прокрутки. В этом режиме список начинает двигаться с определенной скоростью, но затем он замедляется и в конечном итоге останавливается. Чтобы выполнить автоматическую прокрутку, важно определить начальную скорость прямо в этот момент выпуска. Это делается путем отслеживания положения пальца и вычисления скорости, это происходит между нажатием и отпусканием.

кинетический
Если эта кинетическая прокрутка реализована правильно, мы получим что-то, изображенное в следующей анимации (GIF, простите за качество). Если вы хотите поиграть с живой демо-версией, перейдите на ariya.github.io/kinetic/2 с вашим любимым современным смартфоном. Обратите внимание на плавную прокрутку, когда вы щелкаете список. Обратите внимание, что у него пока нет функции отскока краев, мы рассмотрим это в следующем эпизоде.

Как отследить скорость, пока пользователь все еще перетаскивает список? Простым подходом, среди миллиардов возможностей, является использование периодического таймера. Когда таймер срабатывает, найдите текущую позицию и используйте ее для вычисления скорости. Эмпирически, отслеживание положения 10 раз в секунду (интервал таймера 100 мс) оказывается достаточным.

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

function tap(e) {
    pressed = true;
    reference = ypos(e);
 
    velocity = amplitude = 0;
    frame = offset;
    timestamp = Date.now();
    clearInterval(ticker);
    ticker = setInterval(track, 100);
 
    e.preventDefault();
    e.stopPropagation();
    return false;
}

Код для track()довольно короткий. Идея состоит в том, чтобы сравнить текущую позицию и время с предыдущей записанной позицией и временем, frameи timestamp, соответственно. Из этой дельты vлегко получить мгновенную скорость . Последняя строка действует как простой фильтр скользящего среднего , это защищает нас от любых диких изменений, либо от собственных беспорядочных движений пользователя, либо от другого входного шума.

function track() {
    var now, elapsed, delta, v;
 
    now = Date.now();
    elapsed = now - timestamp;
    timestamp = now;
    delta = offset - frame;
    frame = offset;
 
    v = 1000 * delta / (1 + elapsed);
    velocity = 0.8 * v + 0.2 * velocity;
}

track()Функция играет свою роль во время ручного перетаскивания (заштрихованная часть на предыдущей диаграмме). Как только пользователь освобождает палец от сенсорного устройства, ситуация резко меняется. Теперь мы должны использовать вычисленную скорость запуска, чтобы решить следующий шаг, о чем свидетельствует реализация следующей функции.

function release(e) {
    pressed = false;
 
    clearInterval(ticker);
    if (velocity > 10 || velocity < -10) {
        amplitude = 0.8 * velocity;
        target = Math.round(offset + amplitude);
        timestamp = Date.now();
        requestAnimationFrame(autoScroll);
    }
 
    e.preventDefault();
    e.stopPropagation();
    return false;
}

По сравнению с исходным кодом (в части 1 ), есть дополнительный блок для запуска функции автоматической прокрутки autoScroll. Пороговая скорость 10 пикселей в секунду используется для предотвращения автоматической прокрутки, если скорость слишком низкая. Скорость запуска используется для вычисления того, где прокрутка должна быть остановлена, это и есть цель amplitudeпеременной. Коэффициент 0,8 настраивается. Если вы хотите, чтобы список казался «тяжелым», уменьшите число. Следовательно, более высокий фактор создаст иллюзию гладкого и беспроблемного списка. Обновление смещения прокрутки является обязанностью autoScroll()функции. Обратите внимание, как это происходит с помощью requestAnimationFrame для анимации без джанкфайков.

Кинетический-дифференциал

Типичный список щелчков имеет замедление, которое является формой экспоненциального затухания . Другими словами, система замедления — это просто затухающая пружинно-массовая система . К счастью, это стандартное дифференциальное уравнение, и его решение довольно простое ( ŷ — целевое положение, A — амплитуда, t — текущее время, τ — постоянная времени).

Математика может выглядеть страшно, но реальный код не так уж сложен. Пока этой autoScroll()функции все еще нужно перемещать список более чем на 0,5 пикселя, он будет вызываться повторно. На самом деле, следующие строки кода (они действительно довольно короткие, не так ли?) Являются сердцем этого известного эффекта замедления.

function autoScroll() {
    var elapsed, delta;
    if (amplitude) {
        elapsed = Date.now() - timestamp;
        delta = -amplitude * Math.exp(-elapsed / timeConstant);
        if (delta > 0.5 || delta < -0.5) {
            scroll(target + delta);
            requestAnimationFrame(autoScroll);
        } else {
            scroll(target);
        }
    }
}

Значение timeConstantздесь довольно важно. Если вы хотите действительно имитировать iOS (в UIWebView, decelerationRate normal ), предлагаемое значение равно 325. Это соответствует постоянной времени 325 мс. Специалисты по математике также могут понять, что с интервалом анимации 16,7 мс (от 60 кадров в секунду) эта автоматическая прокрутка эффективно снижает скорость прокрутки в 0,95 раза, то есть Math.exp(-16.7 / 325)до тех пор, пока она едва перемещается и, следовательно, прокрутка полностью не останавливается.

Чтобы еще раз подчеркнуть, диаграмма положения как функция времени для этого замедления изображена ниже, созданная с использованием графика Google 150 * (1-exp (-t / 325)) . Возможно, вы видели хвост (последняя часть) первой диаграммы, показывающей этот вид кривой экспоненциального затухания.

замедление

Обратите внимание, что этот вид автоматической прокрутки также означает, что прокрутка будет остановлена, независимо от начальной скорости, вычисленной ранее, примерно через 1,95 секунды ( 6 * 325). Системная теория говорит нам, что после 6-кратной постоянной времени позиция будет находиться в пределах 0,25% от целевой позиции (математически вы можете получить это из расчета Math.exp(-6)). Таким образом, выбирая постоянную времени, вы можете сделать список легким или тяжелым, в зависимости от ваших потребностей.

Как насчет остального кода? Удивительно, но он все тот же, что и в первой части . Нет необходимости изменять drag()функцию, потому что поведение остается прежним. Также вспомогательные функции не должны меняться вообще. Переработка всегда приятна!

Кинетическая-краска
Чтобы увидеть полный код JavaScript (примерно 130 строк), проверьте репозиторий github.com/ariya/kinetic/ . Он был протестирован на Android 4.3 или более поздней версии (Chrome, Firefox), Android 2.3 (Kindle Fire) и iOS 6 или более поздней версии (Mobile Safari). Кроме того, как я упоминал в начале этой серии, рассматривайте этот пример кода как пример, не более и не менее (он оптимизирован для удобства чтения и снижения производительности). Используйте базовые знания, чтобы вдохновить вас на написание, реализацию и настройку собственного варианта кинетической прокрутки. Тем не менее, это не означает, что этот конкретный код действительно медленный. С Nexus 4 и Nexus 7 нет проблем с частотой 60 кадров в секунду. Это также можно проверить, просмотрев подробную статистику фреймов (используя Chrome Developer Tools). Очевидно, что время рисования HUD Также очень полезно в этом анализе.

Эффект физики очаровал многих разработчиков с тех пор, как iPhone популяризировал концепцию. Фактически, веб-ориентированная реализация этой концепции существует в различных формах (практически во всех мобильных библиотеках), от раннего выпуска PastryKit до специализированной микробиблиотеки, такой как FTScroller . Если вам всегда интересно знать математику и физику, стоящую за ней, я надеюсь, что мое объяснение выше показывает, что в этом нет ничего мистического.

В третьей части будет показана важная функция: привязка к сетке .
Обновление : опубликовано, пожалуйста, наслаждайтесь Часть 3 .