Утром доделаю

Про аутлайны

Есть такая банальная проблема дефолтных аутлайнов: кликаешь на ссылку или кнопку, появляется аутлайн. Начинающих разработчиков он бесит, и они его радостно убивают с помощью какого-нибудь *:focus {outline: none}. Прошаренные разработчики знают, что аутлайн убивать нельзя, потому что он помогает ориентироваться при навигации с клавиатуры и со вспомогательных устройств.

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

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

Ситуация отличается не только между браузерами, но и у различных фокусируемых элементов внутри одного браузера. Три вида фокусируемых «нажимабельных» элементов — кнопка, ссылка и элемент с tabindex="0":

Для чистоты эксперимента элементы сидят в айфрейме

А теперь по браузерам — есть ли аутлайн после клика:

Браузер Кнопка Ссылка Спан
Chrome 39 Да Нет Да
Firefox 34 Win Нет Нет Нет
Firefox 34 Mac Нет Нет Да
Safari 8 Нет Нет Да
Opera 12 Нет Нет Нет
IE 10, 11 Нет Нет Нет
IE 7—9 Да Да Да

Старая Опера избавилась от всех «мышиных» аутлайнов раньше всех, но умерла, ИЕ подтянулся в 10 версии, Фаерфокс тоже почти молодец.

Со ссылками ситуация хорошая уже сейчас, поэтому, если придумать стиль фокуса кнопок, который не будет бесить, с аутлайнами даже напрягаться не надо.

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

JS + jQuery:

(function() {
    var mouseFocusedClass = 'is-mouse-focused';

    $(document.body).on('mousedown', function() {
        //wait for `document.activeElement` to change
        setTimeout(function() {
            //find focused element
            var activeElement = document.activeElement,
                $activeElement = $(activeElement);

            //if found and it's not body...
            if (activeElement && activeElement !== document.body) {
                //add special class, remove it after `blur`
                $activeElement.addClass(mouseFocusedClass).one('blur', function() {
                    $activeElement.removeClass(mouseFocusedClass);
                });
            }
        }, 0);
    });
})();

Vanilla JS (не будет работать в IE8, потому что addEventListener):

(function() {
    var mouseFocusedClass = 'is-mouse-focused';

    document.body.addEventListener('mousedown', function() {
        //wait for `document.activeElement` to change
        setTimeout(function() {
            //find focused element
            var activeElement = document.activeElement;

            //if found and it's not body...
            if (activeElement && activeElement !== document.body) {
                //add special class, remove it after `blur`
                activeElement.className += ' ' + mouseFocusedClass;
                activeElement.addEventListener('blur', function(e) {
                    e.target.removeEventListener(e.type, arguments.callee);

                    activeElement.className = activeElement.className.replace(new RegExp('(\\s+|^)'+mouseFocusedClass+'(\\s+|$)', 'g'), ' ').replace(/^\s+|\s+$/g, '');
                });
            }
        }, 0);
    });
})();

Легким движением руки отключаем ненавистные аутлайны, не попортив доступности:

/* keyboard custom focus style */
:focus {
    outline: 1px solid blue;
}

/* mouse/touch focus style */
:active,
.is-mouse-focused:focus {
    outline: none;
}

Костыль ставит класс через setTimeout() (иначе document.activeElement не успевает переключиться), поэтому в селектор добавлен :active (который срабатывает только с мышкой, кстати), чтобы элемент не мигал в момент переключения класса. Если у вас есть более элегантное решение, поправьте код на гитхабе или киньте ссылочку в твиттер.

Пользуйтесь и не убивайте аутлайны полностью, пожалуйста.

Опубликовано в рубрике «Чиним интернет»