Это вопрос, который я слышу довольно часто: Можно ли создавать тени из градиентов вместо сплошных цветов? Нет конкретного свойства 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
к основному элементу, и бум! Тень больше не находится под элементом. Это не ошибка, а логический результат контекста стека. Не волнуйтесь, я не буду начинать скучное объяснение контекста стека (Я уже сделал это в ветке переполнения стека), но я все равно покажу вам, как это обойти.
Первое решение, которое я рекомендую, это использовать 3D transform
:
.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
не вступит в силу.
Насколько мне известно, у этого решения нет побочных эффектов… но, возможно, вы их видите. Если это так, поделитесь им в разделе комментариев, и давайте попробуем найти решение!
Если по какой-то причине вы не можете использовать 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
on ::after
для учета границы, определенной на основном элементе.
Как я уже сказал, это решение, вероятно, достаточно хорошо в большинстве случаев, когда вам нужна градиентная тень, если вам не нужно поддерживать прозрачность. Но мы здесь, чтобы бросить вызов и раздвинуть границы, поэтому, даже если вам не нужно то, что будет дальше, оставайтесь со мной. Вы, вероятно, узнаете новые приемы CSS, которые сможете использовать в других местах.
Прозрачное решение
Давайте продолжим с того места, на котором остановились в 3D. transform
и удалите фон с основного элемента. Я начну с тени, которая имеет как смещения, так и расстояние распространения, равное 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
.
.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
обманывать?
Все зависит от границы. Не забывайте, что ссылкой для псевдоэлемента является поле заполнения, поэтому, если вы примените границу к основному элементу, у вас будет перекрытие. Вы либо сохраняете 3D transform
обмануть или обновить 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);
}
Онлайн-генератор учитывает только однородный радиус для простоты, но теперь вы знаете, как изменить код, если хотите учитывать сложную конфигурацию радиуса.
Подведение итогов
Мы дошли до конца! Магия градиентных теней больше не является загадкой. Я попытался охватить все возможности и возможные проблемы, с которыми вы можете столкнуться. Если я что-то упустил или вы обнаружите какие-либо проблемы, пожалуйста, не стесняйтесь сообщить об этом в разделе комментариев, и я проверю это.
Опять же, многое из этого, вероятно, излишне, учитывая, что решение де-факто покроет большинство ваших вариантов использования. Тем не менее, полезно знать «почему» и «как» этот трюк и как преодолеть его ограничения. Кроме того, мы получили хорошее упражнение, играя с отсечением и маскированием CSS.
И, конечно же, у вас есть онлайн-генератор вы можете связаться в любое время, когда вы хотите избежать хлопот.
- SEO-контент и PR-распределение. Получите усиление сегодня.
- Платоблокчейн. Интеллект метавселенной Web3. Расширение знаний. Доступ здесь.
- Источник: https://css-tricks.com/different-ways-to-get-css-gradient-shadows/
- 1
- 10
- 11
- 3d
- 7
- 9
- 98
- a
- в состоянии
- О нас
- об этом
- выше
- Absolute
- По
- Учетная запись
- Учетные записи
- точный
- на самом деле
- дополнительный
- После
- алгоритм
- Все
- позволяет
- уже
- всегда
- Анна
- и
- Другой
- откуда угодно
- прикладной
- Применить
- Применение
- ПЛОЩАДЬ
- около
- гайд
- фон
- в основном
- , так как:
- до
- за
- верить
- ниже
- большой
- больший
- Немного
- Черный
- Блог
- Синии
- пятно
- граница
- Сверление
- Коробка
- Ломать
- Ошибка
- вычислять
- расчеты
- не могу
- случаев
- случаев
- вызов
- проверка
- классический
- клип-путь
- тесно
- код
- цвет
- сочетание
- приход
- комментарий
- Общий
- полный
- комплекс
- сама концепция
- Конфигурация
- конфликт
- Рассматривать
- принимая во внимание
- считает
- содержание
- контекст
- контроль
- Corner
- углов
- может
- "Курс"
- чехол для варгана
- Обложки
- Создайте
- создает
- CSS
- CSS хитрости
- изготовленный на заказ
- Порез
- сокращение
- сделка
- занимавшийся
- решенный
- снижение
- По умолчанию
- определенный
- Определяет
- определяющий
- определенно
- зависит
- DID
- разница
- различный
- направление
- обнаружить
- расстояние
- не
- дело
- Dont
- недостатки
- каждый
- Ранее
- легко
- эффект
- или
- в другом месте
- достаточно
- обеспечивать
- особенно
- Даже
- многое
- пример
- Примеры
- исключенный
- Упражнение
- ожидаемый
- объяснение
- Больше
- Исследование
- и, что лучший способ
- дополнительно
- Face
- достаточно
- Избранное
- Особенность
- несколько
- фигура
- фильтр
- Найдите
- обнаружение
- First
- фиксированный
- фиксированной
- следовать
- Форс-мажор
- Бесплатно
- от
- Общие
- генерируется
- получить
- Дайте
- Go
- будет
- хорошо
- градиенты
- Зелёная
- происходит
- помощь
- здесь
- Спрятать
- Как
- How To
- HTML
- HTTPS
- БОЛЬНОЙ
- идея
- важную
- in
- Увеличение
- вместо
- вводить
- приглашать
- вопрос
- вопросы
- IT
- Сохранить
- хранение
- Знать
- Фамилия
- слоев
- УЧИТЬСЯ
- Вероятно
- недостатки
- рамки
- мало
- логический
- Длинное
- дольше
- посмотреть
- смотрел
- ВЗГЛЯДЫ
- серия
- магия
- Главная
- поддерживать
- Большинство
- сделать
- маска
- означает
- метод
- может быть
- изменять
- момент
- БОЛЕЕ
- самых
- двигаться
- перемещение
- Mozilla
- Тайна
- Необходимость
- отрицательный
- Тем не менее
- Новые
- следующий
- смещение
- ONE
- онлайн
- противоположность
- оптимальный
- Опции
- Апельсин
- заказ
- Другое
- Другое
- в противном случае
- контур
- внешнюю
- Преодолеть
- параметры
- часть
- особый
- путь
- шаблон
- ИДЕАЛЬНОЕ
- возможно
- выбирать
- Часть
- Платон
- Платон Интеллектуальные данные
- ПлатонДанные
- игры
- пожалуйста
- плюс
- пунктов
- Polygon
- должность
- возможности,
- возможное
- После
- потенциал
- довольно
- предыдущий
- вероятно
- собственность
- Push
- положил
- вопрос
- достигать
- достиг
- Читать
- Реальность
- причина
- рекомендовать
- Red
- уменьшить
- Связанный
- относительно
- помнить
- удаление
- повторный
- отчету
- результат
- показывать
- дорога
- Правило
- Сказал
- Сакэ
- то же
- Раздел
- Shadow
- Поделиться
- должен
- показывать
- сторона
- аналогичный
- просто
- простота
- с
- небольшой
- So
- твердый
- Решение
- некоторые
- удалось
- где-то
- Space
- конкретный
- распространение
- стек
- штабелирования
- Начало
- и политические лидеры
- начинается
- оставаться
- Шаг
- По-прежнему
- История
- подходящее
- поддержка
- Поддержка
- сюрприз
- взять
- целевое
- Сложность задачи
- Ассоциация
- Местоположение
- задача
- вещи
- в
- вместе
- слишком
- тема
- Всего
- трогать
- Transform
- переведите
- Переводы
- Прозрачность
- прозрачный
- правда
- типичный
- понимать
- Обновление ПО
- обновление
- us
- использование
- ценностное
- Наши ценности
- видимый
- способы
- Что
- Что такое
- который
- в то время как
- будете
- без
- Работа
- работает
- работает
- стоимость
- X
- Ты
- ВАШЕ
- Z-индекс
- зефирнет
- нуль