В этой статье описываются шаги по созданию плагина jQuery, который обнаруживает горизонтальное движение пальцами на сенсорных устройствах, таких как iPhone и устройства на базе Android. Эта статья является первой в серии из двух частей. В этой статье мы создадим карусель изображений, которая сможет реагировать на ввод пользователя и соответствующим образом изменять положение карусели. Вторая статья расширит плагин, добавив обнаружение пролистывания.
HTML & CSS
Прежде чем перейти к JavaScript, давайте рассмотрим HTML и CSS для карусели изображений, которая будет использоваться для демонстрации плагина Swiper. HTML показан ниже.
<div style="width: 330px; height: 200px;"> <div id="target"> <div> <div><img alt="" src="rexy.jpg" /></div> <div><img alt="" src="xena.jpg" /></div> <div><img alt="" src="xenaagain.jpg" /></div> <div><img alt="" src="rexyagain.jpg" /></div> </div> </div> </div>
Точно так же CSS карусели показан ниже.
img { /*100% width to scale the height proportionately*/ width: 100%; margin: 0; } .frame { width: 100%; height: 100%; border: 1px solid #ccc; overflow: hidden; position: relative; } .pictures { position: absolute; width: 400%; /*change accordingly*/ left: 0%; } .pictures:after { content: "\0020"; display: none; height: 0; } .pictures .pic { width: 25%; /*change with respect to .pictures*/ float: left; }
Внутренний контейнер ( .pictures
) настроен на 400% и содержит четыре изображения. Контейнер каждого изображения ( .pic
) настроен на 25%, чтобы изображения в конечном итоге .pic
ширину 330 пикселей. Если вы измените количество изображений или используете абсолютные значения вместо процентов, вы захотите соответственно изменить значение ширины элементов .pictures
и .pic
.
Изображения создаются для выравнивания по горизонтали, перемещаясь влево. Рамка ( .frame
) создана для показа только одного изображения за раз. При такой настройке мы можем затем «сдвинуть» карусель, изменив свойство .pictures
элемента .pictures
<div>
.
JavaScript
Вот скелет плагина:
(function ($) { 'use strict'; var Swiper = function (el, callbacks) { } $.fn.swiper = function (callbacks) { if (typeof callbacks.swiping !== 'function') { throw '"swiping" callback must be defined.'; } this.each(function () { var tis = $(this), swiper = tis.data('swiper'); if (!swiper) { //ie plugin not invoked on the element yet tis.data('swiper', (swiper = new Swiper(this, callbacks))); } }); }; }(jQuery));
Этот список является стандартным кодом для создания плагина jQuery. Большая часть сложности обрабатывается внутренним классом Swiper
, методы которого еще не определены. Swiper
отвечает за чтение событий, созданных браузером, и вызов обратного вызова. Плагин определен как замыкание, поэтому класс Swiper
не будет ошибочно переопределен внешним кодом. Плагину также запрещено связываться с элементом более одного раза, связывая экземпляр класса Swiper
с swiper
данных swiper
.
var Swiper = function (el, callbacks) { var tis = this; this.el = el; this.cbs = callbacks; this.points = [0, 0]; //perform binding this.el.addEventListener('touchstart', function (evt) { tis.start(evt); }); this.el.addEventListener('touchmove', function (evt) { evt.preventDefault(); tis.move(evt); }); };
В приведенном выше листинге конструктор Swiper
создает экземпляры свойств объекта и обработчиков событий. Свойство points
представляет собой двухклеточный массив, в котором хранится начальная позиция пальца в первой ячейке и конечная позиция во второй ячейке. Мы увидим использование этого массива в последующих листингах. Его значения изначально равны нулю.
Конструктор связывает события touchstart
и touchstart
и touchmove
события соответствующим методам в классе Swiper
. touchstart
инициализирует массив points
с начальной позицией пальца. touchmove
дает нам движение пальца, которое мы передадим функции обратного вызова, чтобы соответственно сместить карусель.
Swiper.prototype.start = function (evt) { if (evt.targetTouches && evt.targetTouches.length === 1) { if (evt.targetTouches[0].offsetX) { this.points[0] = evt.targetTouches[0].offsetX; } else if (evt.targetTouches[0].layerX) { this.points[0] = evt.targetTouches[0].layerX; } else { this.points[0] = evt.targetTouches[0].pageX; } //make initial contact with 0 difference this.points[1] = this.points[0]; } };
В приведенном выше листинге показан метод start()
, который принимает событие и считывает набор касаний, созданных на экране. В устройствах с функцией мультитач, что означает почти все современные смартфоны и планшеты, это свойство представляет собой массив, хранящий расположение всех точек контакта с экраном. В этой реализации мы отслеживаем одну точку контакта, поскольку мы отслеживаем один жест смахивания, который выполняется одним пальцем.
Мы проверяем различные свойства события касания, чтобы приспособить различные реализации поведения касания на разных устройствах. Раньше это требовалось, чтобы заставить его работать на разных устройствах . Однако сегодня все протестированные устройства генерируют свойство pageX
.
Поскольку мы проверяем только жест горизонтальной прокрутки, мы игнорируем свойство pageY
. Мы также устанавливаем для ячеек свойства points
одинаковое значение, чтобы начальная разница между начальной и конечной точками была равна нулю.
Привязка функции для события touchmove
и других вспомогательных методов перечислены ниже.
Swiper.prototype.diff = function () { return this.points[1] - this.points[0]; }; Swiper.prototype.move = function (evt) { if (evt.targetTouches && evt.targetTouches.length === 1) { if (evt.targetTouches[0].offsetX) { this.points[1] = evt.targetTouches[0].offsetX; } else if (evt.targetTouches[0].layerX) { this.points[1] = evt.targetTouches[0].layerX; } else { this.points[1] = evt.targetTouches[0].pageX; } this.cbs.swiping(this.diff()); this.points[0] = this.points[1]; } };
Метод diff()
просто вычисляет разницу между последней точкой (которая изменяется, когда пользователь перемещает палец) и предыдущей точкой. Это иллюстрируется следующим рисунком.
Метод move()
также проверяет различные свойства, чтобы получить правильный вариант для хранения во второй ячейке свойства points
. После сохранения значения вызывается функция обратного вызова с разницей между предыдущим положением и новым положением пальца. Функция обратного вызова отвечает за изменение положения карусели. Это объясняется ниже.
После вызова обратного вызова значение предыдущей позиции заменяется значением текущей позиции. При следующем вызове обратного вызова разница будет смещением между текущей позицией и предыдущей позицией вместо начальной позиции. Это необходимо, если мы хотим, чтобы движение карусели отражало движение пальца. Без этой линии движение карусели накапливает разницу, и в результате происходит большое смещение изображений в ответ на небольшое движение пальца, что явно нежелательно для плавного взаимодействия с пользователем.
Список ниже вызывает плагин.
var target = $('#target'), pictures = $('.pictures', target), MAX_LEFT = -990, MAX_RIGHT = 0, currPos = 0, cb = { swiping: function (displacement) { currPos += displacement; if (currPos > MAX_RIGHT) { currPos = MAX_RIGHT; } else if (currPos < MAX_LEFT) { currPos = MAX_LEFT; } pictures.css('left', currPos + 'px'); } }; target.swiper(cb);
Мы получаем элемент, используя его id
. Нам также нужен дескриптор элемента .pictures
внутри цели, потому что положение карусели изменяется путем изменения left
свойства CSS этого элемента.
Мы устанавливаем левый и правый предел положения карусели с MAX_LEFT
переменных MAX_LEFT
и MAX_RIGHT
. Эти значения должны меняться в зависимости от размера карусели. Они используются для того, чтобы пользователь не прокручивал карусель на пустые места. Переменная MAX_RIGHT
определяет, как далеко вправо палец может перетащить карусель, чтобы попасть в MAX_RIGHT
левое изображение. Естественно, это значение равно 0
. Переменная MAX_LEFT
ограничивает, насколько далеко левый палец может перемещать карусель. Поскольку для отображения самого правого изображения есть четыре изображения, три изображения слева должны быть смещены. Значения выводятся так:
330 (width of one image) * 3 = 990
У нас также есть переменная, currPos
, которая хранит текущую позицию карусели. В качестве альтернативы мы можем получить позицию карусели следующим образом:
currPos = parseInt(pictures.css('left'));
Предпочтительный подход заключается в использовании переменной. Единственная причина заключается в производительности — извлечение свойства left
элемента и преобразование его в целое число определенно потребляет больше вычислительной мощности, чем доступ к значению переменной. Это осознает тот факт, что мы добавляем поведение поверх интерфейса браузера, поэтому важно, чтобы плагин оставался простым.
Обратный вызов указывается как свойство внутри литерала JSON. Почему бы просто не передать это как функцию? Что ж, это подготовит почву для второй части этой серии, где мы объясним, как добавить обнаружение жестов смахивания к плагину.
Последнее замечание: на устройствах iOS (iPhone и iPad) при прокрутке карусели в окне браузера появляется эффект отскакивания. Это очевидно, если карусель находится внизу или вверху (как в данном случае) страницы. Чтобы этого не случилось, мы вызываем метод preventDefault()
для события touchmove
. Между прочим, вызывая метод preventDefault()
, он предотвращает preventDefault()
события в иерархии DOM , что, в свою очередь, приводит к повышению производительности, особенно это проявляется на более медленных устройствах, таких как Nexus One. Я тестировал плагин на iPad 2 (iOS 6.0.1), Nexus One (Android 2.3.6) и Galaxy Note II (Android 4.1.2). Если вы использовали любые другие устройства / ОС, сообщите нам об этом в комментариях!