Pull to refresh

Comments 21

Это точно не перевод статьи этак 10-летней давности?
Это вы так решили пропиариться и найти стажировку?

1. Вы опоздали с такой статьёй лет на 10 точно. На 10!!! Карл!!!
2. Это плагин? Я уж ожидал тут увидеть супер плагин, ну в 2020 году то. А тут hello world на jquery
3. Чего такого умеет jQuery в 2020, что вы предпочитаете его, а не ванильный JavaScript?
4. Сравните вашу статью и вот эту, от 2012 года. habr.com/ru/post/158235
А где, собственно, разбор кода как написать слайдер, на который дана ссылка? Зачем читателям простейшая информация из документации?

«Slibox — it's a very fast, powerful, tiny slider. Mobile-friendly, easy to use», код — github.com/GitCodeDestroyer/Slibox/blob/master/src/slibox.js. Красивое описание и трешовая реализация — бич опенсорс разработки, которая по моему опыту на 98% состоит из подобного. А статья — бесполезный самопиар, минус.
Вы невнимательно прочли заголовок:
Как я в 15 лет написал свой первый jQuery-плагин и как их создавать.

В статье я показал как написать jQuery-плагин, а не слайдер. Все честно?

По коду, можете показать что не так, как можно улучшить мой код и стиль?

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


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


Скрытый текст
!function ($) {
    /**
     * Все эти функции получаются в глобальной области видимости jQuery - это ненужно и может вступить в конфликт
     * с другими плагинами. Нужно оформить просто в функциях, кроме $.fn.slibox
     * Также лучше по-максимуму избегать анонимных функций, чтобы была возможность делать рекурсию и видеть
     * семантичный стек вызовов
     *
     */

    $.fn.slideTo = function (slideTo, fromTimer = false) {
        this.each(function () {
            let sliderId = '#' + this.id,
                slidesCount = $(sliderId).data("sb-slides-count");

            if ($(sliderId).data("sb-carousel") || fromTimer) {
                if (slideTo > slidesCount) {
                    slideTo = 1;
                } else if (slideTo < 1) {
                    slideTo = slidesCount
                }
            }

            if (fromTimer) {
                if ($(sliderId).data('sb-timer')) {
                    $(sliderId + ' .sb-timer').removeClass('sb-timer-animate');

                    setTimeout(function () {
                        $(sliderId + ' .sb-timer').addClass('sb-timer-animate');
                    }, 100);

                    clearInterval(this.slidingInterval);

                    this.slidingInterval = setInterval(function () {
                        if (!$(sliderId)[0].paused) {
                            // console.log($(sliderId).data('sb-timer-time') / 100, $(sliderId)[0].time);
                            if ($(sliderId).data('sb-timer-time') / 100 != $(sliderId)[0].time - 1) {
                                $(sliderId)[0].time++;
                            } else {
                                if (($(sliderId).data('sb-timer-carousel') || $(sliderId).data('sb-active-slide') < $(sliderId).data('sb-slides-count'))) {
                                    $(sliderId).slideToNext();
                                } else {
                                    $(sliderId + ' .sb-timer').css('animation-play-state', 'paused');
                                }
                                $(sliderId)[0].time = 0;
                            }
                        }
                    }, 100);
                }
            }

            if (slideTo) {
                $(sliderId).toggleClass('sb-last-slide', slideTo == $(sliderId).data('sb-slides-count'));
                $(sliderId).data("sb-active-slide", slideTo);
                $(sliderId + " .sb-slide").removeClass("active");
                $(sliderId + " .sb-slide:nth-of-type(" + slideTo + ")").addClass("active");
                $(sliderId + " .sb-controller").removeClass("active"), $(sliderId + " .sb-controller:nth-of-type(" + slideTo + ")").addClass("active");
            }

        })
    }

    let sbCanDrag = true;

    $.fn.slideToNext = function () {
        this.each(function () {
            if (this.className.match('slibox')) {
                $(this).slideTo($(this).data('sb-active-slide') + 1, $(this).data('sb-timed-carousel'));
            }
        })
        return $(this).data('sb-active-slide');
    }

    $.fn.slideToPrev = function () {
        this.each(function () {
            if (this.className.match('slibox')) {
                $(this).slideTo($(this).data('sb-active-slide') - 1, $(this).data('sb-timed-carousel'));
            }
        })
        return $(this).data('sb-active-slide');
    }

    $.fn.setTimeTo = function (time) {
        this.each(function () {
            $(this).data('sb-timer-time', time);
            this.time = Math.ceil(this.time / time);
            $('#' + this.id + ' .sb-timer').css('animation-duration', time + 'ms');
            $('#' + this.id + ' .sb-timer').css('animation-play-state', 'running');
        });
        return $(this);
    }

    $.fn.slibox = function slibox(options) {
        /**
         * Не стоит сокращать названия переменных до несемантичных значений типа o, это ухудшает читаемость
         * Также не стоит использовать хелперы ($.assign) в качестве замены стандарных возможностей языка
         *
         */
        const extendedOptions = Object.assign({
            /**
             * Не стоит использовать !0 или !1, код должен быть явным и с понятными типами,
             * не заставляя разработчика лишний раз интерпретировать его.
             * Также это упор на особенности языка, желательно избегать подобных конструкций
             *
             */
            height: false,
            width: false,
            activeSlide: 1,
            renderArrows: true,
            renderControllers: true,
            imagesLinks: [],
            loadErrorMessage: "Image is not loaded",
            noImagesMessage: "There are no images links you added<br><small>Slibox</small>",
            imageSize: "contain",
            loaderLink: false,
            imagePosition: "center",
            animateCSS: false,
            carousel: false,
            timer: false,
            timerTime: 5000,
            timerCarousel: true,
        }, options);

        /**
         * Перебор this.each излишен и не несет никакой смысловой нагрузки.
         * Для chaining можно просто сделать return this
         *
         */

        /**
         * Название el не подходит для строки, к тому же не стоит сокращать.
         * Селектор также можно взять из уже обернутого this вместо "#" + this.id
         * 
         * Также неизменяемые примитивы нужно объявлять через const
         *
         */
        const wrapperId = this.selector;

        /**
         * ВАЖНО: оборачивать элементы каждый раз - значит обращаться к DOM, это может быть нужно
         * только тогда, когда элементы динамические. Статичные необходимо кешировать в переменных.
         * Враппер можно взять из this, а не искать через $(wrapperId).
         * 
         * Названия jQuery-переменных рекомендую начинать с $, чтобы избежать путаницы с другими типами
         * сущностей
         * 
         */

        const $wrapper = this;

        $wrapper.addClass("slibox");

        /**
         * Двойное равенство не рекомендую использовать никогда, исключение - if (variable != null),
         * так как это более лаконичная форма для if (variable !== null && variable !== undefined).
         * 
         * imagesLinks - массив, если нужно на него проверить, то "object" != typeof extendedOptions.imagesLinks
         * не подойдет.
         *
         */

        if (!Array.isArray(extendedOptions.imagesLinks) || extendedOptions.imagesLinks.length === 0) {
            /**
             * Не стоит писать конструкции вида return 1, 2, false. Инструкции должны быть описаны отдельно,
             * а возвращаемое значение - соответствовать глобально возвращаемому, то есть this в данном случае
             *
             */

            extendedOptions.imagesLinks = [];

            $wrapper.html('<h1 class="sb-error">' + extendedOptions.noImagesMessage + "</h1>")

            return this;
        }

        if (!$wrapper.children('.sb-slides')) {
            $('<div/>', {
                class: 'sb-slides'
            }).appendTo(wrapperId);
        }

        /**
         * Хранить данные в объекте элемента конечно можно, но смысла в этом нет. Лучше переделать на
         * отдельный объект. Это позволит IDE выдавать подсказки, что не получится в паттерне $(this).data("sb-slide")
         *
         */

        /**
         * ВАЖНО: все строки нужно перевести в именованные константы для избежания опечаток и дубляжей
         *
         */

        $wrapper.data("sb-slides-count", extendedOptions.imagesLinks.length);
        $wrapper.data("sb-carousel", extendedOptions.carousel);
        $wrapper.data("sb-timer-time", extendedOptions.timerTime);
        $wrapper.data("sb-timer-carousel", extendedOptions.timerCarousel);
        $wrapper.data("sb-timer", extendedOptions.timer);

        /*
         * Setting a link of the loader
         */
        if ("string" === typeof extendedOptions.loaderLink) {
            $wrapper.css({
                background: "url(" + extendedOptions.loaderLink + ") no-repeat center"
            })
        }

        /*
         * Create Arrows
         */
        if (extendedOptions.renderArrows) {
            $('<div/>', {
                class: 'sb-arrow sb-arrow-left',
                html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><g><path fill="#da5858" stroke="#da5858" d="M45.539,63.41c0.394,0.394,0.908,0.59,1.424,0.59s1.031-0.197,1.424-0.59c0.787-0.787,0.787-2.061,0-2.848 L20.059,32.233L48.407,3.886c0.786-0.787,0.786-2.062,0-2.848c-0.787-0.787-2.062-0.787-2.849,0L15.822,30.773 c-0.205,0.206-0.384,0.506-0.484,0.778c-0.273,0.738-0.092,1.567,0.465,2.124L45.539,63.41z" /></g></svg>',
                data: {
                    'sb-slider': wrapperId,
                    'sb-arrow-direction': 'left'
                }
            }).appendTo(wrapperId);
            $('<div/>', {
                class: 'sb-arrow sb-arrow-right',
                html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><g><path fill="#da5858" stroke="#da5858" d="M44.152,32.024L15.824,60.353c-0.787,0.787-0.787,2.062,0,2.849c0.394,0.394,0.909,0.59,1.424,0.59 c0.515,0,1.031-0.196,1.424-0.59l29.736-29.736c0.557-0.557,0.718-1.439,0.445-2.177c-0.101-0.272-0.26-0.519-0.464-0.725 L18.652,0.828c-0.787-0.787-2.062-0.787-2.848,0c-0.787,0.787-0.787,2.061,0,2.848L44.152,32.024z" /></g></svg>',
                data: {
                    'sb-slider': wrapperId,
                    'sb-arrow-direction': 'right'
                }
            }).appendTo(wrapperId);
        }

        /**
         * Проще переборы делать через extendedOptions.imagesLinks.forEach
         *
         */
        for (let i = 1; i <= extendedOptions.imagesLinks.length; i++) {
            let slide = $(wrapperId + ' .sb-slides .sb-slide:nth-of-type(' + i + ')');
            if (slide.length != 0) {
                slide = slide
                    .data('sb-slide', i)
                    .attr('draggable', true)
                    .css({
                        'background-image': 'url("' + extendedOptions.imagesLinks[i - 1] + '")',
                        'background-repeat': 'no-repeat'
                    })
                    .addClass('sb-slide-' + i)
                    .html('<div class="sb-slider-content">' + slide.html() + '</div>');
            } else {
                slide = $('<div/>', {
                    class: 'sb-slide sb-slide-' + i,
                    data: {
                        'sb-slide': i
                    },
                    draggable: true,
                    css: {
                        'background-image': 'url("' + extendedOptions.imagesLinks[i - 1] + '")',
                        'background-repeat': 'no-repeat'
                    }
                }).appendTo(wrapperId + ' .sb-slides').html('<div class="sb-slider-content"></div>');
            }

            /*
             * Create Controllers
             */
            if (extendedOptions.renderControllers) {
                if (0 == i - 1) {
                    $('<div/>', {
                        class: 'sb-controllers',
                        data: {'sb-slider': wrapperId}
                    }).appendTo(wrapperId);
                }
                $('<div/>', {
                    class: 'sb-controller',
                    data: {
                        'sb-slider': wrapperId,
                        'sb-controller': i
                    }
                }).appendTo(wrapperId + ' .sb-controllers');
            }

            /*
             * Animate.css functionality
             */
            if (extendedOptions.animateCSS) {
                if ("object" == typeof extendedOptions.animateCSS) {
                    if (extendedOptions.animateCSS.length < i) {
                        $(slide).addClass(extendedOptions.animateCSS[extendedOptions.animateCSS.length - 1])
                    } else {
                        $(slide).addClass(extendedOptions.animateCSS[i - 1]);
                    }
                } else if ("string" == typeof extendedOptions.animateCSS) {
                    $(slide).addClass(extendedOptions.animateCSS);
                }
                $(slide).addClass("animated");
            }

            /*
             * Image Size
             */
            if (extendedOptions.imageSize) {
                if ("object" == typeof extendedOptions.imageSize) {
                    if (extendedOptions.imageSize.length < i) {
                        $(slide).css({
                            "background-size": extendedOptions.imageSize[extendedOptions.imageSize.length - 1]
                        })
                    } else {
                        $(slide).css({
                            "background-size": extendedOptions.imageSize[i - 1]
                        })
                    }
                } else if ("string" == typeof extendedOptions.imageSize) {
                    $(slide).css({
                        "background-size": extendedOptions.imageSize
                    })
                }
            }

            /*
             * Image Position
             */
            if (extendedOptions.imagePosition) {
                if ("object" == typeof extendedOptions.imagePosition) {
                    if (extendedOptions.imagePosition.length < i) {
                        $(slide).css({
                            "background-position": extendedOptions.imagePosition[extendedOptions.imagePosition.length - 1]
                        })
                    } else {
                        $(slide).css({
                            "background-position": extendedOptions.imagePosition[i - 1]
                        })
                    }
                } else {
                    $(slide).css({
                        "background-position": extendedOptions.imagePosition
                    })
                }
            }
        } // End for

        $wrapper.width(extendedOptions.width);

        if (!extendedOptions.height) {
            $wrapper.height(0.5625 * $wrapper.width())
        } else {
            $wrapper.height(extendedOptions.height)
        }

        if ("number" == typeof extendedOptions.activeSlide) {
            if (extendedOptions.activeSlide > extendedOptions.imagesLinks.length) {
                $wrapper.data("sb-active-slide", extendedOptions.imagesLinks.length)
            } else if (extendedOptions.activeSlide < 1) {
                $wrapper.data("sb-active-slide", 1)
            }
            $wrapper.data("sb-active-slide", extendedOptions.activeSlide)
        } else {
            $wrapper.data("sb-active-slide", 1)
        }

        if (extendedOptions.timer) {
            $wrapper.data('sb-timed-carousel', extendedOptions.timerCarousel);
            $('<div/>', {
                class: 'sb-timer-container',
            }).append($('<div/>', {
                class: 'sb-timer sb-timer-animate',
                style: 'animation-duration: ' + extendedOptions.timerTime + 'ms'
            })).appendTo(wrapperId);
            this.time = 2;
        }

        $(".sb-controller").unbind('click');
        $(".sb-controller").click(function () {
            $($(this).data("sb-slider")).slideTo($(this).data("sb-controller"))
        })

        $(".sb-arrow").unbind('click');
        $(".sb-arrow").click(function () {
            let sliderId = $(this).data("sb-slider"),
                activeSlide = $wrapper.data("sb-active-slide");
            if ("left" == $(this).data("sb-arrow-direction")) {
                $(sliderId).slideTo(activeSlide - 1)
            } else if ("right" == $(this).data("sb-arrow-direction")) {
                $(sliderId).slideTo(activeSlide + 1)
            }
        })

        $wrapper.hover(function () {
            let sliderId = '#' + this.id;
            $(sliderId + ' .sb-timer').css('animation-play-state', 'paused');
            $(sliderId)[0].paused = true;
        }, function () {
            let sliderId = '#' + this.id;
            $(sliderId + ' .sb-timer').css('animation-play-state', 'running');
            $(sliderId)[0].paused = false;
        });

        $wrapper.slideTo($wrapper.data("sb-active-slide"), true);

        $(wrapperId + ' .sb-slide').each(function () {
            let box = $(this),
                container = $wrapper[0];
            this.boxOffset, this.myDragFlag = false
            box.on('selectstart', function () {
                sbCanDrag = false;
            });

            box[0].ondragstart = function (e) {
                if (sbCanDrag) {
                    this.startX = e.pageX - box[0].offsetLeft - container.offsetLeft
                    this.myDragFlag = true
                }
            }
            box[0].ondragend = function (e) {
                if (sbCanDrag) {
                    this.boxOffset = e.pageX - this.startX;
                    if (this.boxOffset - container.offsetLeft <= -20) {
                        /**
                         * Вычисляемые переменные нужно оформлять в отдельных константах для лучшей читаемости
                         *
                         */
                        $wrapper.slideTo($(this).data("sb-slide") + 1)
                    }
                    if (this.boxOffset - container.offsetLeft >= 20) {
                        $wrapper.slideTo($(this).data("sb-slide") - 1)
                        this.myDragFlag = false
                    }
                } else {
                    sbCanDrag = true;
                }
            }
        })

        return this;
    }
}(jQuery);

Все еще вижу подобные конструкции:


this.each(function() {
  const $wrapper = $(this)
  $wrapper.slibox('slideTo', $wrapper.data('activeSlide') - 1)
})
return this

this.each не нужен, а само слово this в большинстве случаев лучше не использовать, т.к. непонятно, что в нем находится — контекст штука динамическая. В данном случае можно так


const $wrapper = this;
const prevSlideIndex = $wrapper.data('activeSlide') - 1;

$wrapper.slibox('slideTo', prevSlideIndex);

return $wrapper;

Также вижу анонимные функции, конструкции !1, перебор for вместо forEach, двойное равенство, проверку 'object' != typeof extOptions.imagesLinks вместо Array.isArray, хранение данных в $wrapper.data вместо обычного объекта. В общем, меньше половины рекомендаций реализовано, так что дальше анализировать пока не буду

Три года назад JQuery был мёртв уже несколько лет. Вы три года потратили на изучение библиотеки, от которой уже на тот момент успели отказаться даже самые неповоротливые любители легаси, всё это время упорно игнорируя всё, что происходило в мире фронтенда? Зачем?

Мне показалось, что в статичных страничках использовать jQuery не плохо. А что вы можете посоветовать использовать вместо него?

Как уже написали выше, ванильный js умеет всё то же, что и JQuery.

UFO just landed and posted this here

Через пару лет ждём статью про backbone, а ещё лет через пять — про стрелочные функции :-)

Тоже писал велосипед слайдер собственного сочинения, но на VanillaJS с поддержкой ES5. Прекрасный опыт. Целая гора новых знаний. И чувство удовлетворения от завершения процесса.

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

И все-таки не совсем понятно, зачем для этой цели выбирать jQuery. Если речь не стоит о поддержке старых браузеров, то чем плох современный JS? Если ради опыта и интереса, тогда понятно. А если нет, то нет.
Да, ради опыта и интереса, не для клиента.

Мне очень нравится ES6, его синтаксический сахар и всё такое, но я хотел написать свой слайдер, который будет работать на старых браузерах, по пути научиться писать плагины на jQuery.
Ребята, вы что-то как-то уж резко человека осуждаете. Он же сам написал, что только учится. Да, jQuery устарел, я с этим тоже согласен, но на него уж слишком набросились. Так можно и отбить желание учиться программированию.

А автору статьи я бы рекомендовал начать изучить что-нибудь из этих фреймворков: React, vue или Angular. Коммерческой пользы от потраченного времени больше будет.
Осуждение вполне обосновано. Хочет чувак учиться, пускай идёт и учится. Курсов полно. Написание статьи и обучение, различные вещи. Какой опыт вы сможете перенять у человека с сомнительным опытом?
Я не против осуждения, значит мне есть куда расти. Опыт написания статьи тоже ведь опыт? )
Спасибо за поддержку и понимание! Так как я еще не занимаюсь коммерческой разработкой, больше делаю упор на обучение. Правда, хотелось бы посмотреть как обстоят дела в современной команде, чтобы понять какими стеками и инструментами пользуются профессионалы. Короче, нужен ментор, пока я совсем не пропал )
Сейчас я изучаю Vue, отдельно и в связке с Laravel.
Надо было назвать статью. «Как мне превратить хабр в хламовник своей тривиальной пиар-статьёй»

Разве хабр не хламовник? Лучше читать технические статьи, чем про эффективных менеджеров.

Sign up to leave a comment.

Articles