Как создавать волнистые формы и узоры в CSS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Как создавать волнистые формы и узоры в CSS

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

«SVG это!» Вы могли бы сказать, и вы, вероятно, правы, что это лучший путь. Но мы увидим, что CSS может создавать хорошие волны, и код для него не обязательно должен быть сумасшедшим. И угадайте, что? У меня есть онлайн-генератор чтобы сделать это еще более тривиальным!

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

Некоторые значения могут выглядеть как «магические числа”, но на самом деле за ними стоит логика, и мы разберем код и раскроем все секреты создания волн.

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

Математика волн

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

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

Как создавать волнистые формы и узоры в CSS

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

Волнистая красная линия в форме волн.
Как создавать волнистые формы и узоры в CSS

Мы уже видим волну. Теперь заполним нижнюю часть (или верхнюю), чтобы получилось следующее:

Красная волна.
Как создавать волнистые формы и узоры в CSS

Тада! У нас есть волнистая форма, и мы можем управлять ею, используя одну переменную для радиусов кругов. Это одна из самых простых волн, которую мы можем сделать, и я продемонстрировал ее в this Предыдущая статья

Давайте добавим немного сложности, взяв первую иллюстрацию и немного переместив круги:

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

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

Теперь возьмите эту линию и повторите ее, и вы получите еще одну волну, более плавную.

Красная волнистая линия.
Как создавать волнистые формы и узоры в CSS
Красная волна.
Как создавать волнистые формы и узоры в CSS

Я думаю, вы поняли идею. Управляя положением и размером кругов, мы можем создать любую волну, которую захотим. Мы даже можем создать для них переменные, которые я назову P и S, Соответственно.

Как создавать волнистые формы и узоры в CSS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.
Как создавать волнистые формы и узоры в CSS

Вы, наверное, заметили, что в онлайн-генераторе мы управляем волной с помощью двух входов. Они сопоставляются с вышеуказанными переменными. S это «Размер волны» и P это «кривизна волны».

Я определяю P as P = m*S в котором m это переменная, которую вы настраиваете при обновлении кривизны волны. Это позволяет нам всегда иметь одинаковую кривизну, даже если мы обновляем S.

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

Не забываем о радиусе нашего круга! Это также можно определить с помощью S и P как это:

R = sqrt(P² + S²)/2

После появления P равно 0, у нас будет R = S/2.

У нас есть все, чтобы начать преобразовывать все это в градиенты в CSS!

Создание градиентов

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

Начнем с частного случая, когда P равно 0. Вот иллюстрация первого градиента:

Этот градиент создает первую кривизну, заполняя всю нижнюю область — так сказать, «воду» волны.

Как создавать волнистые формы и узоры в CSS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.
Как создавать волнистые формы и узоры в CSS
.wave {
  --size: 50px;

  mask: radial-gradient(var(--size) at 50% 0%, #0000 99%, red 101%) 
    50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

Ассоциация --size переменная определяет радиус и размер радиального градиента. Если мы сравним его с S переменная, то она равна S/2.

Теперь добавим второй градиент:

Второй градиент — не что иное, как круг, завершающий нашу волну:

radial-gradient(var(--size) at 50% var(--size), blue 99%, #0000 101%) 
  calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%

Если вы отметите предыдущая статья вы увидите, что я просто повторяю то, что уже делал там.

Я следил за обеими статьями, но конфигурации градиента не совпадают.

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

Вот полный код для нашей первой волны:

.wave {
  --size: 50px;

  mask:
    radial-gradient(var(--size) at 50% var(--size),#000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--size) at 50% 0px, #0000 99%, #000 101%) 
      50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

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

Наш код будет выглядеть так:

.wave {
  --size: 50px;
  --p: 25px;

  mask:
    radial-gradient(var(--size) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--size) at 50% calc(-1*var(--p)), #0000 99%, #000 101%) 
      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
}

я представил новую --p переменная, которая использовала ее для определения положения центра каждого круга. Первый градиент использует 50% calc(-1*var(--p)), поэтому его центр перемещается вверх, пока второй использует calc(var(--size) + var(--p)) чтобы переместить его вниз.

Демо стоит тысячи слов:

Круги не выровнены и не касаются друг друга. Мы разнесли их далеко друг от друга, не изменив их радиусы, поэтому мы потеряли нашу волну. Но мы можем все исправить, используя ту же математику, которую мы использовали ранее для расчета нового радиуса. Помните, что R = sqrt(P² + S²)/2. В нашем случае --size равно S/2; то же самое для --p что также равно P/2 так как мы двигаем оба круга. Таким образом, расстояние между их центральными точками в два раза превышает значение --p для этого:

R = sqrt(var(--size) * var(--size) + var(--p) * var(--p))

Это дает нам результат 55.9px.

Наша волна вернулась! Давайте вставим это уравнение в наш CSS:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p) * var(--p) + var(--size)*var(--size));

  mask:
    radial-gradient(var(--R) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0 / calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(-1*var(--p)), #0000 99%, #000 101%) 
      50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

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

Это чертовски круто: достаточно двух градиентов, чтобы получить крутую волну, которую можно применить к любому элементу с помощью mask имущество. Больше никаких проб и ошибок — все, что вам нужно, это обновить две переменные, и все готово!

Обратить волну

Что, если мы хотим, чтобы волны шли в другом направлении, где мы заполняем «небо» вместо «воды». Хотите верьте, хотите нет, но все, что нам нужно сделать, это обновить два значения:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p) * var(--p) + var(--size) * var(--size));

  mask:
    radial-gradient(var(--R) at 50% calc(100% - (var(--size) + var(--p))), #000 99%, #0000 101%)
      calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(100% + var(--p)), #0000 99%, #000 101%) 
      50% calc(100% - var(--size)) / calc(4 * var(--size)) 100% repeat-x;
}

Все, что я сделал, это добавил смещение, равное 100%, выделено выше. Вот результат:

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

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size) * var(--size));

  mask:
    radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% bottom var(--size) / calc(4 * var(--size)) 100% repeat-x;
}

Мы используем left и bottom ключевые слова для указания сторон и смещения. По умолчанию браузер по умолчанию left и top - поэтому мы используем 100% чтобы переместить элемент вниз. На самом деле мы перемещаем его из top by 100%, так что это действительно то же самое, что сказать bottom. Читать гораздо легче, чем математику!

С этим обновленным синтаксисом все, что нам нужно сделать, это поменять местами bottom для top — или наоборот — изменить направление волны.

А если вы хотите получить и верхние, и нижние волны, объединяем все градиенты в одном объявлении:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  mask:
    /* Gradient 1 */
    radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      left calc(50% - 2*var(--size)) bottom 0 / calc(4 * var(--size)) 51% repeat-x,
    /* Gradient 2 */
    radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% bottom var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x,
    /* Gradient 3 */
    radial-gradient(var(--R) at left 50% top calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      left calc(50% - 2 * var(--size)) top 0 / calc(4 * var(--size)) 51% repeat-x,
    /* Gradient 4 */
    radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% top var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x;
}

Если вы проверите код, то увидите, что помимо объединения всех градиентов я также уменьшил их высоту с 100% в 51% чтобы они оба покрывали половину элемента. Да, 51%. Нам нужен этот небольшой дополнительный процент для небольшого перекрытия, чтобы избежать пробелов.

А как насчет левой и правой стороны?

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

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

Волнистые линии

Ранее мы сделали первую волну, используя красную линию, а затем заполнили нижнюю часть элемента. Как насчет этой волнистой линии? Это тоже волна! Еще лучше, если мы сможем контролировать его толщину с помощью переменной, чтобы мы могли использовать ее повторно. Давай сделаем это!

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

#0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%

Думаю, вы уже догадались, что --b переменная — это то, что мы используем для управления толщиной линии. Давайте применим это к нашим градиентам:

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

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

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
  mask:
    radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g)) 
      calc(50% - 2*var(--size)) 0/calc(4*var(--size)) 100%,
    radial-gradient(var(--R) at left 50% top    calc(-1*var(--p)), var(--_g)) 
      50% var(--size)/calc(4*var(--size)) 100%;
}

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

/* Size plus thickness */
calc(var(--size) + var(--b))

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

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;  
  mask:
    radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g)) 
      calc(50% - 2*var(--size)) 50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat,
    radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), var(--_g)) 50%
      50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat;
}

Еще не совсем там:

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

Мы почти на месте! Нам нужно небольшое исправление для радиуса, чтобы иметь идеальное перекрытие. Обе линии должны быть смещены на половину границы (--b) толщина:

Мы сделали это! Идеальная волнистая линия, которую мы можем легко настроить, контролируя несколько переменных:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: calc(sqrt(var(--p) * var(--p) + var(--size) * var(--size)) + var(--b) / 2);

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
  mask:
    radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), var(--_g)) 
     calc(50% - 2*var(--size)) calc(50% - var(--size)/2 - var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x,
    radial-gradient(var(--R) at left 50% top calc(-1*var(--p)),var(--_g)) 
     50%  calc(50% + var(--size)/2 + var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x;
}

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

Волнистые узоры

Мы можем сделать узор из только что созданной волнистой линии!

О нет, код паттерна будет еще сложнее понять!

Нисколько! У нас уже есть код. Все, что нам нужно сделать, это удалить repeat-x от того, что у нас уже есть, и тада. 🎉

Красивый волнистый узор. Помнишь уравнение, о котором я сказал, что мы вернемся?

/* Size plus thickness */
calc(var(--size) + var(--b))

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

Вот та же картина, но в другом направлении:

Я предоставляю вам код в этой демонстрации, но я бы хотел, чтобы вы разобрали его и поняли, какие изменения я внес, чтобы это произошло.

Упрощение кода

Во всех предыдущих демонстрациях мы всегда определяли --size и --p независимо. Но помните ли вы, как я упоминал ранее, что онлайн-генератор оценивает P как равный m*S, Где m контролирует кривизну волны? Определив фиксированный множитель, мы можем работать с одной конкретной волной, и код может стать проще. Это то, что нам понадобится в большинстве случаев: определенная волнистая форма и переменная для управления ее размером.

Давайте обновим наш код и представим m переменная:

.wave {
  --size: 50px;
  --R: calc(var(--size) * sqrt(var(--m) * var(--m) + 1));

  mask:
    radial-gradient(var(--R) at 50% calc(var(--size) * (1 + var(--m))), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(-1 * var(--size) * var(--m)), #0000 99%, #000 101%) 
      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
  }

Как видите, нам больше не нужно --p переменная. я заменил его на var(--m)*var(--size), и соответственно оптимизировал некоторые математические операции. Теперь, если мы хотим работать с определенной волнистой формой, мы можем опустить --m переменную и заменить ее фиксированным значением. Давай попробуем .8 например.

--size: 50px;
--R: calc(var(--size) * 1.28);

mask:
  radial-gradient(var(--R) at 50% calc(1.8 * var(--size)), #000 99%, #0000 101%) 
    calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
  radial-gradient(var(--R) at 50% calc(-.8 * var(--size)), #0000 99%, #000 101%) 
    50% var(--size) / calc(4 * var(--size)) 100% repeat-x;

Видите, как теперь код стал проще? Только одна переменная для управления вашей волной, плюс вам больше не нужно полагаться на sqrt() который не поддерживает браузер!

Вы можете применить ту же логику ко всем демонстрациям, которые мы видели, даже к волнистым линиям и узору. Я начал с подробного математического объяснения и дал общий код, но вам может понадобиться более простой код в реальном случае использования. Это то, что я делаю все время. Я редко использую общий код, но я всегда рассматриваю упрощенную версию, особенно потому, что в большинстве случаев я использую некоторые известные значения, которые не нужно хранить в качестве переменных. (Спойлер Alert: Я поделюсь несколькими примерами в конце!)

Ограничения этого подхода

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

Как создавать волнистые формы и узоры в CSS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.
Как создавать волнистые формы и узоры в CSS

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

Как создавать волнистые формы и узоры в CSS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.
Как создавать волнистые формы и узоры в CSS

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

Подведение итогов

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

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

А вы? Воспользуйтесь моим онлайн-генератором (или напишите код вручную, если вы уже выучили всю математику наизусть) и покажите мне свои творения! Давайте иметь хорошую коллекцию в разделе комментариев.

Отметка времени:

Больше от CSS хитрости