Со­ве­ты по слож­ным CSS-ил­лю­стра­ци­ям

Перевод «Advice for Complex CSS Illustrations»

Джи Томпкинс

Перевод Никита Дубко

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

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

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

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

И хотя не существует универсального подхода для CSS-иллюстраций, я могу предложить несколько техник, которые помогут вам в вашем путешествии.

Время и практика#

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

Два фантастических CSS-художника — Бен Эванс и Диана Смит. Оба недавно рассказывали о затратах времени на CSS-иллюстрации.

Изображение женщины, пристально смотрящей вверх и прижимающей руки к груди, напоминающее классическую картину.
Создание PureCSS Gaze заняло у Дианы два долгих уикенда. Она рассказывает о некоторых своих техниках здесь. «Если у вас есть время, терпение и запал, то это, безусловно, возможно», — говорит она.

Я запостил мем про чашку, и в ответе Бена всё было прекрасно:

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

Это требует времени!

Фотореалистичное изображение белой чашки на белом столе.
CSS-иллюстрация.

Трассировка вполне приемлема#

Зачастую мы уже имеем представление о том, что хотим нарисовать. В конце концов, эта статья не о дизайне. Речь идет о том, чтобы взять изображение и отрендерить его с помощью DOM и CSS. Я уверен, что эта техника существует с незапамятных времен. Но это то, чем я делюсь последние несколько месяцев.

  • Найдите или создайте изображение, которое вы хотите нарисовать.
  • Вставьте его в ваш HTML при помощи <img>.
  • Расположите его так, чтобы оно находилось прямо под вашей иллюстрацией.
  • Уменьшите прозрачность изображения, чтобы его было видно, но не слишком.
  • Трассируйте его с помощью DOM.

К моему удивлению, про эту технику не все знают. Но она бесценна для создания точных CSS-иллюстраций.

Следите за трюком в действии:

Вот таймлапс создания логотипа @eggheadio на CSS 😎 Поигрался с тенями и применил clip-path поверх 🛠️ @jh3yy

И попробуйте его здесь:

Обращайте внимание на отзывчивость#

Если вы начнёте использовать только две техники из этой статьи, пусть это будут трассировка из раздела выше и эта.

Есть немало фантастических примеров CSS-иллюстраций. Но одна из неприятных особенностей некоторых из них в том, что они не стилизованы — или даже не видны — на маленьких экранах. Мы живём в эпоху, когда очень важны первые впечатления от технологий. Рассмотрим на примере клавиатуры, нарисованной на CSS. Кто-то находит вашу работу, открывает её на своём смартфоне, а его встречает только половина иллюстрации или её небольшая часть. Они скорее всего упустили самые крутые части демо!

Вот мой приём: использовать единицы вьюпорта для иллюстраций и создавать свою собственную масштабированную единицу измерения.

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

Рассмотрим CSS-логотип egghead.io, который я создал ранее. Я нашел изображение, которое хотел использовать, и добавил его в DOM тегом <img>.

<img src="egghead.png" alt="">
img {
    height: 50vmin;
    left: 50%;
    opacity: 0.25;
    position: fixed;
    top: 50%;
    transform: translate(-50%, -50%);
}

Высота 50vmin — желаемый размер CSS-иллюстрации. Уменьшение прозрачности позволяет нам «трассировать» иллюстрацию по мере прогресса.

Теперь создадим нашу масштабированную единицу измерения.

/**
* размеры изображения 742 x 769
* ширина — 742
* высота — 769
* желаемый размер — 50vmin
*/
:root {
    --size: 50;
    --unit: calc((var(--size) / 769) * 1vmin);
}

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

Получим что-то вроде такого:

--unit: 0.06501950585vmin;

Выглядит так себе, но поверьте, это нормально. Мы можем использовать её для задания размеров контейнера для иллюстации, используя calc().

.egg {
    height: calc(769 * var(--unit));
    position: relative;
    width: calc(742 * var(--unit));
    z-index: 2;
}

Если мы используем проценты или наше новое кастомное свойство -⁠-⁠unit для стилизации элементов внутри контейнера нашей CSS-иллюстрации, мы получим отзывчивую CSS-иллюстрацию… И всё это требует всего несколько строчек математики с использованием CSS-переменных!

Поизменяйте размеры этого демо, чтобы увидеть, что все пропорции сохраняются, ограничиваясь по размеру до 50vmin.

Семь раз отмерь, один отрежь#

Следующий совет: замеряйте. Чёрт, вы даже можете взять рулетку, если работаете с физическим объектом!

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

:root {
    --light-switch: 15;
    --light-switch-border: 10;
    --light-switch-top: 15;
    --light-switch-bottom: 25;
    --tv-bezel: 15;
    --tv-unit-bezel: 4;
    --desired-height: 25vmin;
    --one-cm: calc(var(--desired-height) / var(--tv-height));
    --tv-width: 158.1;
    --tv-height: 89.4;
    --unit-height: 42;
    --unit-width: 180;
    --unit-top: 78.7;
    --tv-bottom: 114.3;
    --scaled-tv-width: calc(var(--tv-width) * var(--one-cm));
    --scaled-tv-height: calc(var(--tv-height) * var(--one-cm));
    --scaled-unit-width: calc(var(--unit-width) * var(--one-cm));
    --scaled-unit-height: calc(var(--unit-height) * var(--one-cm));
}

Как только мы вычислили переменную, можем использовать её везде. Я знаю, что мой телевизор 158,1 см в ширину и 89,4 см в высоту — подсмотрел в инструкции. Но в моей CSS-иллюстрации он всегда будет ограничен 25vmin.

Используйте абсолютное позиционирование для всего#

Этот совет позволит сэкономить на нажатиях клавиш. Чаще всего вы будете пытаться позиционировать элементы абсолютно. Помогите себе и положите это правило куда-нибудь.

/* Имена классов могут отличаться */
.css-illustration *,
.css-illustration *:after,
.css-illustration *:before,
.css-illustration:after,
.css-illustration:before {
    box-sizing: border-box;
    position: absolute;
}

Ваша клавиатура скажет вам спасибо!

Или поиграйте в этой песочнице:

Придерживайтесь подхода#

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

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

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

Сохраняйте жёсткую структуру ваших стилей#

Это приводит нас к структуре. Старайтесь избегать плоской DOM-структуры для вашей иллюстрации. Сохранение вещей атомарными позволяет проще двигать части вашей иллюстрации. А ещё так гораздо проще показывать или прятать части иллюстрации или даже потом анимировать их. Рассмотрим демо CSS Snorlax. Руки, ноги, голова и прочие части являются отдельными элементами. Это сделало анимирование руки гораздо проще, чем если бы я пытался держать всё вместе, так как я смог просто применить анимацию к классу .snorlax__arm-left.

Вот ускоренная запись того, как я делал это демо:

Попытался собрать таймлапс создания CSS Snorlax, которого мы делали вчера ночью. Забавно пересматривать! @jh3yy

Работа с неудобными фигурами#

Есть довольно неплохая статья на CSS-Tricks про создание фигур с помощью CSS. Но как насчёт более «неуклюжих» фигур, таких как длинная кривая или даже обводка? В таких случаях нужно мыслить нестандартно. Такие свойства, как overflow, border-radius и clip-path — отличные помощники.

Посмотрим на демо CSS Jigglypuff. Нажмите на чекбокс.

Вот ключ к созданию искривлённых фигур! У нас есть элемент, который гораздо больше тела с примененным border-radius. Тогда мы задаем overflow: hidden телу, чтобы обрезать этот лишний кусок.

Как мы можем сделать обводку? Это уже немного сложнее. Но мне нравится следующий приём: используйте прозрачный элемент с толстой рамкой. Затем примените border-radius и обрежьте лишнее, если нужно.

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

clip-path — ваш друг#

В последнем демо вы могли заметить парочку интересных CSS-свойств, например, clip-path. Вам почти наверняка понадобится clip-path, если вы хотите создавать сложные CSS-фигуры. Его особенно удобно использовать для обрезания краёв элементов, когда применение overflow на родителе не помогает.

Вот небольшое демо, которое я собрал некоторое время назад, чтобы показать различные возможности clip-path.

Есть ещё такое демо, которое берёт идеи из статьи Shapes of CSS и воссоздаёт фигуры при помощи clip-path.

border-radius — ещё один друг#

Вам точно понадобится border-radius для создания кривых. Один необычный приём заключается в использовании синтаксиса с двумя значениями. Это позволяет задавать горизонтальный и вертикальный радиус для каждого угла.

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

Техники создания теней#

У вас уже есть все формы, всё хорошо продумано, правильные цвета везде, где нужно… Но что-то всё ещё выглядит не так. Скорее всего, это отсутствие теней.

Тени добавляют глубину и создают реалистичные ощущения. Посмотрим на это воссоздание иллюстрации Галь Шир. Галь великолепно использует тени и градиенты для создания красивой иллюстрации. Я подумал, что было бы интересно воссоздать её и добавить переключатель, который включает и выключает затенение, чтобы оценить разницу, которое оно создаёт.

Эффекты затенения часто создаются при помощи комбинации box-shadow и background-image.

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

.cauldron {
    background:
        radial-gradient(25% 25% at 25% 55%, var(--rim-color), transparent),
        radial-gradient(100% 100% at -2% 50%, transparent, transparent 92%, var(--cauldron-color)),
        radial-gradient(100% 100% at -5% 50%, transparent, transparent 80%, var(--darkness)),
        linear-gradient(310deg, var(--inner-rim-color) 25%, transparent), var(--cauldron-color);
}

Обратите внимание, что здесь используются radial-gradient() и linear-gradient(), и они не всегда содержат идеально круглые числовые значения. Опять же, такие числа нормальны. На самом деле вы потратите много времени, настраивая и подкручивая разные значения в инспекторе стилей.

То же самое применимо и к box-shadow. Однако с ним мы также можем использовать значение inset, чтобы создавать хитрые границы и дополнительную глубину.

.cauldron__opening {
    box-shadow:
        0 0px calc(var(--size) * 0.05px) calc(var(--size) * 0.005px) var(--rim-color) inset,
        0 calc(var(--size) * 0.025px) 0 calc(var(--size) * 0.025px) var(--inner-rim-color) inset,
        0 10px 20px 0px var(--darkness), 0 10px 20px -10px var(--inner-rim-color);
}

Конечно, бывают случаи, когда куда разумнее использовать filter: drop-shadow(), чтобы получить желаемый эффект.

Сайт Линн Фишер a.singlediv.com — яркий пример этих свойств в действии. Потыкайте в разные элементы на этом сайте и исследуйте некоторые из иллюстраций, чтобы найти отличные способы применения box-shadow и background-image в иллюстрациях.

Свойство box-shadow настолько мощное, что вы можете создать целую иллюстрацию только с ним. Я однажды пошутил о создании CSS-иллюстрации доллара.

Я использовал генератор, чтобы создать иллюстрацию всего из одного <div>. Но Альваро Монторо пошёл дальше и написал генератор, который вместо этого использует box-shadow.

Препроцессоры очень полезны#

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

Вот другой пример, который демонстрирует структуру по принципу DRY. Цветы свёрстаны с одинаковой разметкой, но каждый имеет свой собственный класс с индексом, который используется для переопределения CSS-свойств.

У первого цветка такие свойства:

.flower--1 {
    --hue: 190;
    --x: 0;
    --y: 0;
    --size: 125;
    --r: 0;
}

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

.flower--2 {
    --hue: 320;
    --x: 140;
    --y: -75;
    --size: 75;
    --r: 40;
}
Анимированный отзывчивый CSS Leif попал в последний CodePen Spark! ✨ Для тех, кто не знаком с Animal Crossing, Leif — ленивец с зелеными пальцами, который посещает ваш остров 🌻 Вот таймлапс! 📹 @jh3yy

Вот и всё!#

Продолжайте, используйте эти советы, придумывайте свои собственные, делитесь ими, а также делитесь своими CSS-шедеврами! И, эй, если у вас есть свои лайфхаки, пожалуйста, делитесь ими тоже! Это определённо то, чему учатся методом проб и ошибок. То, что работает у меня, может отличаться от того, что работает у вас, и мы можем учиться на этих разных подходах.