CSS: новый вид JavaScript

Перевод «CSS: A New Kind Of JavaScript»

Хейдон Пикеринг

Перевод Вадим Макеев

Редактура Андрей Мелихов Ольга Алексашенко

Те из вас, кто знаком с веб-платформой, наверняка наслышаны и о двух дополнительных технологиях: HTML для структуризации документов и JavaScript для интерактивности и стилизации.

Сколько мы себя помним, стилизация документов (то есть, влияние на их внешний вид) всегда производилась с помощью JavaScript-свойства style, доступного на каждом поддерживаемом DOM-узле.

node.style.color = 'red';

До изобретения этого API для стилизации авторам HTML приходилось писать атрибуты style вручную прямо в DOM, что затрудняло процесс редактирования.

Используя выборку узлов по селектору из JS, мы можем стилизовать несколько элементов одновременно. В примере ниже текст во всех <p> стилизован красным цветом.

const nodes = document.querySelectorAll('p');
Array.prototype.forEach.call(nodes, node => {
    node.style.color = 'red';
});

Отличная возможность выборки по селектору — обращение к нескольким элементам, перечисленным в списке через запятую.

const nodes = document.querySelectorAll('p, li, dd');

Гораздо менее удобно применять несколько стилей к одному узлу. Такой подход быстро становится слишком многословным:

node.style.color = 'red';
node.style.backgroundColor = 'black';
node.style.padding = '1rem';
// и т.д.

Единственная стандартная альтернатива — использовать свойство cssText:

node.style.cssText = 'color: red; background-color: black; padding: 1rem;';

Управлять несколькими стилями в одной строке проблематично. В будущем будет сложно обновлять, удалять или заменять отдельные стили.

Для этого авторы придумали способы управления информацией о стилях с помощью объектов, часто при помощи прототипа интерфейса Element.

Element.prototype.styles = function(attrs) {
    Object.keys(attrs).forEach(attr => {
        this.style[attr] = attrs[attr];
    });
}

Теперь стили к узлу можно добавлять вот так:

node.styles({
    'color': 'red',
    'backgroundColor': 'black',
    'padding': '1rem'
});

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

А есть и другая, более фундаментальная проблема: эти стили не реактивны. К примеру, у меня есть некоторые стили для неактивных кнопок:

const disableds = document.querySelectorAll('[disabled]');

Array.prototype.forEach.call(disableds, disabled => {
    disabled.styles({
    'opacity': '0.5',
    'cursor': 'not-allowed'
});

Эти стили применяются только к неактивным кнопкам, которые уже есть в DOM. Любые кнопки, добавленные в DOM или, что более вероятно, любые кнопки, которые приобретут свойство или атрибут disabled, не получат автоматически подходящую стилизацию.

button.disabled = true;
button.style // ничего нового

Можно, конечно, слушать изменение атрибутов и реагировать на него с помощью mutationObserver:

const button = document.querySelector('button');

var config = { attributes: true }

var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'attributes') {
            if (button.disabled) {
                button.styles({
                    'opacity': '0.5',
                    'cursor': 'not-allowed'
                });
            }
        }
    }
}

var observer = new MutationObserver(callback);

observer.observe(button, config);

Думаю, что все согласятся, что это — довольно много кода, с учётом того, что он всего лишь заставляет один экземпляр элемента одного типа реагировать на изменение единственного атрибута. Также это меняет стилизацию только в одном направлении: нам бы пришлось обрабатывать отмену стилей при удалении свойства disabled. Это не так уж и просто, учитывая, что мы не знаем изначальных значений свойств opacity или cursor.

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

Мы тратим слишком много времени впустую, когда пишем и поддерживаем стили с помощью JavaScript. Пришло время это менять. Поэтому я с большим удовольствием представляю вам новый стандарт, который называется CSS.

CSS#

CSS — это декларативное подмножество JavaScript, оптимизированное для задач стилизации. CSS-файл имеет расширение .css и, что важно, парсится независимо от стандартных JS-файлов. С помощью CSS наконец-то стало возможно отделить стили от поведения. Вы можете брендировать своё приложение, не трогая бизнес-логику!

Синтаксический сахар#

Первое, что вы заметите — это более чистый синтаксис, фанаты CoffeeScript оценят:

[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

Структура, похожая на объектную, сохраняется, но теперь вам не нужно явно вызывать querySelectorAll для перебора DOM-узлов. Вместо этого перебор происходит внутри, и это, конечно же, производительнее.

Пример выше автоматически применяется ко всем DOM-узлам с атрибутом disabled. Более того, все новые кнопки с атрибутом disabled автоматически приобретут связанные стили. Реактивность из коробки!

Каскад#

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

button {
    background-color: blue;
    cursor: pointer;
}

[disabled] {
    cursor: not-allowed;
}

Стили для блока [disabled] идут после блока button в стилях. Любые стили, описанные в блоке [disabled] с такими же ключами (именами свойств), что и в предшествующем button, будут перезаписаны. Самое замечательное заключается в том, что добавление атрибута или свойства disabled кнопке обновит только соответствующие стили. В примере выше свойство cursor обновится, но background-color останется тем же. Своего рода система фильтрации.

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

Устойчивость#

При стилизации с помощью JavaScript любое неизвестное свойство или синтаксическая ошибка остановят парсинг скрипта. Все последующие стили или поведение будут отброшены целиком и всё ваше приложение просто рухнет.

CSS гораздо надёжнее. В большинстве случаев любое неизвестное свойство или синтаксическая ошибка приведут только к тому, что только декларация (пара свойство: значение) с ошибкой будет отброшена.

Эта инновация учитывает, что разные браузеры поддерживают разные свойства стилизации, и что отдельные стили не критичны для всей задачи. Устойчивость CSS значит, что больше пользователей получат доступ к функционирующему интерфейсу.

Заключение#

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

CSS решает проблемы JavaScript со стилизацией, причём элегантно. Вопрос в том, готовы ли вы принять изменения или вы завязли в неудачной методологии?

Больше информации про CSS и советы для быстрого старта вы найдете здесь.