Jump to content
  • 0

Карусель на jQuery.


Great Rash
 Share

Question

Вступление

Что мы будем делать: бесконечную карусель картинок (infinite image carousel - это для тех, кто хочет погуглить на тему). Используем для этого небезызвестный фреймворк jQuery. Но мы не будем писать просто код, а напишем полноценный легко настраиваемый плагин.

Вообще говоря, на сайте http://www.jquery.com/ есть туториал как писать плагины для jQuery, но для тех кто не знает английского, я опишу важные моменты. Т.к. у меня нет хостинга, то я не могу показать вам что получится у нас в итоге, но примерный результат вы можете посмотреть на этой странице http://jqueryfordesigners.com/demo/infinite-carousel.html. Конечно же мы не будем полностью клонировать эту карусель, а напишем свою, которая будет во многом лучше и удобней для конкретного применения.

Итак, приступим...

Базовая верстка

Наша каруселька будет состоять из нескольких элементов, вложенных друг в друга. Обычно (если кто гуглил, то заметил) это <div>, в который вложен неупорядоченный список (<ul>). Но я не хочу привязываться к определенному HTML, поэтому остановимся на таком обязательном условии:

на странице должен быть блок-контейнер, в него должен быть вложен блок-карусель, в который могут быть вложены любые элементы с display: block; или display: inline-block;

Но для простоты будем работать с версткой как у всех (хотя это не важно):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru">

<head>
<title>jQuery Карусель</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />

<style type="text/css">
.container {
border: red 1px solid;
}

.carousel {
margin: 0;
padding: 0;
list-style: none;
}

.carousel li {
float: left;
width: 88px;
height: 88px;
padding: 5px;
background: lightblue;
border: blue 1px solid;
}
</style>

<!--
Тут подключаем фреймворк jQuery.
Лучше конечно скачать его себе в проект,
а не использовать ссылку на сторонний ресурс (как у меня).
-->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.min.js"></script>

<!--
Подключаем наш плагин, который будет расположен
по адресу js/myCarousel.js (ну или куда вы там его положите)
-->
<script type="text/javascript" src="js/myCarousel.js"></script>

<script type="text/javascript">
$(document).ready(function() {
// ...тут будет вызываться наш плагин
});
</script>
</head>

<body>

<div class="container">
<!--
Вместо <ul> может быть любой блочный элемент,
в который могут быть вложены любые блочные элементы
-->
<ul class="carousel">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
</div>

<!--
Кнопки навигации могут располагаться
где угодно на странице
-->
<button class="prev"><</button>
<button class="next">></button>

</body>
</html>

Вот и вся тестовая страничка, над которой мы будем экспериментировать. Больше к HTML мы возвращаться не будем, начнем писать скрипт...

Как пишутся плагины для jQuery

Прежде всего, рекомендую вам пройти по этой ссылке - http://docs.jquery.com/Plugins/Authoring где написан подробный туториал по созданию плагинов для jQuery. Я не буду полностью переводить его, а остановлюсь лишь на моментах, которые важны для нашего проекта.

Приступая

Итак, чтобы написать плагин для jQuery нужно добавить новый метод к объекту jQuery.fn:

// создаем новый метод объекта jQuery.fn
jQuery.fn.myCarousel = function() {
// Тут пишем код нашего плагина
};

"Но где же знак доллара ($)?", спросите вы. Не волнуйтесь, он все еще тут. Однако, чтобы удостовериться, что наш плагин не пересекается с другими библиотеками, которые тоже используют знак доллара, лучше всего передать объект jQuery в самовыполняющуюся функцию (замыкание), которая свяжет его со знаком доллара так, что его нельзя будет переназначить:

// создаем функцию-замыкание...
(function($){
// создаем новый метод объекта jQuery.fn
$.fn.myCarousel = function() {
// Тут пишем код нашего плагина
};
})(jQuery); // ...которую тут же вызываем, передавая ей в качестве аргумента объект jQuery

Таким образом мы сможем использовать знак доллара внутри нашего плагина, не боясь, что он (доллар) может быть переназначен другим скриптом.

Контекст

Для тех кто не знает что такое контекст (<b>this</b>) рекомендую почитать эту статью.

В области видимости (scope) плагина ключевое слово this ссылается на сам объект jQuery, по этому его нет необходимости оборачивать таким образом - $(this);, что является распростаненной ошибкой среди начинающих разработчиков плагинов. Смотрим код:

(function($){
$.fn.myCarousel = function() {
// нет необходимости писать $(this) т.к.
// "this" уже ссылается на объект jQuery

// запись $(this) будет означать то же самое, что и $($('#element'));

this.fadeIn('normal', function() {
// здесь же this будет ссылаться на конкретный DOM-элемент
});
};
})(jQuery);

Цепь вызовов

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

$('div').children().css('background', 'red');

Методы .children() и .css() оба являются методами объекта jQuery, но в коде они вызываются последовательно (по цепочке), это возможно благодаря тому, что каждый метод возвращает нам объект jQuery. Это и называется цепь вызовов.

Чтобы наш плагин обеспечивал эту возможность, нам нужно из ближайшей области видимости (immediate scope) вернуть ссылку на объект-конструктор, т.е на jQuery. Так же. чтобы наш плагин работал со всеми найденными элементами нам надо использовать метод .each():

(function($){
// создаем новый метод объекта jQuery.fn
$.fn.myCarousel = function() {
// возвращаем ссылку на объект jQuery
return this.each(function() {
// сохраняем контекст ($this будет ссылаться на объект jQuery)
var $this = $(this);

// ...далее идет код плагина
});
};
})(jQuery);

Опции и значения по умолчанию

Для того чтобы плагин можно было легко настраивать можно сделать настройки по умолчанию, которые можно изменять или расширять при помощи метода .extend(). Таким образом нам не придется задавать кучу параметров для нашего плагина, вмето этого можно передать всего один, который будет объектным литералом (JSON):

(function($){
/*
* создаем новый метод объекта jQuery.fn,
* в который передаем параметр options
*/
$.fn.myCarousel = function(options) {
// дефолтные настройки
var settings = {
a: 1,
b: 'text'
};

// возвращаем ссылку на объект jQuery
return this.each(function() {
if (options) { // если в функцию передали опции
/*
* метод .extend() сливает два объекта,
* заменяя совпадающие свойства,
* которые есть в объекте settings
* новыми свойствами из объекта options
* и возвращает измененный settings
*/
$.extend(settings, options);
}

// сохраняем контекст ($this будет ссылаться на объект jQuery)
var $this = $(this);

alert(settings.a);
alert(settings.B);
});
};
})(jQuery);

// пример вызова плагина
$(document).ready(function() {
$('div').myCarousel({
a: 42,
b: 'новый текст'
});
});

В принципе это все что понадобится нам для создания нашего плагина, поэтому я не буду описывать остальные параграфы из туториала. Кому интересно прочитает о них самостоятельно.

Начинаем писать плагин

Итак, приступаем к написанию собственно нашего плагина-карусели. Для начала давайте определимся, что нам надо от нашей карусели:

1) она должна быть бесконечной, т.е. когда мы домотаем до последнего элемента все начнется по кругу;

2) кнопки управления могут находится в любом месте на странице (чтобы мы могли подстраивать её под любой дизайн);

3) мы должны сами выбирать по сколько элементов за раз будет проматываться;

4) мы болжны сами выбирать с какой скоростью будут перематываться элементы;

5) мы должны сами выбирать сколько элементов будет отображаться в обласи видимости, а сколько будет "за кадром";

6) должна быть возможность автоматической перемотки если юзер не нажал на стрелки сам.

Вооружившись знаниями о написании плагинов, приступим к реализации задуманного:

(function($){
$.fn.myCarousel = function(options) {
// дефолтные настройки
var settings = {
visible: 3, // видимых элементов - 3
rotateBy: 1, // мотать по одному элементу
speed: 500, // скорость перемотки (в миллисекундах)
btnNext: null, // кнопка перемотки к следующему элементу
btnPrev: null, // кнопка перемотки к предыдущему элементу
auto: null, // время задержки (в миллисекундах) при автоматической перемотке
backSlide: false // будет ли карусель крутиться в обратную сторону при автоматической перемотке
};

return this.each(function() {
if (options) {
$.extend(settings, options);
}

var $this = $(this); // сохраняем контекст
});
};
})(jQuery);

Тут надеюсь все понятно.

Теперь нам необходимо определить, так сказать, "глобальные" переменные (конечно они не глобальные во всем документе, а "глобальные" для нашего плагина):

(function($){
$.fn.myCarousel = function(options) {
// дефолтные настройки
var settings = {
visible: 3,
rotateBy: 1,
speed: 500,
btnNext: null,
btnPrev: null,
auto: null,
backSlide: false
};

return this.each(function() {
if (options) {
$.extend(settings, options);
}

// определяем "глобальные" переменные
var $this = $(this); // сохраняем контекст
var $carousel = $this.children(':first'); // находим первого потомка в нашем контейнере (.container), т.е. <ul>
var itemWidth = $carousel.children().outerWidth(); // находим ширину одного элемента внутри нашего контейнера
var itemsTotal = $carousel.children().length; // определяем сколько всего элементов у нашей карусели
var running = false; // флаг, который хранит информацию о том проигрывается ли анимация на данный момент
var intID = null; // ID интервала (нужен для сброса интервала)
});
};
})(jQuery);

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

Далее нам необходимо присвоить такие стили для нашей карусели без которых она не будет работать. Юзер может забыть их присвоить, поэтому нам необходимо задавать эти стили скриптом:

(function($){
$.fn.myCarousel = function(options) {
// дефолтные настройки
var settings = {
visible: 3,
rotateBy: 1,
speed: 500,
btnNext: null,
btnPrev: null,
auto: null,
backSlide: false
};

return this.each(function() {
if (options) {
$.extend(settings, options);
}

// определяем "глобальные" переменные
var $this = $(this);
var $carousel = $this.children(':first');
var itemWidth = $carousel.children().outerWidth();
var itemsTotal = $carousel.children().length;
var running = false;
var intID = null;

// присваиваем необходимые стили для элементов карусели
// сначала для контейнера
$this.css({
'position': 'relative', // необходимо для нормального отображения в ИЕ6(7)
'overflow': 'hidden', // прячем все, что не влезает в контейнер
'width': settings.visible * itemWidth + 'px' // ширину контейнера ставим равной ширине всех видимых элементов
});

// потом для внутреннего элемента (в нашем случае <ul>)
$carousel.css({
'position': 'relative', // относительное позиционирование нужно для того, чтобы можно было использовать сдвиг влево
'width': 9999 + 'px', // ставим ширину побольше, чтобы точно влезли все элементы
'left': 0 // устанавливаем нулевой девый сдвиг
});
});
};
})(jQuery);

Теперь, после всех приготовлений, пришло время заняться написанием нашей главной функции - функции "скольжения", которая будет прокручивать карусель. Я не буду расписывать на словах как она работает, а лучше покажу схему, из которой вам будет больше понятен принцип работы бесконечной карусели:

tut487682.gif

На картинке описана прокрутка к предыдущему элементу. Такая же последовательность действий у нас будет при прокрутке к следующему элементу.

Остальное, надеюсь, будет понятно из кода:

(function($){
$.fn.myCarousel = function(options) {
// дефолтные настройки
var settings = {
visible: 3,
rotateBy: 1,
speed: 500,
btnNext: null,
btnPrev: null,
auto: null,
backSlide: false
};

return this.each(function() {
if (options) {
$.extend(settings, options);
}

// определяем "глобальные" переменные
var $this = $(this);
var $carousel = $this.children(':first');
var itemWidth = $carousel.children().outerWidth();
var itemsTotal = $carousel.children().length;
var running = false;
var intID = null;

// присваиваем необходимые стили для элементов карусели
// сначала для контейнера
$this.css({
'position': 'relative', // необходимо для нормального отображения в ИЕ6(7)
'overflow': 'hidden', // прячем все, что не влезает в контейнер
'width': settings.visible * itemWidth + 'px' // ширину контейнера ставим равной ширине всех видимых элементов
});

// потом для внутреннего элемента (в нашем случае <ul>)
$carousel.css({
'position': 'relative', // относительное позиционирование нужно для того, чтобы можно было использовать сдвиг влево
'width': 9999 + 'px', // ставим ширину побольше, чтобы точно влезли все элементы
'left': 0 // устанавливаем нулевой девый сдвиг
});

// параметр dir(boolean) - false(сдедующий), true(предыдущий)
function slide(dir) {
var direction = !dir ? -1 : 1; // выбираем направление в зависимости от переданного параметра (влево или вправо)
var leftIndent = 0; // левое смещение (для <ul>)

if (!running) { // если анимация завершена (или еще не запущена)
running = true; // ставим флажок, что анимация в процессе

if (intID) { // если запущен интервал
window.clearInterval(intID); // очищаем интервал
}

if (!dir) { // если мы мотаем к следующему элементу (так по умолчанию)
/*
* вставляем после последнего элемента карусели
* клоны стольких элементов, сколько задано
* в параметре rotateBy (по умолчанию задан один элемент)
*/
$carousel.children(':last').after($carousel.children().slice(0, settings.rotateBy).clone(true));
} else { // если мотаем к предыдущему элементу
/*
* вставляем перед первым элементом карусели
* клоны стольких элементов, сколько задано
* в параметре rotateBy (по умолчанию задан один элемент)
*/
$carousel.children(':first').before($carousel.children().slice(itemsTotal - settings.rotateBy, itemsTotal).clone(true));

/*
* сдвигаем карусель (<ul>) влево на ширину элемента,
* умноженную на количество элементов, заданных
* в параметре rotateBy (по умолчанию задан один элемент)
*/
$carousel.css('left', -itemWidth * settings.rotateBy + 'px');
}

/*
* расчитываем левое смещение
* текущее значение left + ширина одного элемента * количество проматываемых элементов * на направление перемещения (1 или -1)
*/
leftIndent = parseInt($carousel.css('left')) + (itemWidth * settings.rotateBy * direction);

// запускаем анимацию
$carousel.animate({'left': leftIndent}, {queue: false, duration: settings.speed, complete: function() {
// когда анимация закончена
if (!dir) { // если мы мотаем к следующему элементу (так по умолчанию)
// удаляем столько первых элементов, сколько задано в rotateBy
$carousel.children().slice(0, settings.rotateBy).remove();
// устанавливаем сдвиг в ноль
$carousel.css('left', 0);
} else { // если мотаем к предыдущему элементу
// удаляем столько последних элементов, сколько задано в rotateBy
$carousel.children().slice(itemsTotal, itemsTotal + settings.rotateBy).remove();
}

if (settings.auto) { // если карусель должна проматываться автоматически
// запускаем вызов функции через интервал времени (auto)
intID = window.setInterval(function() { slide(settings.backslide); }, settings.auto);
}

running = false; // отмечаем, что анимация завершена
}});
}

return false; // возвращаем false для того, чтобы не было перехода по ссылке
}

// назначаем обработчик на событие click для кнопки next
$(settings.btnNext).click(function() {
return slide(false);
});

// назначаем обработчик на событие click для кнопки previous
$(settings.btnPrev).click(function() {
return slide(true);
});

if (settings.auto) { // если карусель должна проматываться автоматически
// запускаем вызов функции через временной интервал
intID = window.setInterval(function() { slide(settings.backslide); }, settings.auto);
}
});
};
})(jQuery);

Вот и готов наш плагин :angry: Сохраните его в файле my_carousel.js, подключите на страницу, а затем, между тегами <head></head> напишите такой вызов:

<script type="text/javascript">
$(document).ready(function() {
$('.container').myCarousel({
btnNext: '.next',
btnPrev: '.prev',
visible: 4,
rotateBy: 2
});
});
</script>

Итак, опишем настройки для нашей карусели:

btnNext - строка, выражение в формате jQuery (по умолчанию null)

btnPrev - строка, выражение в формате jQuery

visible - целое число, количество видимых элементов (по умолчанию 1)

rotateBy - целое число, по сколько элементов мотать за раз (по умолчанию 1)

speed - целое число, скорость прокрутки элементов в миллисекундах (чем больше тем медленней, по умолчанию 500 миллисекунд)

auto - целое число, задержка автоматической прокрутки элементов в миллисекундах (если назначена, по умолчанию null)

backSlide - булево значение true/false, направление прокрутки: false - к следующему элементу, true - к предыдущему элементу (по умолчанию false)

И на последок полный код нашего плагина без комментариев.

my_carousel.js

(function($){
$.fn.myCarousel = function(options) {
var settings = {
visible: 3,
rotateBy: 1,
speed: 500,
btnNext: null,
btnPrev: null,
auto: null,
backSlide: false
};

return this.each(function() {
if (options) {
$.extend(settings, options);
}

var $this = $(this);
var $carousel = $this.children(':first');
var itemWidth = $carousel.children().outerWidth();
var itemsTotal = $carousel.children().length;
var running = false;
var intID = null;

$this.css({
'position': 'relative',
'overflow': 'hidden',
'width': settings.visible * itemWidth + 'px'
});

$carousel.css({
'position': 'relative',
'width': 9999 + 'px',
'left': 0
});

function slide(dir) {
var direction = !dir ? -1 : 1;
var leftIndent = 0;

if (!running) {
running = true;

if (intID) {
window.clearInterval(intID);
}

if (!dir) {
$carousel.children(':last').after($carousel.children().slice(0, settings.rotateBy).clone(true));
} else {
$carousel.children(':first').before($carousel.children().slice(itemsTotal - settings.rotateBy, itemsTotal).clone(true));
$carousel.css('left', -itemWidth * settings.rotateBy + 'px');
}

leftIndent = parseInt($carousel.css('left')) + (itemWidth * settings.rotateBy * direction);

$carousel.animate({'left': leftIndent}, {queue: false, duration: settings.speed, complete: function() {
if (!dir) {
$carousel.children().slice(0, settings.rotateBy).remove();
$carousel.css('left', 0);
} else {
$carousel.children().slice(itemsTotal, itemsTotal + settings.rotateBy).remove();
}

if (settings.auto) {
intID = window.setInterval(function() { slide(settings.backslide); }, settings.auto);
}

running = false;
}});
}

return false;
}

$(settings.btnNext).click(function() {
return slide(false);
});

$(settings.btnPrev).click(function() {
return slide(true);
});

if (settings.auto) {
intID = window.setInterval(function() { slide(settings.backslide); }, settings.auto);
}
});
};
})(jQuery);

Предложения, критику и прочее в приват. Всем спасибо за внимание.

  • Like 6
Link to comment
Share on other sites

1 answer to this question

Recommended Posts

  • 0

Замечания, которые предложил mishka2

Для внутреннего элемента карусели ($carousel) лучше поставить максимально большую ширину.

В уроке я предложил поставить ширину 9999px, но mishka2 подсказывает, что бывали прецеденты когда этой ширины не хватало и предлагает ставить ширину 99999px. Я же рекомендую все же ставить ширину немного меньше, а именно 32767px, чтобы избежать глюков в старых браузерах (особенно в ИЕ).

Еще одно предложение касается вот этого участка кода:

$(settings.btnNext).click(function() {
return slide(false);
});

Если на страничке будут несколько галлерей - то при клике на кнопки <b>next/prev</b> будут слайдиться все галлереи, а не только та к которой они относяться, поэтому нужно ограничить область этой кнопки, например так:

$(settings.btnNext, this).click(function() {
return slide(false);
});

Спасибо за критику и предложения!

  • Like 4
Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. See more about our Guidelines and Privacy Policy