Различные способы получить градиентную тень с помощью CSS (Gradient Shadows)

Различные способы получить градиентную тень с помощью CSS (Gradient Shadows)
Различные способы получить градиентную тень с помощью CSS (Gradient Shadows)

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

Но сначала ... еще одна статья о тенях градиента? Действительно?

Да, это еще один пост на эту тему, но он другой. Вместе мы собираемся раздвинуть границы, чтобы получить решение, которое охватывает: прозрачность. Большинство трюков работают, если элемент имеет непрозрачный фон, но что, если у нас прозрачный фон? Мы рассмотрим этот случай здесь!

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

Содержание

Непрозрачное решение

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

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

.box {
  position: relative;
}
.box::before {
  content: "";
  position: absolute;
  inset: -5px; /* control the spread */
  transform: translate(10px, 8px); /* control the offsets */
  z-index: -1; /* place the element behind */
  background: /* your gradient here */;
  filter: blur(10px); /* control the blur */
}

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

box-shadow: 10px 8px 10px 5px orange;

Это должно дать вам хорошее представление о том, что делают ценности в первом фрагменте. У нас есть смещения X и Y, радиус размытия и расстояние распространения. Обратите внимание, что нам нужно отрицательное значение для расстояния распространения, которое исходит от inset.

Вот демо, показывающее градиентную тень рядом с классикой box-shadow:

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

Это решение хорошее, но все еще имеет несколько недостатков, связанных с z-index: -1. Да, там происходит “наложение контекста”!

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

Первое решение, которое мы рекомендуем, - это использовать 3Dtransform:

.box {
  position: relative;
  transform-style: preserve-3d;
}
.box::before {
  content: "";
  position: absolute;
  inset: -5px;
  transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */
  background: /* .. */;
  filter: blur(10px);
}

Вместо использования z-index: -1, Мы будем использовать отрицательный перевод вдоль оси Z. Мы положим все внутрь translate3d(). Не забудьте использовать transform-style: preserve-3d на родительском элементе; в противном случае 3D transform не вступит в силу.

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

Если по какой-то причине вы не можете использовать 3D transform, другое решение — полагаться на два псевдоэлемента - ::before и ::after. Один создает тень градиента, а другой воспроизводит основной фон (и другие стили, которые могут вам понадобиться). Таким образом, мы можем легко контролировать порядок наложения обоих псевдоэлементов.

.box {
  position: relative;
  z-index: 0; /* We force a stacking context */
}
/* Creates the shadow */
.box::before {
  content: "";
  position: absolute;
  z-index: -2;
  inset: -5px;
  transform: translate(10px, 8px);
  background: /* .. */;
  filter: blur(10px);
}
/* Reproduces the main element styles */
.box::after {
  content: """;
  position: absolute;
  z-index: -1;
  inset: 0;
  /* Inherit all the decorations defined on the main element */
  background: inherit;
  border: inherit;
  box-shadow: inherit;
}

Важно отметить, что мы заставляем основной элемент создавать контекст стекирования, объявляя для него z-index: 0или любое другое свойство, которое делает то же самое. Кроме того, не забывайте, что псевдоэлементы рассматривают поле заполнения основного элемента в качестве ссылки. Итак, если у основного элемента есть граница, вам нужно учитывать это при определении стилей псевдоэлементов. Вы заметите, что мы используем inset: -2px для::after для учета границы, определенной на главном элементе.

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

Прозрачное решение

Давайте продолжим с того места, где мы остановились на 3D transform и удалим фон из основного элемента. Начнём с тени, у которой оба смещения и расстояние распространения равны 0.

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

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

clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -1

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

Синяя область - это видимая часть после нанесенияclip-path. Мы используем синий цвет только для иллюстрации концепции, но на самом деле мы увидим тень только внутри этой области. Как вы можете видеть, у нас есть четыре точки, определенные с большим значением (B). Наша большая ценность 100vmax, но это может быть любая большая ценность, которую вы хотите. Идея в том, чтобы у нас было достаточно места для тени. У нас также есть четыре точки, которые являются углами псевдоэлемента.

Стрелки иллюстрируют путь, который определяет многоугольник. Мы начинаем с (-B, -B), пока не достигнем (0,0). В общей сложности нам нужно 10 баллов. Не восемь точек, потому что две точки повторяются дважды в пути ((-B,-B) и (0,0)).

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

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

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

.box {
  --s: 10px; /* the spread  */
  position: relative;
}
.box::before {
  inset: calc(-1 * var(--s));
  clip-path: polygon(
    -100vmax -100vmax,
     100vmax -100vmax,
     100vmax 100vmax,
    -100vmax 100vmax,
    -100vmax -100vmax,
    calc(0px  + var(--s)) calc(0px  + var(--s)),
    calc(0px  + var(--s)) calc(100% - var(--s)),
    calc(100% - var(--s)) calc(100% - var(--s)),
    calc(100% - var(--s)) calc(0px  + var(--s)),
    calc(0px  + var(--s)) calc(0px  + var(--s))
  );
}

Мы определили переменную CSS --s для расстояния распространения и обновили точки полигона. Мы не касались точек, где используем большое значение. А только обновляем точки, которые определяют углы псевдоэлемента. Увеличиваем все нулевые значения на --s и уменьшаем 100% значения на --s.

Та же логика со смещениями. Когда мы переводим псевдоэлемент, тень не выровнена, и нам нужно снова исправить многоугольник и переместить точки в противоположном направлении.

.box {
  --s: 10px; /* the spread */
  --x: 10px; /* X offset */
  --y: 8px;  /* Y offset */
  position: relative;
}
.box::before {
  inset: calc(-1 * var(--s));
  transform: translate3d(var(--x), var(--y), -1px);
  clip-path: polygon(
    -100vmax -100vmax,
     100vmax -100vmax,
     100vmax 100vmax,
    -100vmax 100vmax,
    -100vmax -100vmax,
    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
    calc(0px  + var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
    calc(100% - var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
    calc(100% - var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y))
  );
}

Есть еще две переменные для смещений: --x и --y. Мы используем их внутри transform, а также обновляем clip-path значения. Мы по—прежнему не трогаем точки полигона с большими значениями, но мы компенсируем все остальные - уменьшаем --x от координат X и --y от координат Y.

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

Нам всё ещё нужен 3D transform трюк?

Все зависит от границы. Не забывайте, что ссылка на псевдоэлемент - это поле заполнения, поэтому, если вы примените границу к своему основному элементу, у вас будет перекрытие. Вы либо сохраняете transform трюк 3D, либо обновляете inset значение для учета границы.

Вот предыдущая демонстрация с обновленным inset значением вместо 3D transform:

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

С более ранним непрозрачным решением, возможно, вы столкнетесь с проблемой стекирования контекста. И с прозрачным решением, возможно, вместо этого вы столкнетесь с проблемой границ. Теперь у вас есть варианты и способы обойти эти проблемы. Трюк с 3D-преобразованием - мое любимое решение, потому что он устраняет все проблемы.

Добавление радиуса границы

Если вы попытаетесь добавить border-radius к элементу при использовании непрозрачного решения, с которого мы начали, это довольно тривиальная задача. Все, что вам нужно сделать, это наследовать то же значение от основного элемента, и все готово.

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

Это другая история, когда имеешь дело с прозрачным решением. К сожалению, это означает поиск другого решения, потому что clip-path не может справиться с кривизнами. Это означает, что мы не сможем вырезать область внутри основного элемента.

Мы введем mask свойство в микс.

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

Мы решили вставить дополнительный элемент ради упрощения кода. Вот разметка:

<div class="box">
  <sh></sh>
</div>

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

Первым шагом является позиционирование <sh> элемента и преднамеренное создание переполнения:

.box {
  --r: 50px;
  position: relative;
  border-radius: var(--r);
}
.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
}

Код может показаться немного странным, но по ходу дела мы разберемся с логикой, стоящей за ним. Далее мы создаем тень градиента, используя псевдоэлемент <sh>.

.box {
  --r: 50px;
  position: relative;
  border-radius: var(--r);
  transform-style: preserve-3d;
}
.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
  transform: translateZ(-1px)
}
.box sh::before {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: var(--r);
  background: /* Your gradient */;
  filter: blur(10px);
  transform: translate(10px,8px);
}

Как вы можете видеть, псевдоэлемент использует тот же код, что и все предыдущие примеры. Единственное отличие заключается в том, что 3D transform определяется на <sh> элементе вместо псевдоэлемента. На данный момент у нас есть градиентная тень без функции прозрачности:

Обратите внимание, что область <sh> элемента определяется черным контуром. Почему мы это делаем? Потому что таким образом мы можем применить к нему mask, чтобы скрыть часть внутри зеленой области и сохранить переливающуюся часть там, где нам нужно видеть тень.

Знаем, что это немного сложно, но в отличие отclip-path, mask свойство не учитывает область вне элемента для отображения и скрытия объектов. Вот почему мы были обязаны ввести дополнительный элемент — для имитации “внешней” области.

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

Еще одна полезная вещь, которую мы получаем от использования дополнительного элемента, заключается в том, что элемент фиксирован, и только псевдоэлемент перемещается (с помощью translate). Это позволит нам легко определить маску, которая является последним шагом этого трюка.

mask:
  linear-gradient(#000 0 0) content-box,
  linear-gradient(#000 0 0);
mask-composite: exclude;

Дело сделано! У нас есть наша градиентная тень, и она поддерживаетborder-radius! Вы, вероятно, ожидали сложного mask значения с множеством градиентов, но нет! Нам нужны только два простых градиента и>mask-composite для завершения волшебства.

Давайте изолируем <sh> элемент, чтобы понять, что там происходит:

.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid red;
  background: lightblue;
  border-radius: calc(150px + var(--r));
}

Вот что мы получаем:

Обратите внимание, как внутренний радиус соответствует основному элементу border-radius. Мы определили большую границу (150px) и border-radius равный большой границе плюс радиус основного элемента. Снаружи у нас есть радиус, равный 150px + R. Внутри у нас есть 150px + R - 150px = R.

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

mask:
  linear-gradient(#000 0 0) content-box,
  linear-gradient(#000 0 0);
mask-composite: exclude;

Мы использовали ту же технику для создания границы, которая поддерживает градиенты и border-radius.

Есть ли какие-либо недостатки у этого метода?

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

Исправить это относительно просто: добавьте ширину границы для <sh> элемента inset.

.box {
  --r: 50px;
  border-radius: var(--r);
  border: 2px solid;
}
.box sh {
  position: absolute;
  inset: -152px; /* 150px + 2px */
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
}

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

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

.box {
  --r-top: 10px;
  --r-right: 40px;
  --r-bottom: 30px;
  --r-left: 20px;
  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}
.box sh {
  border-radius: calc(150px + var(--r-top)) calc(150px + var(--r-right)) calc(150px + var(--r-bottom)) calc(150px + var(--r-left));
}
.box sh:before {
  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}

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

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

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

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

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

Понравилась статья?
Будем признательны, если поделитесь в соцсетях или мессенджерах, а также присоединитесь к нашей группе Вконтакте. Будет интересно!

Вас может заинтересовать:

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

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

Градиентная обводка блока на CSS
Градиентная обводка блока на CSS

Как сделать красивую градиентную обводку блока на одном только CSS? Для этого мы сделаем следующее:

1. Создадим div.linear-gradient с градиентным фоном;

2. Создадим внутренний блок div с небольшим отступом.


Смена изображения ползунком - "До" и "После".
Смена изображения ползунком - "До" и "После".
Вам нужно показать на сайте различия между двумя изображениями? TwentyTwenty, визуальный инструмент для сравнения, позволяет легко это осуществить!

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

Цитаты на CSS и Blockquote. 11 примеров использования
Цитаты на CSS и Blockquote. 11 примеров использования

Первая статья в этом году будет полностью посвящена цитатам в тексте и такому замечательному CSS тегу, как <blockquote>, а также оформлению цитат с помощью CSS стилей.


QRcode

2010-2024 © Веб студия iNikSite.ru (г. Подольск). Все права сохранены.

Цены на сайте носят ознакомительный характер и не являются публичной офертой! Просим уточнять цены при отправке заявки в нашу компанию. У нас действуют специальные предложения и скидки на различные варианты исполнения заказа и 100% предоплату!

Мы используем файлы cookie. Они помогают улучшить ваше взаимодействие с сайтом.