Введение в CSS-фигуры

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

Но ситуация может измениться с введением новой спецификации CSS – фигур. CSS-фигуры впервые были реализованы в браузере Google Chrome версии 37.

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

Фигуры можно задавать вручную либо производить от рисунков.

Давайте рассмотрим создание фигур на простом примере.

Наверное, не я один был так наивен, впервые поместив на страницу изображение с прозрачным фоном в ожидании того, что контент заполнит прозрачные места. Представьте моё разочарование, когда этого не произошло, и очертания изображения остались прямоугольными! Но теперь у нас есть фигуры, и они помогут нам восстановить справедливость:

Директива shape-outside: url(image.png) говорит браузеру, чтобы он извлёк очертания фигуры из изображения.

Свойство shape-image-threshold задаёт минимальный уровень прозрачности, при котором пикселы будут участвовать в формировании фигуры. Значение этого свойства может изменяться в диапазоне от 0.0 (полностью прозрачный) до 1.0 (полностью непрозрачный).

В нашем примере shape-image-threshold: 0.5 означает, что только пикселы с прозрачностью 50% и меньше будут считаться составной частью фигуры.

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

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

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

Создание фигур вручную

Помимо извлечения фигур из прозрачных изображений, новое расширение CSS позволяет создавать фигуры вручную. Вы можете использовать функциональные атрибуты circle(), ellipse(), inset() и polygon().

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

Функция circle()

Функция circle()

Полная нотация (то есть формат свойства) для круга выглядит как circle(r at cx cy), где r – радиус круга, а cx и cy – положение центра круга на осях координат. Координаты центра круга можно опустить; тогда круг будет выстроен относительно центра родительского блока:

.element{
  shape-outside: circle(50%);
  width: 300px;
  height: 300px;
  float: left;
}

В выше приведённом примере контент обтекает дугу. Параметр 50% задаёт радиус круга, который в данном случае составляет ровно половину высоты и ширины родительского элемента. Изменение размера этого элемента повлияет на размер фигуры. Это – простейший пример того, как сделать фигуру адаптивной.

Необходимо запомнить: CSS-фигуры задают только область обтекания вокруг элемента. Они не влияют на другие его свойства, например, на цвет фона. Чтобы обрезать фон, необходимо воспользоваться другим расширением CSS – маскированием.

К нему относятся свойства clip-path и mask-image. Последнее кажется особенно полезным, так как его нотация совпадает с нотацией CSS-фигур, и одни и те же параметры можно использовать дважды:

Функция circle() - 2

Далее в наших примерах мы будем использовать CSS-маскирование для большей наглядности.

Но вернёмся к нашим фигурам.

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

sqrt(width^2 + height^2) / sqrt(2)

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

Координаты для фигур могут быть заданы в любых единицах, допустимых в CSS: px, em, % и так далее. Вы можете выбрать ту единицу, которая будет отвечать вашим нуждам.

Вы можете уточнить расположение центра круга, указав два значения после ключевого слова at:

.element{
  shape-outside: circle(50% at 0 0);
}

Позиция указывается относительно начала координат. Что это за координаты? Здесь уместно будет разъяснить понятие референсного блока.

Референсные блоки фигур CSS

Референсный блок – это виртуальный прямоугольник вокруг фигуры, который формирует систему координат для её отрисовки. За начало координат принимается верхний левый угол этого прямоугольника; при этом ось X направлена вниз, а Y – вправо:

Референсные блоки фигур CSS

Свойство shape-outside изменяет очертания области обтекания фигуры, вокруг которой будет располагаться контент. По умолчанию эта область у фигуры, как и у любого другого блока, распространяется вплоть до внешних краёв, заданных свойством margin.

Поэтому два ниже приведённых приёма приведут к одинаковым результатам:

.element{
  shape-outside: circle(50% at 0 0);
  /* идентично: */
  shape-outside: circle(50% at 0 0) margin-box;
}

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

.element{
  shape-outside: circle(50% at 0 0) margin-box;
  margin: 100px;
}

Начало координат теперь лежит вне родительского блока (а точнее, на 100 пикселей выше и на 100 пикселей левее). Там же расположен и центр круга. Вычисленное значение радиуса также выросло, принимая во внимание увеличение поверхности, охватываемое системой координат, заданной margin-box:

Референсные блоки фигур CSS - 2

Можно выбрать свойство, задающее референсный блок, из нескольких вариантов: margin-box, border-box, padding-box и content-box. Их названия характеризуют очертания, по которым строится координатная сетка.

Что такое margin-box, мы уже знаем. border-box ограничен внешними краями рамки элемента, padding-box – заполнением, а content-box – поверхностью, которую занимает содержимое элемента:

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

Функция ellipse()

Функция ellipse()

Эллипс выглядит как сплющенный круг. Определение эллипса выглядит как ellipse(rx ry at cx cy), где rx и ry – радиусы эллипса относительно осей X и Y, а cx и cy – координаты центра эллипса:

.element{
  shape-outside: ellipse(150px 300px at 50% 50%);
  width: 300px;
  height: 600px;
}

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

Радиусы также можно указать при помощи ключевых слов: farthest-side будет означать расстояние от центра эллипса до самой дальней стороны референсного блока, а closest-side, соответственно – до ближайшей стороны:

.element{
  shape-outside: ellipse(closest-side farthest-side at 50% 50%);
  /* идентично: */
  shape-outside: ellipse(150px 300px at 50% 50%);
  width: 300px;
  height: 600px;
}

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

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

Функция polygon()

Функция polygon()

Если круги и эллипсы вам наскучили, функция создания многоугольной области наверняка займёт вас на более длительное время. Её формат: polygon(x1 y1, x2 y2, …). В скобках вы должны задать пары координат для каждой вершины многоугольника. Минимальное количество пар – три, для треугольника:

.element{
  shape-outside: polygon(0 0, 0 300px, 300px 600px);
  width: 300px;
  height: 600px;
}

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

.element{
  /* polygon responsive to font-size*/
  shape-outside: polygon(0 0, 0 100%, 100% 100%);
  width: 20em;
  height: 40em;
}

Есть также необязательный параметр fill-rule, заимствованный из SVG, который управляет тем, как браузер определяет, что является «внутренней» поверхностью многоугольника, если его грани пересекаются.

Этот вопрос отлично разъясняет Джони Трифл в своей статье «Как применяется правило заполнения в SVG». Значение fill-rule по умолчанию: nonzero:

.element{
  shape-outside: polygon(0 0, 0 100%, 100% 100%);
  /* идентично: */
  shape-outside: polygon(nonzero, 0 0, 0 100%, 100% 100%);
}

Функция inset()

Функция inset() позволяет создать прямоугольную область, которую будет обтекать контент. Это кажется странным, ведь фигуры CSS, как мы уже говорили, изначально были предназначены для того, чтобы задавать не прямоугольное расположение для содержимого блоков. И это на самом деле странно!

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

Полный формат функции описывается так: inset(top right bottom left border-radius). Первые четыре параметра задают расположение фигуры в системе координат. Пятый задаёт виртуальную рамку для фигуры, причём этот параметр может быть, как развёрнут согласно принятой в CSS нотации для border-radius, так и пропущен как необязательный:

.element{
  shape-outside: inset(100px 100px 100px 100px);
  float: left;
}

Создание фигур из референсных блоков

Если не указать аргумент-функцию в свойстве shape-outside, то браузер сам создаст фигуру на основе референсного блока. Референсным блоком по умолчанию является margin-box.

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

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

Создание фигур из референсных блоков
.element{
  border-radius: 50%;
  shape-outside: border-box;
  float: left;
}

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

Создание фигур из референсных блоков - 2

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

А так можно добиться того же визуального эффекта, но добавив гибкость в размещении цитаты:

.pull-quote{
  shape-outside: content-box;
  margin-top: 200px;
  float: left;
}

Мы явно задали вид референсного блока и его положение в системе координат. В нашем случае объём цитаты определил фигуру, вокруг которой плавает основной контент. А свойство margin-top задало положение цитаты на странице независимо от её расположения в DOM-дереве.

Отступы фигуры

Обратите внимание, как близко плавающий контент примыкает к обтекаемому элементу. Добавить свободного пространства вокруг фигуры можно при помощи свойства shape-margin:

.element{
  shape-outside: circle(40%);
  shape-margin: 1em;
  float: left;
}

Эффект подобен тому, который можно получить при использовании свойства margin, но разница состоит в том, что shape-margin действует только внутри shape-outside. Он добавит отступ только в том случае, если тот поместится в референсный блок. Именно поэтому радиус фигуры в нашем последнем примере составляет 40, а не 50%.

Если бы радиус был установлен по максимуму, для отступа не хватило бы места. Вообще говоря, фигура всегда ограничивается своим margin-box, и при выходе за его пределы просто обрезается по форме прямоугольника.

Также важно учитывать, что свойство shape-margin принимает в качестве аргумента только одно положительное значение. Его нельзя развернуть, как обычный margin. И в самом деле, какой shape-margin-top может быть у круга?

Анимирование фигур

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

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

Вы можете анимировать радиусы и расположение кругов и эллипсов, если они представлены в величинах, которые браузер может интерполировать. Например, перейти от circle(30%) до circle(50%) у вас получится без проблем, но переход между farthest-side и closest-side загонит браузер в тупик:

.element{
  shape-outside: circle(30%);
  transition: shape-outside 1s;
  float: left;
}

.element:hover{
  shape-outside: circle(50%);
}
Анимирование фигур

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

Зато вы можете помещать две и более вершины в одну точку, чтобы создать видимость того, что их число в ходе анимации меняется:

.element{
  /* four vertices (looks like rectangle) */
  shape-outside: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  transition: shape-outside 1s;
}

.element:hover{
  /* four vertices, but second and third overlap (looks like triangle) */
  shape-outside: polygon(0 0, 100% 50%, 100% 50%, 0 100%);
}
Анимирование фигур - 2

Создание обтекания внутри фигуры

Создание обтекания внутри фигуры

Черновые спецификации CSS-фигур включали свойство shape-inside, которое позволяло использовать плавающий контент внутри фигуры. Некоторое время это свойство было реализовано в WebKit-браузерах и в Google Chrome.

Но заворачивание контента с произвольным внутренним позиционированием в произвольный наружный контур оказалось слишком сложно в реализации и вело к программным ошибкам. Поэтому свойство shape-inside было перенесено во второй уровень стандарта, а реализующий его код удалён из браузеров.

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

Компромисс – в нарушении семантики: нам придётся использовать два элемента без какого-либо определённого значения:

<div>
  <div class="left-shape"></div>
  <div class="right-shape"></div>

  Lorem ipsum...
</div>

Положение подпорок (.left-shape и .right-shape) в самом начале контейнера имеет значение, так как они должны обогнуть содержимое по краям:

.left-shape{
  shape-outside: polygon(0 0, ...);
  float: left;
  width: 50%;
  height: 100%;
}

.right-shape{
  shape-outside: polygon(50% 0, ...);
  float: right;
  width: 50%;
  height: 100%;
}
Создание обтекания внутри фигуры - 2

Такое решение позволяет подпоркам занять всё пространство контейнера, но свойство shape-outside выгрызет в них место для остального контента.

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

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

В «Алисе в Стране чудес» мы использовали позицию прокрутки, чтобы изменить верхнее поле контента. Контент оказывался зажатым между двумя плавающими элементами. Это создавало иллюзию того, что текст спускается в кроличью нору. Кому-то этот наворот покажется лишним, но на мой взгляд – круто.

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

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

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

Отказоустойчивость и последовательное улучшение

Начните работу над вёрсткой с предположения, что браузер не поддерживает модные CSS-расширения вроде фигур. Затем добавьте их обнаружение и обработку. Существует отличная библиотека для тестирования браузеров – Modernizr. Тест на поддержку фигур находится в дополнительной секции.

Некоторые браузеры, в том числе и Google Chrome, имеют встроенный механизм обнаружения функций – CSS-правило @supports. Вот как можно отказоустойчиво использовать это правило:

.element{
  /* стили для всех браузеров */
}

@supports (shape-outside: circle(50%)){
  /* стили только для браузеров, поддерживающих CSS-фигуры */
  .element{
    shape-outside: circle(50%);
  }
}

Леа Веру написала подробную статью об использовании правила @supports.

Неоднозначности в именовании CSS-расширений

То, что мы сегодня знаем как CSS-фигуры, когда-то называлось «CSS-исключения и фигуры». Разница может показаться незначительной, но на самом деле она очень важна.

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

Итак, фигуры и исключения – не одно и то же, но они отлично дополняют друг друга. Проблема в том, что фигуры уже реализованы в современных браузерах, в то время как исключения находятся в разработке, и их взаимодействие с фигурами ещё не определено стандартом.

Инструменты для работы с CSS-фигурами

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

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

Brackets. Имеется расширение для этого замечательного текстового редактора, которое называется «CSS Shapes Editor». Оно использует имеющийся в Brackets режим предварительного просмотра для интерактивного редактирования вершин.

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

Инспектор Google Chrome умеет выделять фигуры на странице. Наведите курсор на элемент, имеющий свойство shape-outside, и соответствующая фигура будет подсвечена.

Polyfill. Поддержка фигур была впервые реализована в браузере Google Chrome. В ближайшее время ожидается релиз Apple Safari 8 и iOS 8 с браузером, поддерживающим фигуры.

Другие браузеры также планируют включить этот функционал в свои продукты. А пока существует shapes-polyfill – реализация фигур для всех JavaScript-совместимых браузеров.

Заключение

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

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

РедакцияПеревод статьи «Getting Started with CSS Shapes»