Плитка Sitepoint

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

Я решил, что такая плитка является интересным компонентом для построения, как в части HTML, так и в части CSS. В этой статье шаг за шагом мы построим этот компонент, попытавшись добиться максимума: доступности, поддерживаемости, темизации и адаптированности к SEO.

Начинаем с контента

Компонент всегда стоит создавать в следующем порядке: сначала содержимое, затем разметка, потом стили и, наконец, JavaScript (если он нужен). Мы не будем отклоняться от этого правила и начнем с контента.

HTML & CSS

8 comments

A Tale of CSS and Sass Precision

by Hugo Giraudel

May 12, 2016

Теперь мы можем начать оборачивать наш контент тегами HTML. В роли главного контейнера выступит элемент <article>, мне кажется, что это его корректное использование. Внутри основного контейнера будут размещены контейнер для верхней части, контейнер для названия статьи (хотя он и не обязателен) и футер с метаданными.

<article class="c-article-tile">
  <div class="c-article-tile__header">
    HTML & CSS

    8 comments
  </div>
  <div class="c-article-tile__body">
    A Tale of CSS and Sass Precision
  </div>
  <footer class="c-article-tile__footer">
    by Hugo Giraudel

    May 12, 2016
  </footer>
</article>

Примечание: мы используем именование классов на основе BEM с пространствами имен — вы можете свободно использовать то, что предпочитаете.

Далее нам нужны субконтейнеры для наших элементов. Один для категории, один для счетчика комментариев, тег заголовка для названия, контейнер для автора и еще один для даты. Также добавим ссылки.

<article class="c-article-tile">
  <!-- Tile header -->
  <div class="c-article-tile__header">
    <a class="c-article-tile__category"
       href="https://www.sitepoint.com/html-css/">
      HTML & CSS
    </a>
    <a class="c-article-tile__comment-count"
       href="https://www.sitepoint.com/a-tale-of-css-and-sass-precision/#comments">
      8 comments
    </a>
  </div>

  <!-- Tile body -->
  <div class="c-article-tile__body">
    <h2 class="c-article-tile__title">
      <a href="https://www.sitepoint.com/a-tale-of-css-and-sass-precision/">
        A Tale of CSS and Sass Precision
      </a>
    </h2>
  </div>

  <!-- Tile footer -->
  <footer class="c-article-tile__footer">
    <span class="c-article-tile__author">
      by
      <a href="https://www.sitepoint.com/author/hgiraudel/">
        Hugo Giraudel
      </a>
    </span>
    <time class="c-article-tile__date"
          datetime="2016-05-12T12:00">
      May 12, 2016
    </time>
  </footer>
</article>

Выглядит неплохо! Несколько важных моментов:

  • мы не используем элемент <header> для верхней части, так как в этом теге обычно содержится заголовок, а это не наш случай;
  • мы используем элементы <span> вместо <p>, так как у нас нет контента, соответствующего параграфу;
  • мы используем элемент <time> и его атрибут datetime вместо <span> для описания даты.

Давайте заменим слово comments иконкой, созданной с учетом требований доступности. Я не буду углубляться в объяснения, если вы хотите узнать больше о методике, читайте статью A Working SVG Workflow for Accessible Icons.

<svg style="display: none">
  <symbol id="icon-bubble" viewBox="0 0 32 32">
    <path class="path1" d="M16 2c8.837 0 16 5.82 16 13s-7.163 13-16 13c-0.849 0-1.682-0.054-2.495-0.158-3.437 3.437-7.539 4.053-11.505 4.144v-0.841c2.142-1.049 4-2.961 4-5.145 0-0.305-0.024-0.604-0.068-0.897-3.619-2.383-5.932-6.024-5.932-10.103 0-7.18 7.163-13 16-13z"></path>
  </symbol>
</svg>

<!-- … -->

<a class="c-article-tile__comment-count"
   href="https://www.sitepoint.com/a-tale-of-css-and-sass-precision/#comments">
  8
  <svg class="icon icon-bubble"
       aria-label="comments">
    <use xlink:href="#icon-bubble"></use>
  </svg>
</a>

Обратите внимание, как используется атрибут aria-label — благодаря ему иконка становится доступной для пользователей вспомогательных технологий.

Наконец, мы можем добавить микроразметку в наш код, чтобы облегчить обход и индексацию содержимого поисковыми роботами. Мы будем использовать описание статей с Schema.org:

<article class="c-article-tile"
         itemscope
         itemtype="http://schema.org/Article">
  <!-- Tile header -->
  <div class="c-article-tile__header">
    <a class="c-article-tile__category"
       href="https://www.sitepoint.com/html-css/"
       itemprop="keywords">
      HTML & CSS
    </a>
    <a class="c-c-article-tile__comment-count"
       href="https://www.sitepoint.com/a-tale-of-css-and-sass-precision/#comments"
       itemprop="commentCount">
      8
      <svg class="icon icon-bubble"
           aria-label="comments">
        <use xlink:href="#icon-bubble"></use>
      </svg>
    </a>
  </div>

  <!-- Tile body -->
  <div class="c-article-tile__body">
    <h2 class="c-article-tile__title"
        itemprop="headline">
      <a href="https://www.sitepoint.com/a-tale-of-css-and-sass-precision/">
        A Tale of CSS and Sass Precision
      </a>
    </h2>
  </div>

  <!-- Tile footer -->
  <footer class="c-article-tile__footer">
    <span class="c-article-tile__author">
      by
      <a href="https://www.sitepoint.com/author/hgiraudel/"
         itemprop="author">
        Hugo Giraudel
      </a>
    </span>
    <time class="c-article-tile__date"
          datetime="2016-05-12T12:00"
          itemprop="datePublished">
      May 12, 2016
    </time>
  </footer>
</article>

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

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

Вот как может выглядеть этот компонент-список:

<ul class="c-tile-list">
  <li class="c-tile-list__item">
    <article class="c-article-tile">…</article>
  </li>

  <li class="c-tile-list__item">
    <article class="c-article-tile">…</article>
  </li>

  <li class="c-tile-list__item">
    <article class="c-article-tile">…</article>
  </li>
</ul>

На этой стадии мы полностью разобрались с разметкой. Она чистая, доступная и адаптированная к SEO — больше ничего и не надо. Займемся стилями!

Добавляем стили

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

Компонент контейнера для списка

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

/**
 * 1. Reset default list styles
 * 2. Flexbox used for a grid-like layout for the tiles.
 */
.c-tile-list {
  list-style: none; /* 1 */
  margin: 0; /* 1 */
  padding: 0; /* 1 */
  display: flex; /* 2 */
  flex-wrap: wrap; /* 2 */
}

И теперь элементы списка:

/**
 * 1. Flexbox used for equal-height tiles on a same line.
 * 2. Make sure a time never looks distorded.
 * 3. Spacing between tiles.
 */
.c-tile-list__item {
  display: flex; /* 1 */
  flex-direction: column; /* 1 */
  flex: 0 0 300px; /* 2 */
  margin: 10px; /* 3 */
}

Компонент карточки статьи

Теперь перейдем к основному вопросу: компоненту плитки с карточкой статьи. Здесь нужно добавить стили ко многим элементам, начиная с самой плитки.

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

/**
 * 1. Make it possible to bottom align the footer in a tile that has a minimum
 *    height.
 * 2. Make sure the tile spread across the full height of the parent if inside
 *    a flex container.
 */
.c-article-tile {
  display: flex; /* 1 */
  flex-direction: column; /* 1 */
  flex: 1 0 auto; /* 2 */
  border: 1px solid rgba(0, 0, 0, 0.1);
  background-color: rgb(255, 255, 255);
}

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

.c-article-tile__header,
.c-article-tile__body,
.c-article-tile__footer {
  display: flex;
  padding-left: 20px;
  padding-right: 20px;
}

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

.c-article-tile__header,
.c-article-tile__footer {
  font-size: 80%;
}

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

/**
 * 1. Rely on the `color` property for the border color by not setting any color
 *    value, making it super convenient for theming.
 */
.c-article-tile__header {
  padding-top: 15px;
  padding-bottom: 10px;
  border-bottom: 2px solid; /* 1 */
}

По умолчанию направлением флекс-контейнера является нужное нам row, поэтому мы не будем его явно задавать в стилях для субконтейнеров. Для выравнивания счетчика комментариев по правой стороне хедера есть два решения. Первое — это задать хедеру justify-content: space-between для размещения его элементов. Другое, которое мы будем использовать, это задать счетчику комментариев margin-left: auto, полагаясь на тайну флексбокса.

/**
 * 1. Right align the comment count container in the header.
 */
.c-article-tile__comment-count {
  margin-left: auto; /* 1 */
}

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

.c-article-tile__body {
  padding-top: 20px;
  padding-bottom: 20px;
}

.c-article-tile__title {
  margin: 0;
  color: #333;
  font-size: 150%;
}

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

Также футер всегда должен помещаться в одну строчку. К счастью, мы можем принудить его к этому с помощью hite-space: nowrap. Однако, мы не контролируем длину имени автора, поэтому нам надо обеспечить, что раскладка не поломается, если имя автора окажется слишком длинным. Я прекрасно знаю, что “обрезка символов это пло…”, но в данном случае это меньшее из двух зол.

/**
 * 1. Bottom align the footer in the tile.
 * 2. Prevent any content from the footer from wrapping, effectively forcing it
 *    on a single line at all time.
 */
.c-article-tile__footer {
  padding-top: 10px;
  padding-bottom: 15px;
  margin-top: auto; /* 1 */
  white-space: nowrap; /* 2 */
  color: #949494;
}

/**
 * 1. Prevent the author and the date from overlapping in case they both don’t
 *    fit on the line; add an ellipsis to the author name.
 * 2. Visually no effect when both the author and the date fit; however make
 *    sure they are slightly spaced from each other if they meet on the line.
 */
.c-article-tile__author {
  text-overflow: ellipsis; /* 1 */
  overflow: hidden; /* 1 */
  margin-right: 5px; /* 2 */
}

/**
 * 1. Right align the date container in the footer.
 */
.c-article-tile__date {
  margin-left: auto; /* 1 */
}

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

Темизация

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

Когда мы переходим к темизации, я предпочитаю опираться на независимые от компонентов классы с префиксом “t-“. Начнем с применения класса темы к нашей карточке:

<article class="c-article-tile t-sass"
         itemscope
         itemtype="http://schema.org/Article">
  …
</article>

Теперь мы можем применять стили к карточке на основе этого класса. Мы зададим цвет для текста карточки и ссылок в ней. Это повлияет на цвет границы хедера, но не на цвет футера, цвет которого (#949494) уже задан.

.c-article-tile.t-sass,
.c-article-tile.t-sass a {
  color: #c69;
}

Все замечательно, кроме заголовка статьи, который остается розовым, хотя должен быть черным, розовея только в активном состоянии и при наведении курсора. Решением будет принуждение ссылки к унаследованию цвета родителя (.c-article-tile__title , #333), когда последний не находится в активном состоянии или в состоянии наведения. Для этого мы используем псевдокласс :not():

/**
 * 1. Make the title link inherit the color only when not active / hovered,
 *    effectively making it themed when active / hovered.
 */
.c-article-tile__title:not(:hover):not(:active) > a {
  color: inherit; /* 1 */
}

Подводим итоги

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

See the Pen SitePoint Tile Concept Example by SitePoint (@SitePoint) on CodePen.