Создание непрямоугольных макетов при помощи CSS-форм

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

С появлением CSS-форм в вебе стало проще придать контенту непрямоугольную форму или же воссоздать типографические (печатные) проекты и макеты.

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

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

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

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

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

Объявление формы

Все HTML-элементы имеют прямоугольную блочную модель, которая управляет потоком содержимого внутри и снаружи нее. Для того, чтобы придать элементу непрямоугольную форму, мы используем свойства shape-inside и shape-outside. Когда я писала эту статью, свойство shape-outside применялось только для плавающих элементов, а свойство shape-inside не было полностью внедрено, поэтому при использовании этих свойств у вас могут возникать ошибки. Свойства shape-* могут применяться только к блочным элементам. Неблочные элементы должны приводиться стилями к блочным, и только тогда можно использовать shape-свойства.

Shape-* свойства принимают одно из трех значений: auto, базовая фигура или URI изображения. Если свойство установлено в auto, то плавающие элементы используют margin box в обычном режиме. Если вы не знакомы с CSS box-model, то рекомендую ознакомиться, потому что вы должны знать, как это работает.

Если свойству задана «функция формы» (shape function), то форма вычисляется на основе выбора одного из значений функций — rectangle, inset-rectangle, circle, ellipse, polygon. Больше информации об этих функциях вы сможете найти в статье команды Adobe Platform.

И наконец, если свойству задано URI изображения, браузер будет использовать изображение для извлечения и вычисления формы, основываясь на альфа-канале изображения. Форма вычисляет путь, который охватывает область, где непрозрачность указанного изображения больше, чем значение параметра shape-image-threshold. Если параметр shape-image- threshold не задан, то исходным значением считается 0.5. Изображение должно быть CORS-same-origin, в противном случае оно не будет работать, и значение shape-* свойства автоматически установится в auto.

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

Формы, определенные при помощи свойств shape-*, могут быть изменены при помощи shape-margin и shape-padding. Эти свойства говорят сами за себя.

Установка системы координат на элементе.

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

Система координат обязательна, так как формы, которые вы задаете, будут определены через набор точек (и радиусов, если рисовать круги или эллипсы), и эти точки задаются через координаты x и y на координатной оси.

Свойства формы используют content-box элемента к которому они применяются ради системы координат, поэтому чтобы свойства заработали, вы должны задать ширину и высоту элементу, которые создают bounding-box, который в свою очередь можно использовать для установки системы координат для отрисовки форм. Если ширина и высота явно не указаны, свойства форм работать не будут.

Центр системы координат у элемента с заданными размерами устанавливается в верхний левый угол.

Чтобы задать форму элементу вы должны:

  1. Задать конкретные значения элементу (помните, что он должен быть плавающим, если вы используете shape-outside).
  2. Использовать свойства shape-*.

Применение фона к настраиваемым элементам

В то время как мы можем ограничивать строчный контент с помощью shapes (форм) реальный его контейнер не изменяется. Если элемент имеет конкретные внешние и внутренние отступы, границы, то они будут рассчитаны в соответствии с CSS3BOX. — W3C CSS Shapes Module Level 1

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

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

Фон применен к заданной пользователем форме
Фон применен к заданной пользователем форме

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

Фон применяется к прямоугольной коробке элемента
Фон применяется к прямоугольной коробке элемента

Как же применить цвет только к форме, а не ко всему элементу сразу? Вот где свойство clip-path из CSS-спецификации может помочь.

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

Каким образом? Какое значение свойства clip-path заставляет так работать?

Система координат для формы, к которой применялось свойство clip-path, устанавливается, используя границы элемента, для которого clip-path был применен, поэтому система координат аналогична для свойств shape-*.

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

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

Краткое (быстрое) напоминание

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

Расположение контента с двух сторон от CSS-формы
Расположение контента с двух сторон от CSS-формы

Сэмулировать обтекание формы с двух сторон можно при помощи плагина Исключающего удара (Exclusion Punch plugin) от Биара Трэвиса (Bear Travis).

А теперь приступим к созданию форм и макетов!

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

Вы можете посмотреть живое демо для каждого примера, просто кликнув на скриншот

Пример 1: Плавающий текст вокруг пользовательской формы с shape-outside

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

Скриншот примера №1. Нажмите на скриншот, чтобы увидеть работающее демо
Скриншот примера №1. Нажмите на скриншот, чтобы увидеть работающее демо

В демо-примере мы имеем контейнер с двумя элементами: .content (блок с текстом, находится слева) и .shaped, плавающий блок, прибитый к правой части контейнера, который получит собственную форму и будет иметь обтекающий слева текст. Заголовок в блоке .content получит такое же поведение, которое мы дадим блоку .shaped, далее я пропущу объяснения и буду рассказывать только то, что мы делаем с блоком .shaped.

<div class="container">
  <div class="shaped"></div>
  <div class="content">
    <h1><span>La</span> Tour <br/>Eiffel</h1>
    <p>Lorem Ipsum…</p>
  </div>
</div>        

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

.container{
  overflow:hidden;
  height: 100vh;
  width: 100vw;
}
.shaped{
  float:left;
  height:100vh;
  width:40vw;
  float:right;
  background: black url(../images/eiffel.jpg) center top no-repeat;
  background-size:cover;
}             

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

Использование polygon()

В первом способе мы будем использовать функцию polygon(). Данная функция принимает набор точек, которые образуют четырехугольник, каждая точка задается через координаты x и y. Мы собираемся определить очень простую четырехугольную фигуру с 4 вершинами, как показано на рисунке ниже (оранжевые и голубые точки):

Скриншот на котором изображены вершины, которые образуют четырехугольную форму
Скриншот на котором изображены вершины, которые образуют четырехугольную форму

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

.shaped{
  /*…*/
  shape-outside: polygon(0 0, 100% 0, 100% 100%, 30% 100%);
  shape-margin: 20px;
}         

И это все! Теперь текст обтекает плавающий элемент по специальной форме, которую мы определили.

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

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

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

Поэтому мы вырезаем лишнюю часть с помощью свойства clip-path, которому мы дадим то же значение/форму, что мы дали свойству shape-outside выше. Мы добавим это правило в набор правил:

.shaped{
  /*…*/
  clip-path: polygon(0 0, 100% 0, 100% 100%, 30% 100%);
}

Вот мы и закончили! Легко, не правда ли?

Заголовок страницы слева имеет такое же поведение, как и div с классом .shaped. Заголовок плавает внутри контейнера .content, ему дана конкретная высота и ширина для установки системы координат, а затем на нем устанавливается форма при помощи свойства shape-outside так же, как мы это делали для элемента .shaped.

Использование URI изображения

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

Для примера вместо использования функции polygon() для определения формы, мы укажем в свойстве shape-outside URI изображения, в результате чего браузер достанет форму из изображения и использует ее.

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

Изображение с альфа-каналом чей урл будет использован для извлечения и расчета значения формы
Изображение с альфа-каналом чей url будет использован для извлечения и расчета значения формы

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

Для использования этого изображения мы напишем следующее:

.shaped{
  /*…*/
  shape-outside: url(../images/mm.png);
  shape-image-threshold: 0.5; /* this property is used to set the threshold used
                                for extracting a shape from an image.
                                0.0 = fully transparent and 1.0 = fully opaque
                              */
}

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

Другая ситуация, когда стоит использовать URI изображения вместо функции формы, — это когда у вас много плавающих или исключающих областей в элементе; в таком случае использование URI изображения обязательно, потому что вы не можете (пока что) объявлять несколько форм на одном элементе, но у изображения может быть много областей, которые браузер может извлечь и использовать. Неплохо, правда? Последний пример как раз описывает подобную ситуацию.

Пример 2: оборачивание/обтекание текста внутри произвольной формы при помощи shape-inside

Для второго примера мы создадим простое демо, где окончательный результат будет выглядеть вот так:

Скриншот демо#2
Скриншот примера №2. Нажмите на скриншот, чтобы увидеть работающее демо

Цель этого примера — показать использование свойства shape-inside для плавающего текста в непрямоугольной форме. У нас есть контейнер с несколькими заглушками текста внутри него и мы применяем фоновое изображение этому контейнеру.

<div class="container">            
  <div class="content">
      <p></p>
  </div>
  <h2>Corn Bread</h2>
</div>    

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

Использование circle()

Функция circle() имеет три аргумента: cx, cy и r (радиус), где cx и cy - координаты центра окружности, а r — это радиус окружности.

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

Система координат и форма определенные на контейнере
Система координат и форма определенные на контейнере

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

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

.container{
  float:left;
  width:600px;
  height:900px;
  overflow:hidden;
  margin:0 50px;
  color:white;
  font-size:13px;
  padding:10px;
  background: url(../images/pan.jpg) top left no-repeat;
  background-size:100% 100%;
  /* declare shape using the shape function circle() */
  shape-inside: circle(400px, 60px, 160px);
}

Конечно, если вы не пытаетесь создать окружность идеальной формы, вы можете определить форму при помощи функции polygon().

Использование URI изображения

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

Изображение с альфа-каналом определяющее форму круга
Изображение с альфа-каналом определяющее форму круга

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

Чтобы определить форму, используя URI изображения вместо функции circle(), вы должны будете указать в значении свойства shape-inside URI изображения.

.container{
  /*…*/
  shape-inside: url(mask.png) top left;
}             

Пример 3: размещение текста внутри пользовательской формы при помощи shape-inside

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

Скриншот примера №3
Скриншот примера №3

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

Форма контейнера, содержащая текст — это явно «произвольная» многоугольная форма, а не геометрическая форма, которую можно было бы объявить, используя функции форм вроде circle(), ellipse() или rectangle(), поэтому для ее объявления мы будем использовать функцию polygon().

На рисунке снизу показана форма, задаваемая набором точек.

Многоугольник заданный набором точек
Многоугольник заданный набором точек

Так как эта форма задается довольно большим количество точек, вычислять их координаты было бы утомительно, поэтому было бы намного проще, если бы существовал визуальный инструмент, который помог бы расставить точки на изображении, правда ведь? И такой инструмент существует и создан Биаром Трэвисом из Adobe; на самом деле, это набор инструментов, который поможет вам при работе с CSS-формами. Обязательно посмотрите на Shape Tools, потому что они очень полезны.

Один из инструментов Shape tools называется Poly Draw, и он позволяет вам вручную рисовать формы, в частности многоугольники, а затем генерирует координаты формы, которые вы можете скопировать и вставить в свой CSS, чтобы задать форму на элементе.

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

[Рэзвэн Кэлимэн]30] (Razvan Caliman) предложил мне эту идею, когда я спросила его о доступности инструмента, который позволял бы определять формы на картинке прямо в браузере, такого же как, и тот, что он показывал во время своего выступления на CSS- конференции Conf EU]31]. Если вы еще не видели, то обязательно посмотрите. Исходники этого инструмента когда-нибудь (очень надеюсь, что скоро), будут открыты компанией Adobe, и тогда этот инструмент станет незаменимым при работе с CSS-формами. Ну, а пока можно обходиться инструментом Poly Draw.

После того как вы нарисовали форму при помощи Poly Draw, вам нужно только объявить результирующую форму элемента.

.container{
  width:445px;
  height:670px;
  overflow:hidden;
  margin:30px auto;
  /*shape generated by the Poly Draw tool*/
  shape-outside: polygon(170.67px 291.00px,126.23px 347.56px,139.79px 417.11px,208.92px 466.22px,302.50px 482.97px,343.67px 474.47px,446.33px 452.00px,443.63px 246.82px,389.92px 245.63px,336.50px 235.26px,299.67px 196.53px,259.33px 209.53px,217.00px 254.76px);
}    

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

Изображение с альфа-каналом определяющее форму для примера №2
Изображение с альфа-каналом определяющее форму для примера №2

Если вы хотите использовать URI изображения вместо функции формы, вы должны заменить значение свойства shape-inside следующим кодом:

.container{
  /*…*/
  shape-inside: url(mask.png) top left;
}           

Пример 4: Множественные области с shape-inside

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

Скриншот демо#3
Скриншот примера №3

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

<div class="container">
  <div class="content">
      <h2>Rosemary Sandwich</h2>
      <p></p>
  </div>
</div>      

Так как мы не можем объявить несколько форм на элементе, мы будем использовать изображение с альфа-каналом. Изображение может содержать любое количество форм и областей, поэтому оно идеально для определения множества форм на элементе; браузер извлекает все формы из изображения и использует их на элементе. Мы будем использовать следующее изображение для определения форм. Черные области на изображении будут определять плавающие области для контента внутри элемента .container, где будет находиться текст.

Изображение с альфа-каналом для определения формы для примера №3
Изображение с альфа-каналом для определения формы для примера №3

В определении элемента .container в значение свойства shape-inside мы подставим наше изображение с альфа-каналом и не забудем указать блоку высоту и ширину:

.container{
  width:556px;
  height:835px;
  overflow:hidden;
  margin:0 50px;
  color:white;
  position:relative;
  background: url(../images/bread.jpg) top left no-repeat;
  background-size: 100% 100%;
  shape-inside: url(mask.png) top left;
  font-size:13px;
}          

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

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

Иллюстрация набора точек для создания одного многоугольника
Иллюстрация набора точек для создания одного многоугольника

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

Комбинирование CSS-форм с регионами и флексбоксами для создания журнальных макетов

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

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

Travel Magazine by Bartosz Kwiecień on Behance. Подобный макет может быть воссоздан при помощи CSS-форм и регионов
Подобный макет может быть воссоздан при помощи CSS-форм и регионов

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

Заключение

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

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

Будущее веб-разработки выглядит все ярче и привлекательней с каждым днем. Хорошо быть веб-разработчиком!

Я надеюсь, что эта статья поможет вам войти в технический курс дела относительно CSS- форм и исключений. Это не последняя моя статья на эту тему. Комбинирование CSS-форм с другими передовыми технологиями мира CSS, такими как регионы, открывает двери в новый мир творческих идей и создания новых статей! ;)