Мне часто задают вопрос: можно ли создавать тени из градиентов вместо сплошных цветов? Не существует конкретного свойства CSS, которое делает это (поверьте мне, я смотрел), и любой пост в блоге, который вы найдете об этом, в основном представляет собой множество трюков CSS для аппроксимации градиента. На самом деле мы рассмотрим некоторые из них по ходу дела.
Вместе мы раздвинем границы, чтобы получить решение, которое охватывает то, чего я не видел больше нигде: прозрачность. Большинство трюков работают, если у элемента непрозрачный фон, но что, если у нас прозрачный фон? Мы рассмотрим этот случай здесь!
Прежде чем мы начнем, позвольте мне представить генератор градиентных теней. Все, что вам нужно сделать, это настроить конфигурацию и получить код. Но продолжайте, потому что я помогу вам понять всю логику сгенерированного кода.
Непрозрачное решение
Начнем с решения, которое сработает в 80% случаев. Самый типичный случай: вы используете элемент с фоном, и вам нужно добавить к нему градиентную тень. Здесь нет вопросов прозрачности.
Решение состоит в том, чтобы полагаться на псевдоэлемент, в котором определен градиент. Вы помещаете его за фактический элемент и применяете к нему фильтр размытия.
Это выглядит как много кода, и это потому, что это так. Вот как мы могли бы сделать это с помощью box-shadow, если бы использовали сплошной цвет вместо градиента.
Это должно дать вам хорошее представление о том, что делают значения в первом фрагменте. У нас есть смещения по осям X и Y, радиус размытия и расстояние распространения. Обратите внимание, что нам нужно отрицательное значение расстояния разброса, полученное из свойства inset.
Вот демонстрация, показывающая градиентную тень рядом с классической коробчатой тенью:
Если вы внимательно посмотрите, то заметите, что обе тени немного отличаются, особенно часть размытия. Это неудивительно, потому что я почти уверен, что алгоритм свойства filter работает иначе, чем алгоритм box-shadow. Это не имеет большого значения, так как результат, в конце концов, очень похож.
Это решение хорошее, но все же имеет несколько недостатков, связанных с объявлением z-index: -1. Да, там происходит «наложение контекста»!
Я применил преобразование к основному элементу, и бум! Тень больше не находится под элементом. Это не ошибка, а логический результат контекста стека. Не волнуйтесь, я не буду начинать скучное объяснение контекста стекирования (я уже делал это в ветке Stack Overflow), но все равно покажу вам, как это обойти.
Первое решение, которое я рекомендую, — использовать 3D-преобразование:
Вместо использования z-index:-1 мы будем использовать отрицательный сдвиг по оси Z. Мы поместим все в translate3d(). Не забудьте использовать transform-style: preserve-3d для основного элемента; в противном случае 3D-преобразование не вступит в силу.
Насколько мне известно, у этого решения нет побочных эффектов… но, возможно, вы их видите. Если это так, поделитесь им в разделе комментариев, и давайте попробуем найти решение!
Если по какой-то причине вы не можете использовать 3D transform, другое решение — полагаться на два псевдоэлемента — ::before и ::after. Один создает градиентную тень, а другой воспроизводит основной фон (и другие стили, которые могут вам понадобиться). Таким образом, мы можем легко контролировать порядок расположения обоих псевдоэлементов.
Пример Вы можете посмотреть на CodePen.
Важно отметить, что мы заставляем основной элемент создавать контекст стека, объявляя для него z-index: 0 или любое другое свойство, которое делает то же самое. Кроме того, не забывайте, что псевдоэлементы рассматривают поле заполнения основного элемента как ссылку. Итак, если основной элемент имеет рамку, вам необходимо принять это во внимание при определении стилей псевдоэлементов. Вы заметите, что я использую inset: -2px для ::after для учета границы, определенной для основного элемента.
Как я уже сказал, это решение, вероятно, достаточно хорошо в большинстве случаев, когда вам нужна градиентная тень, если вам не нужно поддерживать прозрачность. Но мы здесь, чтобы бросить вызов и раздвинуть границы, поэтому, даже если вам не нужно то, что будет дальше, оставайтесь со мной. Вы, вероятно, узнаете новые приемы CSS, которые сможете использовать в других местах.
Прозрачное решение
Давайте продолжим 3D-преобразование с того места, где остановились, и удалим фон с основного элемента. Я начну с тени, у которой и смещения, и расстояние распространения равны 0.
Идея состоит в том, чтобы найти способ вырезать или скрыть все внутри области элемента (внутри зеленой рамки), сохранив при этом то, что снаружи. Для этого мы будем использовать clip-path. Но вы можете задаться вопросом, как clip-path может сделать разрез внутри элемента?
На самом деле, сделать это невозможно, но мы можем смоделировать это, используя определенный шаблон полигона:
clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)
Тада! У нас есть градиентная тень, поддерживающая прозрачность. Все, что мы сделали, это добавили clip-path к предыдущему коду. Вот рисунок, иллюстрирующий полигональную часть.
Синяя область — это видимая часть после применения clip-path. Я использую синий цвет только для иллюстрации концепции, но на самом деле мы будем видеть только тень внутри этой области. Как видите, у нас есть четыре точки, определенные большим значением (B). Мое большое значение — 100vmax, но это может быть любое большое значение, которое вы хотите. Идея состоит в том, чтобы у нас было достаточно места для тени. У нас также есть четыре точки, которые являются углами псевдоэлемента.
Стрелки показывают путь, определяющий полигон. Мы начинаем с (-B, -B), пока не достигнем (0,0). Всего нам нужно 10 точек. Не восемь точек, потому что две точки повторяются дважды в пути ((-B,-B) и (0,0)).
Нам осталось сделать еще одну вещь, а именно учесть расстояние распространения и смещения. Единственная причина, по которой приведенная выше демонстрация работает, заключается в том, что это частный случай, когда смещения и расстояние распространения равны 0.
Давайте определим размах и посмотрим, что произойдет. Помните, что для этого мы используем inset с отрицательным значением:
Псевдоэлемент теперь больше основного элемента, поэтому clip-path обрезает больше, чем нам нужно. Помните, нам всегда нужно вырезать часть внутри основного элемента (область внутри зеленой рамки примера). Нам нужно настроить положение четырех точек внутри clip-path.
Мы определили переменную CSS --s для расстояния разброса и обновили точки многоугольника. Я не трогал точки, где я использую большое значение. Я обновляю только точки, определяющие углы псевдоэлемента. Я увеличиваю все нулевые значения на --s и уменьшаю значения 100% на --s.
Пример см. на CodePen.
Та же логика и со смещениями. Когда мы перемещаем псевдоэлемент, тень выходит из строя, и нам нужно снова выпрямить полигон и переместить точки в противоположном направлении.
Есть еще две переменные для смещений: --x и --y. Мы используем их внутри преобразования, а также обновляем значения clip-path. Точки полигона с большими значениями мы по-прежнему не трогаем, а все остальные смещаем — уменьшаем --x от координаты X, а --y от координаты Y.
Теперь все, что нам нужно сделать, это обновить несколько переменных для управления градиентной тенью. И пока мы этим занимаемся, давайте также сделаем радиус размытия переменной: см. код на CodePen.
Нужен ли нам трюк с 3D transform?
Все зависит от границы. Не забывайте, что ссылкой для псевдоэлемента является поле заполнения (padding), поэтому, если вы примените границу к основному элементу, у вас будет перекрытие. Вы либо сохраняете трюк с 3D transform, либо обновляете значение inset, чтобы учесть границу.
Вот предыдущая демонстрация с обновленным значением вставки вместо 3D transform.
Я бы сказал, что это более подходящий способ, потому что расстояние распространения будет более точным, так как оно начинается с рамки, а не с области заполнения. Но вам нужно будет настроить значение вставки в соответствии с границей основного элемента. Иногда граница элемента неизвестна, и приходится использовать предыдущее решение.
С более ранним непрозрачным решением, возможно, вы столкнетесь с проблемой контекста стека. А с прозрачным решением, возможно, вместо этого вы столкнетесь с проблемой границы. Теперь у вас есть варианты и способы обойти эти проблемы. Трюк с 3D transform — мое любимое решение, потому что оно устраняет все проблемы.
Продолжение статьи читайте в моей блоге через день.
Ссылка на полную оригинальную статью.