В этой статье мы поговорим о концепции иконификации – размещения контента на странице и применении CSS для его трансформирования в упрощенное превью в виде значка. Давайте рассмотрим на примере. В этом демо показана иконификация на примере календаря. Чтобы развернуть месяц, нужно нажать на его значок.
Визуальная абстракция
В примере с календарем в качестве значков используются простые цветные квадраты, показывающие, что месяц содержит интересные нам даты. На каждый значок можно нажать и вызвать полный вид с большим количеством деталей, таких как даты и названия праздников. Контекст сохраняет плавный переход между двумя состояниями.
Такой вариант отлично подходит для представления компактного обзора контента, при этом постоянно сохраняя доступность деталей. Другими словами, он предоставляет визуальную абстракцию информации.
HTML
<article tabindex="0">
<div class="outline"></div>
<div class="dismiss"></div>
<div class="binding"></div>
<h1>January</h1>
<table>
<thead>
<tr>
<th>Sun</th>
<th>Mon</th>
<th>Tue</th>
<th>Wed</th>
<th>Thu</th>
<th>Fri</th>
<th>Sat</th>
</tr>
</thead>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="is-holiday">
<div class="day">1</div>
<div class="holiday">New Year's Day</div>
</td>
<td><div class="day">2</div></td>
<td><div class="day">3</div></td>
</tr>
<tr>
<td><div class="day">4</div></td>
<td><div class="day">5</div></td>
<td><div class="day">6</div></td>
<td><div class="day">7</div></td>
<td><div class="day">8</div></td>
<td><div class="day">9</div></td>
<td><div class="day">10</div></td>
</tr>
<tr>
<td><div class="day">11</div></td>
<td><div class="day">12</div></td>
<td><div class="day">13</div></td>
<td><div class="day">14</div></td>
<td><div class="day">15</div></td>
<td><div class="day">16</div></td>
<td><div class="day">17</div></td>
</tr>
<tr>
<td><div class="day">18</div></td>
<td class="is-holiday">
<div class="day">19</div>
<div class="holiday">MLK Day</div>
</td>
<td><div class="day">20</div></td>
<td><div class="day">21</div></td>
<td><div class="day">22</div></td>
<td><div class="day">23</div></td>
<td><div class="day">24</div></td>
</tr>
<tr>
<td><div class="day">25</div></td>
<td><div class="day">26</div></td>
<td><div class="day">27</div></td>
<td><div class="day">28</div></td>
<td><div class="day">29</div></td>
<td><div class="day">30</div></td>
<td><div class="day">31</div></td>
</tr>
</table>
</article>
Здесь никаких сюрпризов. Календари – это на самом деле просто дни, оформленные в формате таблицы, поэтому table здесь является очевидным семантическим вариантом. Каждый месяц завернут в article, подразумевая, что он является полноценной частью контента. Каждый article включает tabindex=«0», чтобы включить возможность табуляции, что очень важно при работе с клавиатуры (об этом поговорим чуть позже).
CSS
Здесь очень много CSS, поэтому я расскажу только об основном. Полный CSS доступен в источнике и включает комментарии по некоторым исправлениям для определенных браузеров.
Каждый article центрируется внутри li, который размещен внутри ul. Используемая мной техника центрирования в article позволяет накладываться на окружающий контент и оставаться по центру при разворачивании до полного размера. Соответствующие описания приведены ниже.
article {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(.25);
/* и так далее... */
}
Обратите внимание, что свойство transform также включает scale(.25). Это не для центрирования, а скорее для создания размера ? значка.
Восстановление article до полного размера выполняется за счет добавления класса active в родительский элемент, что приводит к изменению свойства transform в article.
transform: translate(-50%, -50%) scale(1);
Свойство scale(1) говорит само за себя, но все равно стоит отметить, что translate(-50%, -50%) необходимо заявить, иначе сползет в свое нецентрированное положение.
В то же время в родственные элементы добавляется класс inactive, таким образом, что еще один невозможно развернуть, пока активный article не будет свернут. Это выполняется за счет блокирования событий курсора.
.inactive {
pointer-events: none;
}
Наложение z-index требует некоторой точности. Активный должен появляться поверх соседнего контента. Это означает увеличение z-index при запуске анимации развертывания (простая часть) и запрет сбрасывания z-index, пока не завершится анимация сжатия (сложная часть). К счастью, z-index можно анимировать, поэтому для этого мы можем использовать transition-delay.
li {
position: relative;
z-index: 1;
transition: z-index;
transition-delay: .4s; /* задержка при переходе от полного размера к значку */
/* ... */
}
.active {
z-index: 2;
transition-delay: 0s; /* отсутствие задержки при переходе от значка к полному размеру */
}
Класс active также вызывает изменения свойства opacity в различных дочерних элементах, что дает нам отличный эффект усиления/затухания деталей.
Конечно, ни один из этих элементов невозможно анимировать без описания transition в article (для масштабирования) по различным дочерним элементам (для затухания).
Пиксельное совершенство
Достижение пиксельного совершенства с помощью трансформированных элементов – до, в процессе и после анимации – требует терпения. Придется побороться с большим количеством загвоздок, но, к счастью, существует несколько уловок и подходов, которые могут с этим помочь.
Все пиксельные размеры в CSS умножены на 4. И это не случайность. Контент масштабируется до ? размера при отображении в виде значка, поэтому умножение на 4 обеспечивает, что не возникнут дробные пиксельные размеры. Благодаря этому, пиксели идеально выравниваются, и получаются ровные края.
Еще может помочь определение ширины и высоты элементов. Это не только позволяет проще добиться ранее описанного выравнивания пикселей, но также может помочь избежать смещения после анимации. Браузеры постоянно перенастраивают текст по завершении анимации. Если элемент, содержащий такой текст, не имеет установленной ширины/высоты, тогда его размер изменится соответствующим образом, что приведет к цепной реакции смещения положения элементов, расположенных после него.
Производительность
Иконификация контента подразумевает наличие большого количества элементов в ограниченном пространстве. Имея такое количество анимированных элементов, важно не забывать о производительности, иначе вы получите низкую частоту кадров и кривые переходы.
Есть две вещи, которых следует избегать: перекраска и пересчет положения. Перекраска возникает ввиду свойств анимации, которые заставляют браузер изменять цвет элементов. Это включает color,background, box-shadow и прочее. Пересчет положения возникает в результате свойств анимации, которые заставляют браузер обновлять выкладку элементов. Это включает width, height, margin,top, left и прочее.
Перекраска и пересчет – это не конец света, но они могут снизить производительность. Вы можете избежать таких эффектов с помощью двух свойств, которые довольно эффективны при анимации, так как им помогает графический процессор: opacity и transform (когда они используются для положения, вращения или масштабирования).
Это объясняет некоторые решения, используемые в CSS. Есть места, в которых анимация элементов color или background-color будет более интуитивной, но использование подхода с opacity обеспечивает лучшую производительность.
JavaScript и доступность
JavaScript используется для управления поведением разворачивания и сжатия контента. Клики мышкой являются очевидным тригером, но доступность с клавиатуры также является важным моментом, поэтому JavaScript это также учитывает. Для удобства используется jQuery.
function activate(e) {
var $wrapper = $(e.currentTarget).parent();
$wrapper
.addClass('active')
.siblings().addClass('inactive');
}
function dismiss(e) {
var $wrapper = $(e.currentTarget).closest('li');
$wrapper
.removeClass('active')
.siblings().removeClass('inactive');
e.stopPropagation();
}
function checkKey(e) {
var $wrapper = $(e.currentTarget).parent();
var isActive = $wrapper.hasClass('active');
if (isActive && (e.keyCode === 13 || e.keyCode === 27)) {
// активно, нажать ввод для выхода
dismiss(e);
} else if (!isActive && e.keyCode === 13) {
// неактивно, нажать ввод
activate(e);
}
}
$('article').on({
'click': activate,
'blur': dismiss,
'keyup': checkKey
});
$('.dismiss').on('click', dismiss);
Нажатие на article вызывает свойство activate(), которое применяет класс active к соответствующему элементу и класс inactive для остальных элементов.
Нажатие на значок выхода или нажатие на табуляцию/клик за пределами article вызывает свойство dismiss(), которое просто удаляет классы active и inactive.
Свойство checkKey() обеспечивает поддержку нажатия определенных клавиш. Нажатие на enter переключает текущий article. Нажатие на escape отключает текущий article, если он развернут.
Это описывает мой первый пример. Можете посмотреть готовое демо, если вы еще его не видели.
Второе демо
Следующей будет концепция иконификации карточек профиля. Демо.
Здесь используется большинство техник, описанных в первом демо. Но тут есть новый интересный момент – превращение отдельных слов в тексте профиля в абстрактные блоки. Первый этап осуществления этого – заворачивание каждого слова в собственный тег span, в результате чего у нас появляется то, над чем можно работать. Делать это вручную было бы действительно сложно, поэтому пусть за нас это сделает JavaScript.
$('.about p').each(function() {
var $this = $(this);
var words = $this.text().trim().split(' ');
var spans = '<span>' + words.join('</span> <span>') + '</span>';
$this.html(spans);
});
Этот сниппет просто собирает текст в теги p, разбивает его везде, где находит пробелы, потом заворачивает каждый кусок в теги span. Теперь мы можем использовать эти теги span, чтобы подключить CSS.
.about span {
position: relative;
overflow: hidden;
}
.about span::after {
content: '';
opacity: 1;
display: block;
position: absolute;
top: -2px;
bottom: -2px;
left: 0;
right: 0;
background-image: linear-gradient(to bottom, #fff 25%, #c7c5bf 25%, #c7c5bf 75%, #fff 75%);
transition: opacity .4s;
}
Сам по себе тег span в основном опускается. Вместо ::after используется псевдо-элемент, покрывающий каждое слово блоком цвета. Свойствам top и bottom задаются слегка негативные значения, чтобы растянуть блок, чтобы он покрывал слова, которые возвышаются или свисают (например, “g”).
К сожалению, это делает блоки толще, что визуально выглядит не очень привлекательно. Решение – использовать аккуратный линейный градиент, который перекрывает верх и низ белым цветом (чтобы соответствовать фону). Блоки становятся тоньше, текст все еще покрывается, все счастливы.
При этом будьте осторожны, эту технику с элементами span следует использовать под присмотром. Очень легко увлечься и разнести дерево DOM за счет слишком большого количества элементов span, каждый из которых будет анимироваться, что может привести к проблемам с производительностью. Лучше всего для этого использовать небольшие кусочки текста, как в примере.
Третье демо
В последнем демо показана иконификация в контексте аналитической панели.
В этом демо происходит много чего, но на самом деле в нем нет никаких новых техник иконификации контента, поэтому на этом я остановлюсь. Если вам интересны подробности, все есть в источнике.
> Принимайте оплату от компаний через Интернет. Без сайта, ИП и ООО.
> Приём платежей от компаний для Вашего сайта. С документооборотом и обменом оригиналами.
> Автоматизация продаж и обслуживание сделок с юр.лицами. Без посредника в расчетах.
Комментарии (7)
maovrn
28.05.2015 19:24Не пойму в чем проблема, во втором демо в последнем хроме невозможно выделить слово в середине текста.
PSDCoder
28.05.2015 21:04+2Потому что поверх, с помощью использования псевдоэлемента ::after, отрисовывается прозрачный блок, что и блокирует выделение текста.
tegArt
08.06.2015 15:46.about span::after { pointer-events: none;}
Должно решить проблему.
ps. проверил в хроме — текст уже по-человечески выделяется.PSDCoder
08.06.2015 16:03Конечно, решит) Но все-равно не будет работать в IE < 11, что ограничивает круг использования.
Да и вообще, имхо, второе демо интересно лишь в роли демо как раз, в проектах бы я такое не применял… Для каждого слова по dom-элементу как-то черезчур)tegArt
08.06.2015 16:13Согласен, но и проекты бывают разные, и целевая аудитория у всех разная. Для какой-нибудь промо странички вполне себе фишка.
Просто тут был озвучен вопрос, а крохотного решения не было. Вот и подумалось, что надо написать — вдруг кому пригодится.
TroL929
01.06.2015 06:47Как раз сегодня собирался начать писать задачник для CRM и эта статья случайно попалась. Совпадение? Не, не думаю…
goooseman
Во втором демо текста гениальны)