jQuery - własny plugin

Korzystając z jQuery, nie raz używamy różnego rodzajów pluginów, czyli rozszerzeń do tej wspaniałej biblioteki. W tym artykule stworzymy nasze własne rozszerzenie.

Rozszerzenia do jQuery można podzielić na dwie części. Jedne z nich rozszerzają jQuery nie operując na elementach HTML:


    $.fn.random = function(min, max) {
        return Math.floor(Math.random()*(max-min+1)+min);;
    }

    console.log($.random(5, 50));

Drugie - bardziej nas interesujące operują na zbiorach elementów. Przykładem takiej funkcji może myć np. fadeOut(), który powoduje zanikanie obiektów, które zostały do niego przekazane.


$(".someElements").fadeOut("slow");

HTML i CSS

Nasz plugin będzie bardzo prostą karuzelą. Zanim zaczniemy pisać kod, stwórzmy HTML i CSS, który nam się przyda.

Pokaż HTML

<div class="carousel">
    <span class="carousel-prev">
        &#x25C2;
    </span>
    <div class="carousel-wrapper">
        <ul class="carousel-list">
            <li class="carousel-list-el">..1..</li>
            <li class="carousel-list-el">..2..</li>
            <li class="carousel-list-el">..3..</li>
            <li class="carousel-list-el">..4..</li>
        </ul>
    </div>
    <span class="carousel-next">
        &#x25B8;
    </span>
</div>

Pokaż CSS

.carousel {
    margin: 1rem auto;
    background: #fff;
    position: relative;
    max-width: 680px;
    width: 100%;
    height: 230px;
    padding: 5px;
}

.carousel-wrapper {
    overflow: hidden;
    position: relative;
    width: 100%;
    height: 100%;
    border-radius: 3px;
}

.carousel-list {
    list-style-type: none;
    height: 100%;
    margin: 0;
    padding: 0;
    display: flex;
    width: 99999px;
}

.carousel-list-el {
    display: inline-flex;
    margin-right: 5px;
    text-align: center;
    background: #fff;
    height: 220px;
    overflow: hidden;
    border-radius: 3px;
    overflow: hidden;
}

.carousel-prev,
.carousel-next {
    cursor: pointer;
    position: absolute;
    overflow: hidden;
    width: 40px;
    height: 40px;
    border-radius:50%;
    z-index: 2;
    top:50%;
    transform: translate(0, -50%);
    font-size:2rem;
    font-family:sans-serif;
    text-align: center;
    line-height: 50px;
    color: #fff;
    background: var(--color-main);
    background-position: center;
    background-repeat: no-repeat;
    text-indent: -9999px;
    overflow: hidden;
}

.carousel-prev {
    left: -15px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' class='bi bi-chevron-left' viewBox='0 0 16 16'%3E  %3Cpath fill-rule='evenodd' d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'%3E%3C/path%3E%3C/svg%3E");
}

.carousel-next {
    right: -15px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' class='bi bi-chevron-right' viewBox='0 0 16 16'%3E  %3Cpath fill-rule='evenodd' d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'%3E%3C/path%3E%3C/svg%3E");
}

Ogólny schemat pluginów

Ogólny schemat plugiu może mieć postać:


(function($) {
    $.fn.functionName = function(config) {
        const options = $.extend({
            //...parametry
        }, config);

        return this.each(function() {
            //...metody, właściwości itp
        });
    }
})(jQuery);

Nasz plugin powinien mieć określone parametry swojego działania, które użytkownik powinien móc nadpisać własnymi wartościami. W czystym Javascript użylibyśmy tutaj Object.assign() lub stread syntax. Podobne czynności robiliśmy już w rozdziale gdzie tworzyliśmy slider. W jQuery możemy skorzystać z funkcji extend():


(function($) {
    $.fn.carousel = function(config) {

        const options = $.extend({
            animationTime   : 700, //czas animacji
            pauseTime       : 3000, //przerwa między automatycznym przewijaniem
            onScroll        : function() {} //opcjonalna funkcja zwrotna po przewinięciu jednego slajdu
        }, config);

    }
})(jQuery);

Dzięki temu użytkownik używający naszego dodatku będzie mógł go odpalać przekazujac do niego stosowne parametry:


$(".some-div").carousel({
    animationTime : 2000
});

Tworzymy plugin

Przechodzimy do meritum. Plugin może być odpalony na pojedynczego ale i dla wielu elementów na stronie. Powinniśmy więc zrobić po nich pętlę i obsłużyć każdy z osobna:


(function($) {
    $.fn.functionName = function(config) {
        const options = $.extend({
            //...parametry
        }, config);

        return this.each(function() {
            //...metody, właściwości itp
            const $this = $(this);
        });
    }
})(jQuery);

Po pierwsze podstawmy odpowiednie elementy pod zmienne:


...
return this.each(function() {
    const $this = $(this);

    const $ul = $this.find(".carousel-list");
    const $li = $ul.find(".carousel-list-el");
    const $prevBtn = $this.find(".prev");
    const $nextBtn = $this.find(".next");
    let time = null; //posłuży do automatycznego przewijania slidera

});
...

Podepnijmy teraz odpowiednie zdarzenia pod przyciski przesuwające:


(function($) {
    $.fn.carousel = function(config) {
        const options = $.extend({
            animationTime : 700,
            pauseTime     : 3000,
            onScroll      : function() {},
        }, config);

        return this.each(function() {
            const $this = $(this);

            ...

            const scrollPrev = () => { ... }
            const scrollNext = () => { ... }

            $prevBtn.bind("click", e => {
                e.preventDefault();
                scrollPrev();
            });

            $nextBtn.bind("click", e => {
                e.preventDefault();
                scrollNext();
            });
        });
    }
});

Nasza karuzela będzie miała bardzo prostą zasadę. Jeżeli przewijamy listę w lewo, wtedy na pierwszą pozycję wrzucamy element z jej końca. Jeżeli listę przesuwamy w prawo, wtedy wrzucimy na jej koniec element z jej początku.


(function($) {
    $.fn.carousel = function(config) {
        const options = $.extend({
            animationTime : 700,
            pauseTime     : 3000,
            onScroll      : function() {},
            auto          : false
        }, config);

        return this.each(function() {
            ...

            const scrollNext = () => {
                e.preventDefault();

                if (!$ul.is(":animated")) {
                    const $li = $ul.find(".carousel-list-el");
                    $ul.animate({
                        "margin-left" : -$li.outerWidth(true) //właściwość outerWidth(true) zwraca szerokość pierwszego elementu w kolekcji. Parametr true oznacza czy doliczyć margines
                    }, options.animationTime, () => {
                        $li.last().after($li.first());
                        $ul.css({"margin-left" : 0});
                        options.onScroll();
                    });
                }
            }

            ...
        });
    }
});

Pozostał nam przeciwny kierunek:


(function($) {
    $.fn.carousel = function(config) {
        const options = $.extend({
            animationTime : 700,
            pauseTime     : 3000,
            onScroll      : function() {},
            auto          : false
        }, config);

        return this.each(function() {
            ...

            const scrollPrev = () => {
                e.preventDefault();

                if (!$ul.is(":animated")) {
                    const $li = $ul.find(".carousel-list-el");
                    $ul.css("margin-left", -$li.last().outerWidth(true));
                    $li.first().before($li.last());
                    $ul.animate({
                        "margin-left" : 0
                    }, options.animationTime, () => {
                        options.onScroll();
                    });
                }
            }

            $prevBtn.bind("click", e => {
                e.preventDefault();
                scrollPrev();
            });

            $nextBtn.bind("click", e => {
                e.preventDefault();
                scrollNext();
            });

        });
    }
});

Nasze przesuwanie realizujemy tylko w momencie gdy nasza lista się nie przesuwa, stąd na początku sprawdzenie $ul.not(":animated").

Jeżeli elementów w karuzeli będzie mniej niż zajmowana przez nią przestrzeń, zauważalne będzie przekładanie elementu z lewej na prawą stronę. Bardzo prosto możemy to naprawić poprzez zduplikowanie elementów na liście.


(function($) {
    $.fn.carousel = function(config) {
        const options = $.extend({
            animationTime : 700,
            pauseTime     : 3000,
            onScroll      : function() {},
            auto          : false
        }, config);

        return this.each(function() {
            const $this = $(this);

            const $ul = $this.find(".carousel-list");
            const $li = $ul.find(".carousel-list-el");
            const $prevBtn = $this.find(".carousel-prev");
            const $nextBtn = $this.find(".carousel-next");
            let time = null;

            $ul.append($li.clone(true)).append($li.clone(true)); //klonujemy elementy 2x (może być i więcej)
            ...
        });
    }
});

Automatyczne przewijanie

Aby nasz slider dodatkowo automatycznie się przesuwał, wykorzystamy dobrze nam znaną metodę setTimeout(), którą podstawimy pod zmienną time.

Dodajemy więc dodatkową opcję auto, która automatycznie będzie odpalać funkcję scrollNext():


(function($) {
    $.fn.carousel = function(config) {
        const options = $.extend({
            animationTime : 700,
            pauseTime     : 3000,
            onScroll      : function() {},
            auto          : false
        }, config);

        ...

}

Automatyczne przesuwanie powinniśmy odpalić w trzech momentach: przy starcie naszej karuzeli, i gdy użytkownik będzie klikał przyciski Poprzedni/Następny.

Żeby nie duplikować kodu, napiszmy dodatkową funkcję i odpalmy ją w stosownych momentach:


(function($) {
    $.fn.carousel = function(config) {
        const options = $.extend({
            animationTime : 700,
            pauseTime     : 3000,
            onScroll      : function() {},
            auto          : false
        }, config);

        return this.each(function() {
            const $this = $(this);

            const $ul = $this.find(".carousel-list");
            const $li = $ul.find(".carousel-list-el");
            const $prevBtn = $this.find(".carousel-prev");
            const $nextBtn = $this.find(".carousel-next");
            let time = null;

            $ul.append($li.clone(true)).append($li.clone(true));

            const autoNext = function() {
                if (options.auto) {
                    clearTimeout(time);
                    time = setTimeout(function() {
                        scrollNext()
                    }, options.pauseTime);
                }
            };

            const scrollPrev = () => {
                if (!$ul.is(":animated")) {
                    const $li = $ul.find(".carousel-list-el");
                    $ul.css("margin-left", -$li.last().outerWidth(true));
                    $li.first().before($li.last());
                    $ul.animate({
                        "margin-left": 0
                    }, options.animationTime, () => {
                        options.onScroll();
                    });

                    autoNext();
                }
            };

            const scrollNext = () => {
                if (!$ul.is(":animated")) {
                    const $li = $ul.find(".carousel-list-el");

                    $ul.animate({
                        "margin-left": -$li.outerWidth(true)
                    }, options.animationTime, () => {
                        $li.last().after($li.first());
                        $ul.css({"margin-left": 0});
                        options.onScroll();

                        autoNext();
                    });
                }
            };

            $prevBtn.bind("click", e => {
                e.preventDefault();
                scrollPrev();
            });

            $nextBtn.bind("click", e => {
                e.preventDefault();
                scrollNext();
            });

            autoNext();
        });
    }
})(jQuery);

Przykład użycia

I w zasadzie zakończyliśmy naszą pracę. Pozostaje odpalenie jej dla danych elementów.


<div class="carousel demo" id="demo">
    <button arial-label="poprzedni" class="carousel-prev"> &lt; </button>
    <div class="carousel-wrapper">
        <ul class="carousel-list">
            <li class="carousel-list-el">
                <img src="https://pokaimg.me/150x200/people?text=1" width="200" height="200" alt="">
            </li>
            <li class="carousel-list-el">
                <img src="https://pokaimg.me/300x200/people?text=2" width="300" height="200" alt="">
            </li>
            <li class="carousel-list-el">
                <img src="https://pokaimg.me/100x200/people?text=3" width="100" height="200" alt="">
            </li>
            <li class="carousel-list-el">
                <img src="https://pokaimg.me/200x200/people?text=4" width="200" height="200" alt="">
            </li>
            <li class="carousel-list-el">
                <img src="https://pokaimg.me/250x200/people?text=5" width="250" height="200" alt="">
            </li>
        </ul>
    </div>
    <button arial-label="następny" class="carousel-next"> &gt; </button>
</div>

$(function() {
    $("#demo").carousel({
        animationTime: 300,
        pauseTime: 5000,
        auto: true
    });
});

Tutaj mała uwaga. W powyższym przykładzie do slajdów wstawiłem umyślnie różnej wielkości zdjęcia. Może tam się znaleźć tekst czy dowolny inny element. Gdy przewijamy karuzelę, pobieramy szerokość danego elementu. W przypadku zdjęć mogą one być w tym momencie jeszcze w pełni nie wczytane, dlatego przy ich używaniu warto dodać im atrybuty width/height.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.