Заметки в категории «Вёрстка»

Ещё один вариант загрузки набора SVG-изображений

Когда мы в компании экспериментировали с inline SVG пиктограммами, то обнаружили разные браузеры реагировали различным образом на то, как мы ссылались на SVG с набором иконок. Нам хотелось получить универсальный и стабильный метод, который бы работал одинаково предсказуемо во всех браузерах, поддерживающих SVG.

Цепочка проб и ошибок привела нас к такой схеме:

  1. Преобразуем SVG-файл в строку текста и присваиваем её какой-либо переменной.
  2. Объединяем JavaScript файл, который мы получили на предыдущем этапе, с другими JS-файлами, которые подключаются в <head>.
  3. В до первого использования SVG добавляем пустой элемент — плейсхолдер и вставляем в него содержимое SVG-строки.

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

<svg xmlns="http://www.w3.org/2000/svg" class="icon_circle">
  <use xlink:href="#circle"></use>
</svg>

Опишу все проделанные шаги подробнее.

Конвертирование SVG в JS

Этой задачей занимается Grunt.

grunt.registerTask('elements', 'Transform a SVG sprites to a JS file',
  function () {
    var LINE_LENGTH = 100, svg = [], i, l, content;

    content = grunt.file.read('assets/images/elements.svg');
    content = content.replace(/'/g, "\\'");
    content = content.replace(/>\s+</g, "><").trim();
    l = Math.ceil(content.length / LINE_LENGTH);

    for (i = 0; i < l; i++) {
      svg.push("'" + content.substr(i * LINE_LENGTH, LINE_LENGTH) + "'");
    }

    grunt.file.write('assets/_/js/elements.js',
      'var SVG_SPRITE = ' + svg.join('+\n') + ';');
  }
);

Из файла assets/images/elements.svg получается файл assets/_/js/elements.js.

Конкатенация

Файл elements.js должен быть загружен в <head> Наверняка у вас есть ещё несколько скриптов, которые тоже требуется загружать в начале страницы. Их можно объединить между собой чтобы уменьшить количество HTTP-запросов к серверу.

Добавление на страницу

Важно, чтобы спрайты появились на странице до их первого использования. Добавим на страницу пустой элемент.

<div id="elements-placeholder"
  style="border: 0; clip: rect(0 0 0 0); overflow: hidden;
    margin: -1px; padding: 0; position: absolute;
    width: 1px; height: 1px;"></div>

Плейсхолдер нужен для того, чтобы безопасно модифицировать DOM-дерево в процессе загрузки страницы. У него должен быть уникальный id. Я ещё добавил некоторые inline-стили, чтобы защитить страницу от эффекта «упячки», если контуры случайно окажутся не помещены в <defs>.

Заполняем плейсхолдер.

<script>document.getElementById("elements-placeholder").innerHTML = SVG_SPRITE;</script>

Замечания

  1. При формировании JS-файла для простоты полагается, что будет создана глобальная переменная SVG_SPRITE. Чтобы не загрязнять глобальную область, я рекомендую сохранить содержимое SVG в пространство имён приложения.
  2. Имена файлов жёстко прописаны в Grunt-задаче. Есть смысл задачу сделать конфигурируемой, если у вас в проекте несколько наборов SVG-пиктограмм.

Демонстрация

У нас в компании есть проект-шаблон, в котором можно детально посмотреть эту технику подключения SVG.

Комментарии к заметке: 12

Миксины для модификаторов блоков

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

Мне кажется, что это может быть подходящим решением в данной ситуации.

Примеры

// namespace
@block: ~".cool-widget";

// block
@{block} {
  // primary styles
  position: relative;
  &__title {
    font: bold 20px/1 sans-serif;
  }
  &__body {
    font: normal 14px/1.4 sans-serif;
    color: white;
  }
}

Добавим «тему», изменяющую оформление некоторых элементов.

@{block} {
  // red modifier
  &_red {
    @{block} {
      .cool-widget_red();
    }
  }
}

// red theme
.cool-widget_red() {
  &__title {
    color: red;
  }
  &__body {
    background: red;
  }
}

К одному модификатору можно подмешать несколько миксинов.

@{block} {
  // blue modifier
  &_blue {
    @{block} {
      .cool-widget_big-title();
      .cool-widget_blue();
    }
  }
}

А так же можно создать стили для «глобального» модификатора.

@{block} {
  // global modifiers
  .ie6 & {
    .cool-widget_ie();
  }
  .no-js & {
    .cool-widget_no-js();
  }
}

Исходный LESS-файл и результат — https://gist.github.com/mistakster/619b94f50ad7b6c861ba

Комментарии к заметке: 7

Декомпозиция блоков в стиле БЭМ

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

/* Плохой пример! Не повторяйте такого */
.block {
    &__element {
    }
    &__child {
        color: black;
    }
    &__element_active &__child {
        color: red;
    }
}

Если приглядеться внимательно к получившейся конструкции, то можно найти аналогию с модификатором блока. Так оно и есть. Опишем элемент как новый блок с именем block-element и всё встанет на свои места.

/* Хороший пример. Декомпозиция упростила
   взаимоотношения между элементами */
.block {
    &__element {
    }
}
.block-element {
    &__child {
        color: black;
    }
    &_active &__child {
        color: red;
    }
}

На одних и тех же «физических» DOM-элементах мы разместили два «логических» блока.

<div class="block">
    <div class="block__element block-element">
        <span class="block-element__child">Some text</span>
    </div>
</div>

Миксин для генерации Блока

<article class="block">
    <h1 class="block__title">
        <span class="block__title-wrap">My cool title</span>
    </h1>
    …
</article>

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

.cool-title(@color: yellow) {
    font-size: normal 24px/1 sans-serif;
    &-wrap {
        background: @color;
    }
}

.block {
    &__title {
        .cool-title();
    }
}

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

Лучше представить такой миксин в виде отдельного блока и позволить ему генерировать имена элементов и модификаторов явным образом.

.cool-title(@color: yellow) {
    font-size: normal 24px/1 sans-serif;
    &__wrap {
        background: @color;
    }
}

.block-title {
    .cool-title();
}

Опять у нас на одних и тех же DOM-элементах прекрасно уживаются два блока.

<article class="block">
    <h1 class="block__title block-title">
        <span class="block-title__wrap">My cool title</span>
    </h1>
    …
</article>
Комментарии к заметке: 2

Генерация названий селекторов в стиле БЭМ с помощью препроцессоров

Методология БЭМ никогда не подразумевала того, что длинные названия классов в селекторах типа .block__element_modifier будут записываться вручную. В Яндексе для этого сразу используются специальные утилиты, которые генерируют стили и разметку блока. Если вы используете препроцессоры для генерации CSS, то вам тоже не нужно каждый раз повторять названия блока или элементов. Сейчас я расскажу, как это делается.

Все препроцессоры позволят вкладывать селекторы друг в друга. Таким образом, строятся каскады.

.header {
    .title {
        font: bold 24px/1 sans-serif;
    }
}

После трансформации получаем CSS:

.header .title {
    font: bold 24px/1 sans-serif;
}

Вместо «родительской части» селектора можно использовать символ &. Удобно, когда нужно применить свойства и выбранному элементу, и его потомкам или псевдо-классам.

.header {
    a {
        &, &:hover, &:focus {
            color: white;
            text-decoration: none;
        }
    }
}

CSS:

.header a,
.header a:hover,
.header a:focus {
    color: white;
    text-decoration: none;
}

LESS (а с недавнего времени и SASS) подставляют в новый селектор вместо & часть родительского селектора без каких-либо изменений. Так же нет ограничений на последующие или предыдущие символы в новом селекторе. Поэтому вместо каскада можно генерировать простые селекторы.

.header {
    &__link {
        &, &:hover, &:focus {
            color: white;
            text-decoration: none;
        }
    }
}

CSS:

.header__link,
.header__link:hover,
.header__link:focus {
    color: white;
    text-decoration: none;
}

Аналогично формируем простые селекторы для модификаторов элементов.

.header {
    background: white;
    &__title {
        font: bold 24px/1 sans-serif;
        &_featured {
            font-size: 30px;
        }
    }
}

CSS:

.header {
    background: white;
}
.header__title {
    font: bold 24px/1 sans-serif;
}
.header__title_featured {
    font-size: 30px;
}

Вложенные селекторы превратились в простые.

Важно знать

А теперь несколько простых правил, чтобы обладая этим знаниями вы «не прострелили себе ногу».

Название блока используется только один раз

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

.block {
    // стили блока
}

Элементы идут на втором уровне вложенности

.block {
    // стили блока
    &__element {
        // стили элемента
    }
    &__title {
    }
}

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

.block {
    &__element {
        &-wrapper {
            // Пример плохого элемента.
            // Его будет трудно отыскать.
        }
    }
}

Никогда не делайте подобного. Такой селектор очень трудно будет найти в коде. В примере название элемента element-wrapper разорвано на две части. Пишите название элементов полностью даже если они частично повторяют уже существующие.

Псевдо-классы, псевдо-элементы и модификаторы элементов допускается писать на третьем уровне вложенности

.block {
    &__element {
        &_modifier {
            // стили модификатора элемента
        }
        &_modifier_value {
            // модификатор со значением не разбиваем на части
        }
        &:hover {
            // псевдо-класс — это тоже модификатор
        }
    }
}

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

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

.block {
    &__element_active {
        // модификатор элемента на втором уровне
    }
    &__element_modifier_good {
        // пример модификатора со значением
    }
}

На практике мне больше нравится именно такая запись — модификатор на втором уровне. Также считаю, что недопустимо разбивать название модификатора на части, а так же не стоит отделять значение модификатора от его названия. Излишняя структурированность может ухудшить читаемость. Легко потерять текущий контекст. Помните об этом.

Модификаторы блока могут участвовать в каскаде и располагаются на втором уровне

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

.block {
    background: white;
    &__title {
        color: black;
        font: bold 24px/1 sans-serif;
    }
    &_featured {
        background: black;
    }
    &_featured &__title {
        color: white;
        font-size: 30px;
    }
}

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

.block {
    background: white;
}
.block__title {
    color: black;
    font: bold 24px/1 sans-serif;
}
.block_featured {
    background: black;
}
.block_featured .block__title {
    color: white;
    font-size: 30px;
}

Заключение

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

Эта статья была любезно переведена на английский язык Варей Степановой и опубликована во «Frontend Babel» — Generating BEM selectors with CSS preprocessors.

Комментарии к заметке: 15

Доступность SVG-пиктограмм

Когда вы используете inline-SVG без текстового пояснения (например, как пиктограмму на кнопке), то неплохо было бы снабдить это изображение заголовком. В SVG-документе есть описательные теги: <title>, <desc>, <metadata>. Для наших целей хорошо подходит тег <title>.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Document title</title>
</head>
<body>
  <p>This is a test page</p>

  <button>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="32" height="32">
      <title>Next page</title>
      <polygon points="15,7 11,11 20,20 11,29 15,33 28,20"/>
    </svg>
  </button>

  <p>Actually, there is no next page here.</p>

</body>
</html>

Содержимое <title> не отображается, но может использоваться вспомогательными технологиями.

Содержимое <title> не отображается, но может использоваться вспомогательными технологиями

Более того, каждый графический элемент может иметь собственный заголовок.

Таким образом, нарушая все запреты о наличии только одного тега <title> на странице, мы получили абсолютно валидный документ с несколькими тегами.

На странице с валидной разметкой могут присутстовать несколько тегов <title>

Комментарии к заметке: 4