Статьи

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

Список щелчков, с его эффектом импульса и эластичным краем, становится обычным шаблоном пользовательского интерфейса, так как он был популярен в Apple iPhone несколько лет назад. Реализация этого шаблона с использованием HTML и JavaScript кажется сложной задачей для многих веб-разработчиков. В этой серии статей я раскрою тайну кинетической прокрутки с помощью нескольких простых в использовании примеров кода.

Прежде чем сойти с ума и применить эти причудливые эффекты, важно создать прочную основу. Следовательно, первая часть будет иметь дело только с примерной реализацией базовой техники перетаскивания в прокрутку (без импульса, без отскока ребра). Концепция, а также некоторые части кода будут повторно использованы в остальной части серии. Если вы хотите следовать и получить полный код, проверьте репозиторий github.com/ariya/kinetic .

свиток

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

Для этого урока представление содержит общий текст Lorem ipsum (созданный с помощью lipsum.com ). Также обратите внимание, что прокрутка только в вертикальном направлении. Чтобы дать идею, попробуйте загрузить демо ariya.github.io/kinetic/1 на ваш современный смартфон. Он был протестирован на Android 4.3 (Chrome, Firefox), Android 2.3 (Kindle Fire) и iOS 6 (Mobile Safari).

basicscroll

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

view = document.getElementById('view');
if (typeof window.ontouchstart !== 'undefined') {
    view.addEventListener('touchstart', tap);
    view.addEventListener('touchmove', drag);
    view.addEventListener('touchend', release);
}
view.addEventListener('mousedown', tap);
view.addEventListener('mousemove', drag);
view.addEventListener('mouseup', release);

Инициализация некоторых переменных состояния также является важным шагом. В частности, нам нужно найти правильные границы ( maxи min) для прокрутки представления offset. Поскольку наш вид будет занимать весь экран, innerHeightздесь используется. В реальном приложении вы можете вместо этого использовать вычисленную (стиль) высоту родительского элемента представления. Как вы вскоре увидите, pressedнеобходимо знать состояние, когда пользователь перетаскивает список.

max = parseInt(getComputedStyle(view).height, 10) - innerHeight;
offset = min = 0;
pressed = false;

Если вы попробуете демо, вы заметите тонкий свиток indicator. Преднамеренно это размещено на левой стороне вида. Таким образом, вы сразу заметите, что это не родная полоса прокрутки браузера. Этот индикатор должен быть расположен где-нибудь между самым верхним и самым нижним краями экрана, следовательно, необходим relativeфактор (будет использоваться позже). Бонусный пункт: откуда эта жестко закодированная ценность 30?

indicator = document.getElementById('indicator');
relative = (innerHeight - 30) / max;

Так как мы хотим отрегулировать позицию представления с помощью преобразования CSS3 , нам нужно выяснить, какое свойство стиля нужно использовать. Может использоваться комплексное обнаружение, но следующий простой подход уже работает достаточно надежно.

xform = 'transform';
['webkit', 'Moz', 'O', 'ms'].every(function (prefix) {
    var e = prefix + 'Transform';
    if (typeof view.style[e] !== 'undefined') {
        xform = e;
        return false;
    }
    return true;
});

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

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

function ypos(e) {
    // touch event
    if (e.targetTouches && (e.targetTouches.length >= 1)) {
        return e.targetTouches[0].clientY;
    }
 
    // mouse event
    return e.clientY;
}

Еще одна важная функция — scrollона перемещает вид и индикатор прокрутки в нужное место. Обратите внимание, что нам нужно зафиксировать смещение прокрутки, чтобы оно не выходило за пределы вычисленных границ.

function scroll(y) {
    offset = (y > max) ? max : (y < min) ? min : y;
    view.style[xform] = 'translateY(' + (-offset) + 'px)';
    indicator.style[xform] = 'translateY(' + (offset * relative) + 'px)';
}

Все хорошие вещи приходят в три. Функции tap, releaseи dragимеют важное значение для основной логики прокруткой. Удивительно, но все они просты и лаконичны!

Первый, tapзапускается, когда пользователь касается списка первым. Здесь мы должны пометить его как нажатый .

function tap(e) {
    pressed = true;
    reference = ypos(e);
    e.preventDefault();
    e.stopPropagation();
    return false;
}

Позже, когда пользователь отпускает свою рукоятку, нам нужно отменить маркировку с помощью release.

function release(e) {
    pressed = false;
    e.preventDefault();
    e.stopPropagation();
    return false;
}

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

function drag(e) {
    var y, delta;
    if (pressed) {
        y = ypos(e);
        delta = reference - y;
        if (delta > 2 || delta < -2) {
            reference = y;
            scroll(offset + delta);
        }
    }
    e.preventDefault();
    e.stopPropagation();
    return false;
}

И это весь код прокрутки ! В целом, он весит всего около 80 строк.

Чувствуете себя смелым и хотите заняться спортом? Настройте индикатор прокрутки так, чтобы он появлялся и исчезал в нужное время (синхронизировано с pressedсостоянием). Его непрозрачность может быть анимирована с помощью CSS3 перехода .

Также имейте в виду, что код, представленный здесь, служит главным образом источником вдохновения . Он оптимизирован для удобства чтения, а не для производительности (экстремальная оптимизация JavaScript , компоновка графических процессоров и т. Д.). Ваша реальная реализация должна быть более структурированной, надежной и хорошо протестированной.

scrollframes

Как насчет производительности? Ну, на данном этапе вряд ли что-то вычислительно дорого. Скорость прокрутки можно проверить, используя частоту кадров Chrome или время рисования . Более подробные сроки также доступны в Chrome Developer Tools. Приведенный выше снимок показывает статистику кадров на Nexus 4 под управлением Chrome 28. Мы находимся в пределах 60 кадров в секунду!

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