Создание плагина jQuery на примере слайдера

в 7:00, , рубрики: javascript, jquery, jquery plugins, slider, tutorial, метки: , , ,

Часто по работе приходится встраивать в страницу слайдеры прокрутки изображений, блоков и т.д. Наигравшись с «чужими» разработками, которые часто обладают лишним и ненужным мне функционалом было решено сделать свой велосипед и оформить его в виде плагина для jQuery, который бы банально выполнял свои функцию горизонтального слайдера, и был бы мне понятен От и До.

Данная реализация далеко не претендует на идеальную, так как это мой первый плагин, да и код может быть через время мне будет видеться страшным, но сейчас мне кажется все прекрасно и довольно радужно и может быть кому-то будет полезным.

Сначала нужно изучить правила оформления плагина для jQuery. Я пользовался информацией по этой ссылке. Откуда я узнал, что для файлов с плагинами jQuery существует соглашение касательно их названия — оно должно удовлетворять формату jquery.pluginName.js. Таким образом, файл с нашим плагином нужно будет назвать jquery.lbslider.js — именно так я решил назвать свой слайдер, почему lb не понятно никому, кроме моей любимой невесты (нужно же, чтобы была хоть какая-то загадка). Сам же код плагина должен иметь такое оформление (более подробно можно почитать по ссылке выше):

(function($){
    jQuery.fn.pluginName = function(options){
        // Зададим список свойств и укажем для них значения по умолчанию.
        // Если при вызове метода будут указаны пользовательские
        // варианты некоторых из них, то они автоматически перепишут
        // соответствующие значения по умолчанию
        options = $.extend({
            param1: 'param1Value', //параметр1
            param2: 'param2Value' //параметр2
        }, options};

    var make = function(){
        // реализация работы метода с отдельным элементом страницы
    };

    return this.each(make);
    // в итоге, метод pluginName вернет текущий объект jQuery обратно
};
})(jQuery);

Для начала я решил использовать такие параметры в своей реализации:
leftBtn — кнопка для прокрутки слайдера влево
rightBtn — кнопка для прокрутки слайдера влево
quantity — количество видимых элементов на странице (иногда их нужен всего 1, а иногда и 3, 4 и более)
autoPlay — булевое значение, указывающее, нужна ли автопрокрутка слайдера
autoPlayDelay — задержка при автопрокрутке

Итак, вот мои опции по умолчанию:

var options = $.extend({
    leftBtn: 'leftBtn',
    rightBtn: 'rightBtn',
    quantity: 4,
    autoPlay: false,  // true or false
    autoPlayDelay: 10  // delay in seconds
}, options);

Теперь приступим непосредственно к реализации. Я решил что html код слайдера будет состоять из блока-обертки, внутри которого будут находится кнопки для прокрутки и еще один блок со списком <ul>, элементы которого и будут проркучиваться. Сначала добавим несколько стилей.

var make = function() {
    $(this).css('overflow', 'hidden');
    var el = $(this).children('ul');
    el.css({
        position: 'relative',
        left: '0'
    });
};

Прокрутку решено было сделать бесконечной, для этого продублируем несколько элементов с конца вначале и несколько первых элементов в конце. Несколько — это как раз количество наших видимых элементов на странице — параметр quantity.

var sliderFirst = el.children('li').slice(0, options.quantity);
var tmp = '';
sliderFirst.each(function(){
    tmp = tmp + '<li>' + $(this).html() + '</li>';
});
sliderFirst = tmp;
var sliderLast = el.children('li').slice(-options.quantity);
tmp = '';
sliderLast.each(function(){
    tmp = tmp + '<li>' + $(this).html() + '</li>';
});
sliderLast = tmp;

var elRealQuant = el.children('li').length;
el.append(sliderFirst);
el.prepend(sliderLast);

Если Вы заметили, мы также сохранили первоначальное количество элементов в переменной elRealQuant — оно нам еще пригодится. Далее установим ширину одного элемента, она зависит от ширины всего блока и от количества видимых элементов, также установим CSS свойство float: left. Узнаем новое количество всех элементов, после того, как мы продублировали некоторые из них и установим ширину всего списка равной количеству элементов умноженному на ширину одного элемента.

var elWidth = el.width()/options.quantity;
el.children('li').css({
    float: 'left',
    width: elWidth
});
var elQuant = el.children('li').length;
el.width(elWidth * elQuant);
el.css('left', '-' + elWidth * options.quantity + 'px');

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

Теперь добавим функции отключения кнопок прокрутки на время самой анимации прокручивания и последующего их включения. Это будет просто элементарное добавление/убирание класса inactive к кнопкам, а по нажатии на кнопки будем проверять, если этот класс есть — ничего не делать. Конечно также можно задать отдельное стилевое оформление для таких кнопок.

function disableButtons() {
    $('.' + options.leftBtn + ', .' + options.rightBtn).addClass('inactive');
}
function enableButtons() {
    $('.' + options.leftBtn + ', .' + options.rightBtn).removeClass('inactive');
}

Теперь напишем собственно функции, которые будут отрабатывать по нажатии кнопок прокрутки слайдера:

$('.' + options.leftBtn).click(function(event){
    event.preventDefault();
    if (!$(this).hasClass('inactive')) {
        disableButtons();
        el.animate({left: '+=' + elWidth + 'px'}, 300,
            function(){
                if ($(this).css('left') == '0px') {$(this).css('left', '-' + elWidth * elRealQuant + 'px');}
                enableButtons();
            }
        );
    }
    return false;
});

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

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

if (options.autoPlay) {
    function aPlay() {
        $('.' + options.rightBtn).click();
        delId = setTimeout(aPlay, options.autoPlayDelay * 1000);
    }
    var delId = setTimeout(aPlay, options.autoPlayDelay * 1000);
    el.hover(
        function() {
            clearTimeout(delId);
        },
        function() {
            delId = setTimeout(aPlay, options.autoPlayDelay * 1000);
        }
    );
}

Ну вот собственно и все. Можно посмотреть теперь весь код плагина.

Весь код

(function($){
    $.fn.lbSlider = function(options) {
        var options = $.extend({
            leftBtn: 'leftBtn',
            rightBtn: 'rightBtn',
            quantity: 3,
            autoPlay: false,  // true or false
            autoPlayDelay: 10  // delay in seconds
        }, options);
        var make = function() {
            $(this).css('overflow', 'hidden');
            var el = $(this).children('ul');
            el.css({
                position: 'relative',
                left: '0'
            });

            var sliderFirst = el.children('li').slice(0, options.quantity);
            var tmp = '';
            sliderFirst.each(function(){
                tmp = tmp + '<li>' + $(this).html() + '</li>';
            });
            sliderFirst = tmp;
            var sliderLast = el.children('li').slice(-options.quantity);
            tmp = '';
            sliderLast.each(function(){
                tmp = tmp + '<li>' + $(this).html() + '</li>';
            });
            sliderLast = tmp;

            var elRealQuant = el.children('li').length;
            el.append(sliderFirst);
            el.prepend(sliderLast);
            var elWidth = el.width()/options.quantity;
            el.children('li').css({
                float: 'left',
                width: elWidth
            });
            var elQuant = el.children('li').length;
            el.width(elWidth * elQuant);
            el.css('left', '-' + elWidth * options.quantity + 'px');

            function disableButtons() {$('.' + options.leftBtn + ', .' + options.rightBtn).addClass('inactive');}
            function enableButtons() {$('.' + options.leftBtn + ', .' + options.rightBtn).removeClass('inactive');}

            $('.' + options.leftBtn).click(function(event){
                event.preventDefault();
                if (!$(this).hasClass('inactive')) {
                    disableButtons();
                    el.animate({left: '+=' + elWidth + 'px'}, 300,
                        function(){
                            if ($(this).css('left') == '0px') {$(this).css('left', '-' + elWidth * elRealQuant + 'px');}
                            enableButtons();
                        }
                    );
                }
                return false;
            });

            $('.' + options.rightBtn).click(function(event){
                event.preventDefault();
                if (!$(this).hasClass('inactive')) {
                    disableButtons();
                    el.animate({left: '-=' + elWidth + 'px'}, 300,
                        function(){
                            if ($(this).css('left') == '-' + (elWidth * (options.quantity + elRealQuant)) + 'px') {$(this).css('left', '-' + elWidth * options.quantity + 'px');}
                            enableButtons();
                        }
                    );
                }
                return false;
            });

            if (options.autoPlay) {
                function aPlay() {
                    $('.' + options.rightBtn).click();
                    delId = setTimeout(aPlay, options.autoPlayDelay * 1000);
                }
                var delId = setTimeout(aPlay, options.autoPlayDelay * 1000);
                el.hover(
                    function() {
                        clearTimeout(delId);
                    },
                    function() {
                        delId = setTimeout(aPlay, options.autoPlayDelay * 1000);
                    }
                );
            }
        };
        return this.each(make);
    };
})(jQuery);

И вызывается он довольно просто. Примерно вот так:

$('.slider').lbSlider({leftBtn: 'sa-left', rightBtn: 'sa-right', quantity: 3, autoPlay: true});

И конечно ДЕМО

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

Автор: equinox7


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js