На переднем крае

Программирование CSS: делайте больше с помощью LESS

Дино Эспозито

Дино ЭспозитоВ этой статье мы обсудим веб-разработку с применением инфраструктуры LESS Framework для динамической генерации CSS-контента.

Несомненно, что появление CSS стало в свое время большим шагом вперед к полному отделению контента от его представления на веб-страницах. Хотя CSS находится (или должен быть?) в компетенции дизайнеров, он вводит принцип разделения обязанностей, задевающий практически любого разработчика. Поэтому CSS быстро завоевал популярность и теперь тесно интегрирован в веб-разработку до такой степени, что иногда с трудом поспевает за современными веб-сайтами.

Дело не в том, что CSS недостаточно для оформления современных, с богатой графикой веб-сайтов, а скорее в том, что чисто декларативный язык не всегда годится для выражения сложных и взаимосвязанных объявлений стилей. К счастью, браузеры по-прежнему умеют обрабатывать любой CSS, если только он написан корректно, но можно ли сказать то же самое о людях?

Сравнительно новое направление в веб-разработке нацелено на создание инфраструктуры вокруг CSS, чтобы разработчики и дизайнеры могли получать тот же CSS более рациональным способом. Конечная таблица стилей для браузера не меняется, но способ ее создания должен быть другим — более простым в чтении и сопровождении.

Эта область веб-разработки появилась несколько лет назад и сейчас достигла зрелого состояния, так что несколько имеющихся инфраструктур уже могут помочь вам в динамической генерации CSS-контента. Я дам краткий обзор одной из таких инфраструктур — LESS Framework— и покажу, как ее можно интегрировать в решения на основе ASP.NET MVC.

Почему LESS?

Одна из крупнейших проблем, решаемых с помощью LESS, — повторение информации. Вероятно, вам известен принцип DRY (don’t repeat yourself) (не повторяйтесь), и вы ежедневно его применяете. Основное преимущество DRY в том, что он уменьшает количество мест, где хранится одна и та же информация, и тем самым количество мест, где ее нужно обновлять.

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

Одна из проблем с CSS-классами — они работают на уровне семантического HTML-элемента. Создавая различные CSS-классы, вы часто сталкиваетесь с повторением небольших частей информации, таких как данные о цветах или размерах по ширине. Не так-то легко создать класс для каждой из этих повторяющихся малых частей. Даже если вы ухитритесь сделать это почти для всех повторяющихся стилей, например цветов и размеров по ширине, то, когда дело дойдет до оформления семантического элемента, скажем, контейнера, вы должны будете объединить множество CSS-классов для достижения нужного эффекта.

Если вы когда-нибудь пользовались инфраструктурой наподобие Bootstrap для дизайна своих веб-страниц, то знаете, что я имею в виду. Вот пример:

<a class="btn btn-primary" ... />

Тег anchor (a) сначала указывается как кнопка (класс btn), а затем уточняется конкретная разновидность кнопки (класс btn-primary). Этот подход работает, но может потребовать значительных усилий в планировании классов, которые вам понадобятся. Это приводит к дополнительным издержкам в веб-проектах, которые зачастую и так близки к предельным срокам реализации.

Язык динамических таблиц стилей, такой как LESS, отражает своего рода нестандартное мышление. Вы не тратите время на то, чтобы сделать свой чистый CSS интеллектуальнее; вы просто используете другие инструменты (в основном языки) для его генерации. Таким образом, LESS — это инфраструктура, которая вводит дружественные к программисту концепции в кодирование CSS, например переменные, блоки и функции.

С динамической генерацией CSS прямо связана задача его обработки и преобразования в чистый CSS, понятный браузеру. Клиент может обрабатывать LESS-код с помощью специализированного JavaScript-кода или предварительно обрабатывать его на сервере так, чтобы браузер просто получал конечный CSS.

LESS в ASP.NET MVC

Я продемонстрирую, что нужно для использования LESS в приложении ASP.NET MVC. Для начала я сосредоточусь на обработке LESS-кода на клиентской стороне. В раздел HEAD файла разметки добавьте следующее:

<link rel="stylesheet/less"
  type="text/css"
 href="@Url.Content("~/content/less/mysite.less")" />
<script type="text/javascript"
 src="@Url.Content("~/content/scripts/less-1.3.3.min.js")"></script>

Здесь предполагается, что вы создали в проекте папку Content/Less, которая будет содержать все ваши LESS-файлы. Вам понадобится JavaScript-файл для преобразования LESS в CSS в браузере. Этот файл скриптов вы можете получить с сайта lesscss.org. Рассмотрим несколько сценариев, где LESS доказал свою пользу.

LESS в действии: переменные

Хороший способ понять роль LESS-переменных — заглянуть в CSS-градиенты. Годами дизайнеры использовали небольшие GIF-файлы для прорисовки фона HTML-контейнеров с градиентами. Потом в браузеры добавили CSS-поддержку градиентов. Они также являются частью официального стандарта CSS3 и поддерживаются синтаксисом линейных градиентов и его вариациями. К сожалению, если вы хотите, чтобы данный градиент использовался максимально широким спектром браузеров, то должны прибегнуть примерно к такому коду, который показан на рис. 1.

Рис. 1. Полный код для отображения градиентов в широком спектре браузеров

/* Откат для старых браузеров */
background-color: #ff0000;
background: url(images/red_gradient.png);
background-repeat: repeat-x;
/* Синтаксис, специфичный для браузера */
background: -moz-linear-gradient(  left,  #fceabb 0%, 
  #fccd4d 50%, #f8b500 51%, #fbdf93 100%);
background: -Webkit-linear-gradient(  left, #fceabb 0%,
  #fccd4d 50%,#f8b500 51%,#fbdf93 100%);
background: -o-linear-gradient(  left, #fceabb 0%,
  #fccd4d 50%,#f8b500 51%,#fbdf93 100%);
background: -ms-linear-gradient(  left, #fceabb 0%,
  #fccd4d 50%,#f8b500 51%,#fbdf93 100%);
/* Стандартный синтаксис */
background: linear-gradient(  to right, #fceabb 0%,
  #fccd4d 50%,#f8b500 51%,#fbdf93 100%);

Код на рис. 1 практически не читаем. Еще хуже то, что его нужно повторять везде, где вам требуется этот градиент. Кроме того, если вы захотите слегка изменить цвет градиента (или просто его насыщенность или затухание), единственным вариантом будет редактирование вручную всех вхождений. Если без обиняков, то это может оказаться крайне трудным делом. Однако это единственный способ, который сработает в чистом CSS.

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

.background-gradient-orange { background: #fceabb; ... }
.container { .background-gradient-orange; }

Класс background-gradient-orange встраивается по имени в класс container и любой другой необходимый вам класс. Однако определение градиента хранится в одном месте.

В этом нет ничего революционного, если взглянуть на это с точки зрения разработчика. Однако здесь используется средство (переменные), которого нет в CSS. По сути, предыдущий синтаксис не будет работать, если вы сохраните показанный код в файле и будете ссылаться на него как на обычную таблицу стилей. Вам потребуется код для преобразования расширенного синтаксиса в чистый CSS. JavaScript-анализатор LESS как раз это и делает, раскрывая переменные в настоящий CSS-контент.

Переменные также применимы к скалярным значениям вроде цветов и размеров. Возьмем следующий LESS-код:

@black: #111;
#main {  color: @black; }
.header { background-color: @black; }

Анализатор (parser) раскрывает переменную @black в присвоенное значение и заменяет ее по всему файлу. В итоге вы изменяете цвет в одном месте, а в остальных местах файла он меняется автоматически.

LESS в действии: импорт

Вы можете разделить LESS-код между несколькими файлами и ссылаться на них и классы там, где это необходимо. Например, вы создаете файл gradients.less с таким контентом:

.background-gradient-orange { background: #fceabb; ... }

В другом LESS-файле, скажем, main.less, вы можете ссылаться на любые градиенты, импортируя этот файл:

@import "gradients";
.container { .background-gradient-orange; }

Если gradients.less (расширение файла не обязательно) находится в другой папке, вы должны указать путь к нему в вызове для импорта.

Подмешанные классы в LESS

Я назвал артефакт LESS для градиентов переменной. Если придираться к словам, то это не совсем правильно. В LESS переменная содержит одно значение. Контейнер для CSS-класса известен как подмешанный класс, или примесь (mixin). Он похож на функцию, но не содержит никакой пользовательской логики. Просто подмешанный класс в LESS может, как и функция, принимать и обрабатывать параметры. Рассмотрим код на рис. 2, демонстрирующий такой класс.

Рис. 2. Подмешанные классы в LESS Framework

/*  Подмешанные классы  */
.shadow(@color) {
  box-shadow: 3px 3px 2px @color;
}
.text-box(@width) {
  .shadow(#555);
  border: solid 1px #000;
  background-color: #dddd00;
  padding: 2px;
  width: @width;
}
/*  CSS-классы  */
.text-box-mini {
  .text-box(50px);
}
.text-box-normal {
  .text-box(100px);
}
.text-box-large {
  .text-box(200px);
}

На рис. 2 подмешанный класс с именем shadow определяет стили для прямоугольной тени и предоставляет цвет как внешний параметр. Аналогично подмешанный класс text-box определяет базовый внешний вид поля ввода. Он импортирует определение shadow и хранит width как параметр. Тем самым очень легко определить три класса для полей ввода разных размеров (mini, normal и large). Еще важнее, что для обновления требуется минимум усилий (рис. 3).

Подмешанные классы LESS в действии
Рис. 3. Подмешанные классы LESS в действии

Подмешанные классы могут принимать несколько параметров, а также варьируемое количество параметров. Кроме того, индивидуальные параметры поддерживают значения по умолчанию:

.mixin(@color: #ff0) { ... }

LESS не является богатым языком программирования и по своей архитектуре не содержит команд, указывающих условия или циклы. Однако поведение подмешанного класса все же может меняться в зависимости от переданного значения. Допустим, вы хотите придать более крупной кнопке более толстую границу и более жирный шрифт. Вы определяете параметризованный подмешанный класс с именем button и используете ключевое слово when для связывания настроек с неким условием. Это условие должно основываться на единственном параметре:

.button (@size) when (@size < 100px) {
 padding: 3px;
 font-size: 0.7em;
 width: @size *2;
}
.button (@size) when (@size >= 100px) {
  padding: 10px;
  font-size: 1.0em;
  font-weight: 700;
  background-color: red;
  width: @size *3;
}

Вы применяете разные настройки, но также используете базовые операции для умножения размера на какой-то коэффициент. Затем используете подмешанные классы в CSS-классах:

.push-button-large {
  .button(150px);
}
.push-button-small {
  .button(80px);
}

Результат выполнения этого кода приведен на рис. 4.

Эффекты использования подмешанных классов LESS в CSS-классах
Рис. 4. Эффекты использования подмешанных классов LESS в CSS-классах

В LESS имеется длинный список предопределенных функций для манипуляций над цветами. В вашем распоряжении есть функции для затемнения, осветления и насыщения цветов на заданные доли в процентах, равно как и для затухания цветов или их «расцвета»:

.push-button {
  background-color: fade(red, 30%);
}

Полную документацию о функциях, поддерживаемых LESS, см. на lesscss.org.

Вложение классов

Лично я нахожу довольно раздражающей необходимость повторять блоки CSS для указания стилей одного вида (sibling styles). Типичный пример:

#container h1 { ... }
#container p { ... }
#container p a { ... }
#container img { ... }

В хорошо написанном чистом CSS на самом деле можно избежать большую часть повторов, но принцип структуризации стилей — с использованием линейного списка — не оптимален. В этом случае предпочтительнее иметь какое-то подобие иерархии. В LESS можно вкладывать правила стилей, например:

.container {
  h1 {
    font-size: 0.8em;
   color: fade(#333, 30%);
   a {
     color: #345;
     &:hover {color: red;}
    }
  }
}

Однажды обработанный LESS-код, показанный выше, дает следующие стили:

.container h1
.container h1 a
.container h1a:hover

Обработка на серверной стороне

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

Предварительная обработка на серверной стороне может оказаться более эффективным вариантом, если вам важна производительность и вы имеете дело с большими и сложными CSS-файлами. Такая обработка происходит всякий раз, когда вы модифицируете CSS на сервере. Вы можете вручную позаботиться о дополнительном этапе в конце процесса сборки. С этой целью вы используете компилятор LESS из командной строки. Компилятор является частью NuGet-пакета dotless, который устанавливается для работы на серверной стороне.

Однако в ASP.NET MVC 4 можно интегрировать LESS Framework с помощью механизма, который обсуждался в моей статье за октябрь 2013 г. «Programming CSS: Bundling and Minification». Это гарантирует, что преобразование LESS в CSS выполняется всякий раз, когда выдается запрос на LESS-файл. Кроме того, это обеспечивает должное управление кешированием через заголовок If-Modified-Since. Наконец, вы можете смешивать разбор (parsing) и минимизацию (minifying). Чтобы интегрировать LESS в ASP.NET MVC, сначала скачайте и установите NuGet-пакет dotless. Потом добавьте следующий код в класс BundleConfig:

var lessBundle =
  new Bundle("~/myless").IncludeDirectory("~/content/less", "*.less");
lessBundle.Transforms.Add(new LessTransform());
lessBundle.Transforms.Add(new CssMinify());
bundles.Add(lessBundle);

Механизм объединения (bundle) упакует все файлы .less, обнаруженные в заданной папке. Класс LessTransform отвечает за преобразование LESS в CSS. Этот класс использует dotless API для разбора LESS-скриптов. Код для LessTransform довольно прост:

public class LessTransform : IBundleTransform
{
  public void Process(BundleContext context, BundleResponse response)
  {
    response.Content = dotless.Core.Less.Parse(response.Content);
    response.ContentType = "text/css";
  }
}

Более интеллектуальные средства

LESS — не единственный препроцессор CSS. Например, еще одна популярная инфраструктура — Syntactically Awesome Stylesheets (Sass) (sass-lang.com). Суть в том, что независимо от конкретного средства препроцессор CSS определенно является тем инструментом, о котором вам следует задуматься при интенсивном веб-программировании. Кто бы вы ни были — дизайнер графики или разработчик, более интеллектуальные средства для управления и организации CSS-кода — это почти необходимость. Они еще эффективнее, когда интегрируются в веб-платформу. Наконец, заметьте, что Visual Studio 2012 и Visual Studio 2013 обеспечивают превосходную поддержку LESS (и релевантных технологий) через расширение Web Essentials, которое можно скачать с сайта vswebessentials.com. Кроме того, в Visual Studio 2012 Update 2 и в Visual Studio 2013 появился редактор LESS.


Дино Эспозито (Dino Esposito) — автор «Architecting Mobile Solutions for the Enterprise» (Microsoft Press, 2012) и готовящейся к выпуску книги «Programming ASP.NET MVC 5» (Microsoft Press). Является идеологом JetBrains в области технологий для платформ .NET и Android, часто выступает на различных отраслевых мероприятиях по всему миру. Делится своим видением программного обеспечения на software2cents.wordpress.com. Следите за его заметками в twitter.com/despos.

Выражаю благодарность за рецензирование статьи эксперту Microsoft Мэдсу Кристенсену (Mads Kristensen).