Достъпен Слайдер (Accessible Slider) – 2/2

22.01.2019

За годините, изминали от оригиналното публикуване на статията, технологиите са се променили прекалено много. Моля не следвайте описаното по-долу като урок как да създадете slider, а гледайте на нещата само като на насоки за някои основни принципи като:

  1. класове в JavaScript
  2. публични и частни променливи на JS обект
  3. достъпност в JS:
    1. сайтът да не се чупи без JS
    2. контролите, които добавяме да функционират и с клавиатурата

Стигнахме и до втора част от нашия урок. Тук вече ще разгледаме и JS кода, който ще превърне нашия списък в истински слайдер.

Зареждането на JavaScript-а

HTML кодът, който използваме за зареждане на нашия скрипт, има една малка особеност, която може и да сте пропуснали преглеждайки кода от първия урок, която е атрибутът defer="defer". Това е нов атрибут, добавен в HTML5, който е позволен само за външни <script> тагове (такива, които имат src атрибут и зареждат кода от друг файл, а кодът не е inline — нека думата “външни” не ви кара да се бъркате, че става въпрос за скриптове от друг домейн — другите домейни в случая нямат значение), които трябва да се изпълнят след парсирането на целия HTML код по страницата. Иначе казано — скриптовете трябва автоматично да се изпълнят на DOM Ready event-а.

Въпреки всичко, в самия script аз ще си добавя DOM Ready event handler, заради по-стари браузъри, които не поддържат този атрибут. По този начин, ако браузърът е достатъчно нов печеля това, че просто рендирането на страницата няма да се забави от JIT компилацията на JS кода и е все едно да съм го заредил в края на сайта, но пак запазвам подредбата всички JS файлове да са си в <head>, което според мен е семантично и подредено и затова държа на него.

За да бъде кодът ни добре подреден и преизползваем, ще го обвием в един простичък JS клас. Тъй като кодът ни ще бъде jQuery зависим, би било най-добре да го направим под формата на jQuery plugin, но в случая ще прескоча тази част и ще я оставя за някой бъдещ урок.

OOP JavaScript

Ще дефинираме нов клас с името Slider. В JavaScript това става като просто създадем нова функция с това име.

function Slider() {

}

Ще развием функцията като добавим аргумент, който да съдържа обекта, който ще искаме да превърнем в слайдер, както и ще дефинираме няколко параметъра, които ще са ни нужни за самия слайдер.

function Slider() {
    // private variables
    var container = slider.addClass('js');
    var ul = container.find('ul');
    var lis = ul.find('li');
    var btnNext = null;
    var btnPrev = null;
    var currentSlide = 0;

    // public variables
    this.duration = 300;
}

Сам по себе си JavaScript няма поддръжка за public и private променливи и методи, затова прилагаме друга особеност на езика. Променливите, които дефинираме с var са private, тъй като са дефинирани в scope-а на функцията на класа и не могат да бъдат достъпени извън тази функция.

От друга страна променливи (в този случай е просто една), които са дефинирани като this.{variable_name} са публични, тъй като по този начин ги дефинираме като част от класа и винаги когато имаме инстанция на този клас имаме и достъп до тези променливи.

По аналогичен начин създаваме и private метода slideTo, който извършва три прости действия:

  1. спира всички текущи анимации, в които участва нашия списък
  2. започва нова анимация
  3. задава нова стойност на променливата currentSlide.
function Slider() {
    ...

    // private methods
    function slideTo(index) {
        ul.stop();
        ul.animate({ left: '-' + (index * lis.outerWidth(true)) + 'px' }, this.duration);
        currentSlide = index;
    }
}

За да пресметнем къде трябва да се намира слайдера трябва да умножим номера на елемента, към който ще слайдваме с широчината на един такъв елемент.

lis.outerWidth(true) връща точно широчината (включително и от padding и margin за първия елемент от lis).

Анимираме left стила на слайдера до минус това отместване, което пресметнахме. Минус, защото относително искаме да преместим прозореца, през който виждаме слайдера.

Да, но прозорецът трябва да запази мястото си, а не да се мести, което означава, че вместо да местим прозореца надясно, трябва да преместим елементите наляво.

Освен това създаваме и два публични метода — за връщане на текущия слайд и за фокусиране на слайдера.

function Slider() {
    ...

    // public methods
    this.currentSlide = function() {
        return currentSlide;
    }

    this.focus = function() {
        container.focus();
    }
}

Фокусирането е възможно благодарение на онзи tabindex атрибут, който добавихме на слайдера си още в HTML-а. Това ще ни позволи след малко да добавим JavaScript, който да се изпълнява в отговор на клавиатурни event-и когато на фокус е нашият слайдер. След малко ще стигнем и до тях.

function Slider() {
    ...

    this.slideNext = function() {
        if (++currentSlide >= lis.length) currentSlide = 0;
        slideTo(currentSlide);
    }

    this.slidePrev = function() {
        if (--currentSlide < 0) currentSlide = lis.length - 1;
        slideTo(currentSlide);
    }
}

Добавяме още два публични метода, които да позволят на други скриптове да комуникират с нашия слайдер и съответно да преминават на следващия слайд или да се връщат на предишния.

Инициализация

След като сме подготвили по-голямата част от функциите на слайдера си остава и да го инициализираме.

function Slider() {
    ...

    // initialization
    slider.data('slider', this);

    container.append('<a href="#" class="prev" /><a href="#" class="next" />');

    btnNext = container.find('a.next').click(function() {
        s.slideNext();
        return false;
    });

    btnPrev = container.find('a.prev').click(function() {
        s.slidePrev();
        return false;
    });

    container.keydown(function(e) {
        if (e.which == 39) {
            s.slideNext();
        } else if (e.which == 37) {
            s.slidePrev();
        }
    });

    this.focus();
}

Първо записваме референция към самия slider обект като data property на елемента, който превръщаме в слайдер. Едно малко удобство, ако имаме няколко слайдера на страницата и не ги пазим в отделни променливи.

После добавяме двата линка, които ще служат за навигация в слайдера.

На всеки от тези линкове добавяме click event handler, който вика съответните методи на слайдера за навигация. Ето тук има една променлива s, която може да ви обърка. Това би трябвало да е просто this, но тъй като извикването на метода става в това функция, там вътре this е просто линкът, който е кликнат. По тази причина трябва да си създадем нова променлива, която да съдържа референция към обекта на класа, която бидейки различна от this можем да използваме в event handler функцията. Най-лесно е тази променлива да си я запишем просто в началото на кода на функцията на класа:

var s = this;

Нищо повече не е нужно.

Добавяне на достъпност

Следват и event handler-ите за keydown на container-а.

Тези event-и се изпълняват само когато той е на фокус — ето затова ни беше.

Лично аз смятам, че тази възможност на слайдера, който правим, е жизнено важна, тъй като именно тя го превръща в достъпен (accessible).

Да бъде един JS достъпен означава не само той да е unobtrusive (да е написан така, че нищо да не се чупи, когато посетителите на сайта нямат JavaScript), но и когато кодът дава възможност за работа със съдържанието дори и без мишка.

Достъпността има различни форми и ние винаги трябва да се грижим за всички.

Тази фокус вазможност на слайдера е достъпност, навигация със стрелките от клавиатурата на компютъра ни също е за достъпност. Срещал съм много jQuery slider plugin-и — много от тях са били кадърно написани, много са били направени просто докато някой се е учил, но така и не съм виждал slider, който да е напълно достъпен.

Слайдерът, който правим тук е значително ограничен и не би бил добър, ако се превърне в plugin, но е може би едно от най-добрите custom решения, ако правите сайт.

В края на кода на слайдера просто преместваме фокуса на слайдер контейнера, така че още при инициализирането му да можем да използваме стрелките за навигация между елементите му.

Може да ви се стори забавен и факта, че въпреки, че кодът ни разчина на jQuery, никъде не сме използвали нито $ нито jQuery, което е едно удобство при използване на няколко библиотеки и jQuery.noConflict().

В края на скриптовия си файл остава само да добавим и същинския jQuery код за страницата ни, който да изчака DOM Ready event-а и да създаде нова инстанция на нашия слайдер, подавайки му <div id="slider"> елемента, който сме създали с това предназначение.

jQuery(function() {
    var slider = new Slider($('#slider'));
});

Цялостен код

Ето го и пълния код на трите ни файла (HTML, CSS и JS), а в края на статията има и връзка към демо на двете части на урока.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Slider</title>
    <link rel="stylesheet" href="style.css" media="all" />
    <script type="text/javascript" charset="utf-8" src="http://code.jquery.com/jquery-1.6.min.js"></script>
    <script type="text/javascript" charset="utf-8" src="func.js" defer="defer"></script>
</head>
<body>
    <div id="slider" tabindex="0">
        <ul>
            <li><img src="images/slide-1.jpg" alt="" /></li>
            <li><img src="images/slide-2.jpg" alt="" /></li>
            <li><img src="images/slide-3.jpg" alt="" /></li>
            <li><img src="images/slide-4.jpg" alt="" /></li>
            <li><img src="images/slide-5.jpg" alt="" /></li>
        </ul>
    </div>
</body>
</html>
* { margin: 0; padding: 0; }
body { padding: 20px 0; background: #333; color: #FFF; font: normal 12px Arial, Helvetica, sans-serif; }

#slider { position: relative; width: 960px; margin: 0 auto; border: 2px solid #000; }
#slider:focus { outline: none; }
#slider ul { width: 100%; list-style-type: none; overflow: hidden; }
#slider ul li { display: inline; float: left; }
#slider li img { float: left; }

#slider.js { height: 540px; overflow: hidden; }
#slider.js:focus { border-color: #666; }
#slider.js ul { position: absolute; width: 50000em; height: 100%; }
#slider.js ul li { width: 960px; height: 100%; }
#slider.js a { position: absolute; top: 250px; z-index: 10; width: 20px; height: 40px; background: rgba(255, 255, 255, 0.2); }
#slider.js .next { right: 10px; }
#slider.js .prev { left: 10px; }
function Slider(slider) {
    // object holder
    var s = this;

    // private variables
    var container = slider.addClass('js');
    var ul = container.find('ul');
    var lis = ul.find('li');
    var btnNext = null;
    var btnPrev = null;
    var currentSlide = 0;

    // public variables
    this.duration = 300;

    // private methods
    function slideTo(index) {
        ul.stop();
        ul.animate({ left: '-' + (index * lis.outerWidth(true)) + 'px' }, this.duration);
        currentSlide = index;
    }

    // public methods
    this.currentSlide = function() {
        return currentSlide;
    }

    this.focus = function() {
        container.focus();
    }

    this.slideNext = function() {
        if (++currentSlide >= lis.length) currentSlide = 0;
        slideTo(currentSlide);
    }

    this.slidePrev = function() {
        if (--currentSlide < 0) currentSlide = lis.length - 1;
        slideTo(currentSlide);
    }

    // initialization
    slider.data('slider', this);

    container.append('<a href="#" class="prev" /><a href="#" class="next" />');

    btnNext = container.find('a.next').click(function() {
        s.slideNext();
        return false;
    });

    btnPrev = container.find('a.prev').click(function() {
        s.slidePrev();
        return false;
    });

    container.keydown(function(e) {
        if (e.which == 39) {
            s.slideNext();
        } else if (e.which == 37) {
            s.slidePrev();
        }
    });

    this.focus();
}

jQuery(function() {
    var slider = new Slider($('#slider'));
});

https://magadanski.com/demo/slider/

3 Отговори на “Достъпен Слайдер (Accessible Slider) – 2/2”

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *

Този сайт използва Akismet за намаляване на спама. Научете как се обработват данните ви за коментари.