Свойство background-clip и как его применять

Свойство background-clip и как его применять

От автора: свойство background-clip – одно из тех свойств, которые я уже знаю на протяжении нескольких лет, но так толком и не использовал. Может пару раз оно мне пригодилось, как часть решения, в вопросе на сайте Stack Overflow. Я его не использовал вплоть до этого года, пока не начал создавать мою огромную коллекцию слайдеров.

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

Сначала давайте разберемся, что же такое это свойство background-clip и что оно делает. На рисунке ниже изображен квадрат.

Значение padding равно 0, а padding-box совпадает со свойством content-box. Content limit и padding limit также равны.

Если свойство border-width: 0, то border-box совпадает с padding-box, а padding limit совпадает с border limit.

Если и padding и border-width равны 0, то все три content-box, padding-box и border-box одинаковы, также будут равны content limit, padding limit и border limit.

По умолчанию фон полностью перекрывает свойство border-box (он заходит под рамку), но background-position (и background-size в процентах) относится к padding-box.

Чтобы лучше понять, о чем идет речь, давайте разберем пример. У нас есть блок со случайными размерами. Зададим ему простой градиент background-size: 50% 50% и прерывистый border (border-image) таким образом, чтобы мы могли видеть, что находится под рамкой:

В этом демо видно, что градиент перекрывает весь border-box (градиент виден под рамкой). Свойство background-position мы не задавали, т.е. оно по умолчанию 0 0. Видно, что позиционирование относится к padding-box, так как начинается из верхнего левого угла (0 0) блока. Также background-size задан в процентах и относится к padding-box.

По умолчанию фон перекрывает border-box полностью, но начинается в левом верхнем углу padding-box

Когда мы задаем background-size для градиента (а не для изображений) нам нужно два значения для разных браузеров. Если использовать только одно, то Firefox второе установит в 100% (по спецификации), а все остальные браузеры будут неправильно приравнивать второе значение к первому. Отсутствующее значение background-size устанавливается в auto, а так как у градиента собственных размеров нет, то значение auto не сможет вычислиться. Значит его нужно задать в 100%. То есть, если мы не хотим, чтобы оба значения background-size были 100%, то следует использовать оба значения.

Используя одно значение для background-size не обеспечит кроссбраузерности (тест). Слева: Firefox (по спецификации принимает второе значение за 100%). Справа: Chrome/ Opera, Safari, IE/ Edge (неправильно задают второе значение равным первому).

С помощью свойства background-clip можно сделать так, чтобы фон закрывал только padding-box или content-box. Свойство отсекает и не показывает ту часть, которая выходит за область кадрирования, где область кадрирования это область в пунктирной рамке на рисунке ниже.

По умолчанию background-clip: border-box область кадрирования это border-box. Т.е. мы видим фон под рамкой.

Если задать background-clip: padding-box, областью кадрирования станет padding-box. Это значит, что фон будет показываться только в padding-box (не будет виден под рамкой).

И если задать background-clip: content-box, то область кадрирования будет content-box. Фон будет виден только внутри области content-box.

В демо ниже проиллюстрированы три ситуации:

Также есть еще одно свойство background-origin, которое задает к какой из трех областей будет относиться background-position (и background-size в %).

Рассмотрим точно такую же конструкцию с прерывистой рамкой border, но в этот раз с видимым padding’ом. На фон мы поставим изображение и градиент. И изображению и градиенту задан background-size: 50% без повторений. Также у изображения задан background-position: 100% 100% (а градиент по умолчанию 0 0):

В демо ниже показаны случаи для каждого из трех возможных значений свойства background-origin — border-box, padding-box и content-box:

Значение 100% 100% свойства background-position для изображения соответствуют значениям 100% 100% для блока со свойством background-origin. Но в то же время значение background-size: 50% 50% это половина ширины и высоты блока со свойством background-origin.

В сокращенном свойстве background можно задать значения свойства background-origin и background-clip именно в таком порядке в конце слоя. Так как данные свойства принимают значения от блока, то если у блока указано только одно значение, то оба свойства также будут установлены в это значение. Если блоку заданы оба значения, то первое это background-origin, а второе background-clip. Если значения у блока не заданы, то будут взяты значения по умолчанию (padding-box для background-origin и border-box для background-clip). Отлично, теперь разберемся, как это можно использовать!

Прозрачный промежуток между рамкой и фоном

Кто-то, может быть, еще помнит, что с помощью свойства background-clip можно получить полупрозрачную рамку. Но мы также можем добавить дополнительное место между рамкой и областью с фоном без введения дополнительных элементов. Самый простой способ это в дополнении к border добавить еще и padding и установить background-clip: content-box. Если задавать все это через сокращенное свойство, то необходимо также задать background-origin: content-box. В нашем случае так делать можно, и не будет никаких нежелательных эффектов.

Кадрирование фона в области content-box означает, что фон не будет вылезать за пределы данной области. За пределами данной области фон не будет виден, и нам откроется вид на то, что находится под нашим блоком. Если добавить border, то покажется рамка между padding limit и border limit. Но если padding не равен 0, то нам все еще будет видна прозрачная область между content limit и padding limit.

Подсветка border’а, padding’а и области контента через панель разработчика. Можно потестить вживую:

Можно сделать чуть-чуть поинтереснее, добавив фильтр drop-shadow(), который придаст желтоватого свечения:

Префиксы: Я встречал множество ресурсов, которые добавляют вендорные префиксы –moz- и –ms- к данному CSS фильтру. Пожалуйста, не делайте этого! В Firefox у CSS фильтров убрали префиксы с первым же релизом (Firefox 34, осенью 2014 г.). Сегодня в Edge фильтры также употребляются без префиксов. Т.е. CSS фильтрам никогда не были нужны префиксы –moz- и –ms-, добавлять их бесполезно. Они только засоряют стили.

Если задать градиент для background-image и border-image, то получится крутой эффект. Мы сделаем градиент, который начинается с ярко оранжевого/красного сверху и постепенно угасает до полной прозрачности. Так как у нас разные только тени, а градиенты одинаковые, можно создать Sass функцию.

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

Проблема в том, что текст начинается прямо от края блока с оранжевым фоном и мы не можем задать padding, так как он уже у нас есть, и он прозрачный. Можно добавить еще один элемент, или же использовать box-shadow!

Свойство box-shadow принимает 2, 3 или 4 значения. Первое это сдвиг по оси Х (задает насколько сдвигается тень вправо), второе – сдвиг по оси У (сдвиг вниз), третье – радиус размытия (как сильно будет размыт край тени) и четвертое значение – радиус разброса (разброс тени во всех направлениях). В демо ниже можно поиграться со значениями – кликните на любое из них, и появится слайдер.

Радиус размытия может принимать значения равные или выше нуля, а сдвиги и радиус распространения могут принимать отрицательные значения. Отрицательные значения сдвигают тень в противоположную сторону по оси (влево или вверх). Отрицательное значение для радиуса разброса означает, что тень не расширяется, а сжимается.

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

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

Возвращаясь к нашему демо, мы используем box-shadow для имитации рамки, а настоящую рамку используем для создания прозрачной области. Для этого необходимо задать background-clip: padding-box, а padding сделает все остальное:

Также имитировать рамку можно с помощью внутренней тени inset. В таком случае тень начинается от padding limit (область между padding и border) и направлена внутрь:

Так как у нас несколько теней, мы можем эмулировать несколько рамок. Давайте разберем пример с двумя тенями, одна inset, другая обычная. Если настоящий border не равен нулю и прозрачный, а background-clip: padding-box, то мы получаем имитацию двойной рамки с прозрачной областью (настоящая рамка) между внешней и внутренней рамками. Обратите внимание, что необходимо увеличить padding для компенсации расстояния, занимаемого внутренним box-shadow.

Можно протестировать вживую. Мы добавили drop-shadow() фильтр для свечения:

Мишень из одного элемента с гладкими краями (без псевдоклассов)

Скажем, мы хотим создать мишень, как на картинке ниже. Одно ограничение – мы можем использовать только один элемент без псевдоклассов.

Первым приходит в голову использовать repeating-radial-gradient. Структура мишени примерно такая:

Половина мишени занимает 9 единиц, тогда горизонтальный и вертикальный размеры будут 18 единиц. Первый круговой градиент черный и занимает первую единицу, потом идет прозрачная область вплоть до третьей единицы, потом опять черная окружность и прозрачная область… вроде бы значения повторяются. Все кроме первого от 0 до 1. Сначала у нас черная область, потом снова черная, но уже занимающая расстояние с 3 до 5 единицы – две единицы! Т.е… мы не можем начать с 0, необходимо начать с -1, так ведь? Ну по спецификации это должно работать.

Первая проблема в том, что IE думает, что это должно отображаться по-другому.

К нашему счастью в Edge это пофиксили. Но если вам нужно поддерживать IE, то проблема остается. Данную проблему можно решить простым радиальным градиентом, тем более нам не нужно так много окружностей. Кода будет больше, но это не так плохо…

Теперь круги одинаковые во всех браузерах, но все еще есть одна проблема: в IE/Edge края могут быть не такими гладкими, но в Firefox и Chrome они смотрятся еще хуже!

Оригинал (слева сверху), IE/Edge (справа сверху), Firefox (снизу слева) и Chrome (снизу справа).

Можно воспользоваться non-sharp transition trick:

Данный способ улучшает результат в IE (где уже все хорошо) и Firefox, но в Chrome края все еще ужасные.

Оригинал (слева сверху), IE/Edge (справа сверху), Firefox (снизу слева) и Chrome (снизу справа)

Может быть, радиальные градиенты не самое лучшее решение. А что если бы нам пришлось адаптировать способ с background-clip и box-shadow из предыдущего примера под эту проблему? Можно использовать внешнюю тень box-shadow для внешнего круга, а inset тень для внутреннего круга. Между кругами прозрачная рамка. Также необходимо задать background-clip: content-box и задать соответствующий padding, чтобы между центральным кругом и первым черным кругом была прозрачная область.

Ползунки как в реальных примерах

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

Для дорожек есть webkit-slider-runnable-track, -moz-range-track и -ms-track. Для ползунков есть -webkit-slider-thumb, -moz-range-thumb и -ms-thumb. А для прогрессбаров и тега fill есть -moz-range-progress, -ms-fill-lower (для левой части) и -ms-fill-upper (и для правой части). В Webkit браузерах нет специальных псевдоклассов для стилизации частей ползунка отдельно.

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

Необходимо писать так:

Данные подход похож на стиль написания кода WET. Во многих случаях это он и есть – хотя, учитывая многочисленные несоответствия браузеров с ползунками, лучше выравнивать код по отдельным строкам. Я решил использовать миксин thumb() и передавать ему аргументы. Что-то вроде этого:

Давайте вернемся к стилизации. Мы можем добавить псевдоклассы к этим компонентам только в Chrome/Opera, а значит, что при воссоздании их внешнего вида мы не должны полагаться на псевдоклассы. Т.е. фон, рамка, тени и фильтры будут выставляться на самом ползунке. Рассмотрим пару примеров!

Мягкий пластиковый ползунок

Визуально пример будет выглядеть примерно так:

Первое приходит в голову градиент для свойства background и градиент для border-image, затем задать тень и все.

И этот метод работает (вместо ползунка для простоты можно использовать button):

Только наш ползунок не квадратный, а круглый. Для этого необходимо просто задать radius: 50%, так ведь? Ну… это не сработает, мы ведь используем border-image, поэтому border-radius игнорируется. Довольно забавно, но радиус применяется к box-shadow.

И что же делать? Использовать background-clip! Сперва зададим элементу нулевой padding, уберем рамку и сделаем ползунок круглым border-radius: 50%. Затем накладываем два градиента, первый для content-box (задается через сокращенное свойство). И в конце добавляем две тени: одна темная под ползунком, а вторая расположена внутри и затемняет немного нижнюю и внешние границы.

Матовый ползунок

Что-то похожее на картинку ниже:

Выглядит очень похоже на предыдущий пример, только теперь сверху у нас светлая линия, а внутри тень. Ползунок не будет круглым, если мы установим внешнюю светлую линию. Для этого необходимо компенсировать высоту элемента, уменьшить ее. А значит, нам нужно провести больше вычислений, чтобы определить позицию внутренней части. Если использовать inset для светлой линии, то потом мы не сможем его использовать для добавления внутренней тени. Но это можно имитировать с помощью radial-gradient, который хорошо подогнан по размеру, позиционированию к content-box. Т.е. стратегия у нас точно такая же, как и в предыдущем примере, только тут есть дополнительный слой radial-gradient сверху других фонов. Настоящий background-size радиального градиента больше чем content-box, т.е. мы можем сдвинуть его вниз, не доводя его верхнюю границу до границы контента.

3D ползунок

К примеру, что-то похожее на это:

Такой ползунок уже сложнее предыдущих. Необходимо будет задать разные content-box, padding-box и border-box таким образом, чтобы мы смогли наложить фон и использовать background-clip.

Основной части слайдера задается фон градиент, который кадрируется по области content-box, а под ним еще один фон кадрируется по области padding-box. Оба градиента сверху третьего linear-gradient, который обрезается по border-box. Также используется внутренняя тень box-shadow для подсветки области padding limit (между padding и border):

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

Можно приблизить наш результат еще ближе к оригиналу с помощью теней, еще одного радиального градиента или с помощью background-blend-mode. Однако я не вижу в этом смысла.

Добиться такого эффекта можно с помощью псевдоклассов – сперва необходимо спозиционировать и задать размер свойству, сделать элемент круглым border-radius: 50%. Затем задаем padding, убираем рамку и добавляем два градиента для свойства background. Верхний градиент радиальный и обрезается по области content-box:

Для создания этого ползунка я использовал радиальный градиент сверху, а для Blink браузеров добавил псевдоклассы сверху градиентов. Все потому что в Safari стили применяются через ::-webkit-slider-thumb, и Safari не поддерживает псевдоклассы на ползунке (трек). Т.е. если бы я удалили фолбэк стилей ::-webkit-slider-thumb, то Safari не отобразил бы круг вообще.

Иллюзия глубины

Идея показана на картинке ниже:

Так же как и раньше мы задаем элементу ненулевой прозрачный border, padding и накладываем фоны с разными background-clip значениями (помните, что content-box должен быть сверху border-box). В этом примере у нас linear-gradient закрывает border. Темный градиент и пара радиальных меньшего размера без повторений для затемнения краев и области padding’а, и последний полностью черный для content области. Затем задается border-radius, равный как минимум половине высоты контентной области плюс два паддинга и две рамки. Также добавляется внутренняя тень box-shadow для подчеркивания области padding limit.

Но есть одна проблема, которую можно увидеть ниже:

Из-за принципа работы border-radius – для области контента мы вычли border-width и padding, что привело к тому, что значение радиуса стало отрицательным. Т.е. нам нечего скруглять в области контента. Но это можно исправить! Можно имитировать нужную нам форму с помощью линейного и радиального градиентов в области контента.

Сперва необходимо убедиться, что ширина области контента кратна высоте (в нашем примере 15.75em = 9*1.75em). Первый слой это our case, 15.75em = 9*1.75em). We first layer a non-repeating linear-gradient внутри элемента. Градиент занимает всю высоту, а по краям оставляет область, равную половине высоты контентной области. Сверху этого градиента мы добавляем radial-gradient со свойством background-size, значения равны высоте контентной области.

Металлический ползунок

Что-то вроде кнопки ниже:

Данный пример немного сложнее, так что разобьем его на части. Сперва мы делаем круглую кнопку с одинаковыми width и height и border-radius: 50%. Затем задаем box-sizing: border-box так, чтобы border-width и padding были направлены внутрь. Следующим шагом будет задать прозрачную рамку и паддинг. На данный момент мы имеем следующее:

Пока что это еще ни на что не похоже, так как мы использовали всего два свойства: box-shadow и background.

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

Начиная с внешней части и идя внутрь:

Широкое внешнее кольцо с лампочками

Тонкое внутреннее кольцо

Перфорированная область (она включает в себя также голубое свечение от внутреннего диска)

Большая центральная часть

Широкое внешнее кольцо это рамка, центральная часть – область контента, а все остальное между ними (тонкое кольцо и область с точками) это паддинг. Тонкое внутреннее кольцо можно создать с помощью внутренней тени:

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

Все еще не похоже, но уже что-то:

Теперь необходимо наложить три типа фонов сверху вниз: только для content-box (центральная область), только для padding-box (область с точками и голубым свечением) и только для зоны border-box (широкое внешнее кольцо и лампочки).

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

Вот теперь немного похоже на результат!

Переходим к области с точками. Голубое свечение это простой радиальный градиент с прозрачной внешней частью. Точки основаны на шаблоне Carbon fibre из галереи Lea Verou, которую она собрала около 5 лет назад. До сих пор полезная вещь, особенно для артистичных людей, как я.

Мы уже ближе к оригиналу:

Широкое внешнее кольцо без лампочек создается при помощи конического градиента:

И у нас почти готовый металлический ползунок!

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

Зададим $k-ое количество лампочек. Для верхнего градиента мы используем голубой цвет, а после – серый. У нас 24 лампочки, размещенных на окружности посередине области рамки. Т.е. радиус лампочки равен радиус ползунка минус половина ширины рамки.

Все градиенты мы будем генерировать через Sass. Сперва создадим пустой список градиентов, затем в цикле к списку будет добавляться по два градиента. Расположение градиентов вычисляется так, что на следующей итерации мы находимся на предыдущем круге. Первый градиент зависит от индекса в цикле, а второй всегда такой же (меняется только позиция на окружности).

Тени в перпендикулярных плоскостях

Рассмотрим пример ползунка, когда он размещен вертикально, а его тень лежит под ним в горизонтальной плоскости. Что-то похожее на:

Нам необходимо воссоздать данный эффект, используя только один элемент без псевдоклассов. В этом случае хорошо работает размещение различных фонов с разными значениями background-clip и background-origin. Создаем кнопку с двумя фонами: один сверху и обрезается по области content-box, второй под ним и обрезается по области padding-box. Для тени используются radial-gradient(), background-clip и background-origin: border-box. Базовые стили схожы с предыдущим примером:

Задаем толстую прозрачную рамку так, чтобы нам хватило места для тени снизу. Делаем это для всех теней, не только нижней, так как для области padding-box нам нужно одинаковое скругление углов (если забыли, как это работает, посмотрите видео по ссылке border-radius).

Первый background сверху это conic-gradient(), и он создает конический металлический блеск. Он обрезается по области content-box. Следом под ним идет обычный линейный градиент, который обрезается по padding-box. Мы используем три внутренних тени, чтобы сделать второй фон менее плоским – добавляем еще одну тень по кругу с нулевым размытием, положительным значением разброса, делаем ее ярче сверху с помощью полупрозрачной белой тени, а снизу – темнее с помощью полупрозрачной черной тени.

Для тени добавляем третий фон, которому выставляем background-clip и background-origin в border-box. Этот фон не повторяет radial-gradient(), который мы прикрепили к низу (а по горизонтали по центру) и сжали по вертикали так, чтобы он вписался в нижнюю рамку и даже оставлял немного места. Радиальная тень у нас занимает около .75 от border-width.

Ну вот и все! Можете поиграться с готовыми кнопками:

У свойства background-clip совершенно точно есть свои способы применения! В частности, когда необходимо использовать несколько слоев по краям элемента.

Автор: Ana Tudor

Источник: //css-tricks.com/

Редакция: Команда webformyself.

Метки:

Похожие статьи:

Комментарии Вконтакте: