Использование процентных значений с background-position на линейном градиенте
Есть ли способ сделать background-position
принимать значения в процентах? В настоящее время моя кнопка работает только с явными значениями для width
а также background-position
,
body {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.button {
display: flex;
justify-content: center;
align-items: center;
text-decoration: none;
color: white;
font-weight: bold;
width: 350px;
height: 50px;
border: 1px solid green;
transition: background 0.5s;
background-repeat: no-repeat;
background-image: linear-gradient(to left, #2484c6, #1995c8 51%, #00bbce), linear-gradient(to right, #2484c6 0%, #1995c8 51%, #00bbce 76%);
}
.button-pixel {
background-position: -350px 0px, 0px 0px;
}
.button-pixel:hover {
background-position: 0px 0px, 350px 0px;
}
.button-percentage {
background-position: -100% 0px, 0px 0px;
}
.button-percentage:hover {
background-position: 0% 0px, 100% 0px;
}
<a href="#" class="button button-pixel">In Pixel</a>
<a href="#" class="button button-percentage">In Percentage</a>
2 ответа
TL;DR
Все процентные значения, используемые с background-position
эквивалентны при использовании градиента в качестве фона, поэтому вы не увидите никакой разницы. Вам необходимо указать background-size
отличается от размера контейнера:
body {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.button {
display: flex;
justify-content: center;
align-items: center;
text-decoration: none;
color: white;
font-weight: bold;
width: 350px;
height: 50px;
border: 1px solid green;
transition: background 0.5s;
background-repeat: no-repeat;
background-image: linear-gradient(to left, #2484c6, #1995c8 51%, #00bbce), linear-gradient(to right, #2484c6 0%, #1995c8 51%, #00bbce 76%);
background-size:200% 100%; /*Changed this*/
}
.button-pixel {
background-position: -350px 0px, 0px 0px;
}
.button-pixel:hover {
background-position: 0px 0px, 350px 0px;
}
.button-percentage {
background-position: 100% 0px, 0px 0px;
}
.button-percentage:hover {
background-position: 0% 0px, 100% 0px;
}
<a href="#" class="button button-pixel">In Pixel</a>
<a href="#" class="button button-percentage">In Percentage</a>
Как работает фоновая позиция?
Давайте использовать классическое изображение, чтобы объяснить, как background-position
работает.
При использовании значений пикселей, ссылка - это верхний / левый угол изображения независимо от его размера. Это как использовать top
/ left
с позиционированным элементом:
.box {
width:200px;
height:200px;
background-image:url(https://picsum.photos/100/100?image=1069);
border:1px solid;
background-position:0 0;
background-repeat:no-repeat;
position:relative;
animation:back 5s infinite linear alternate;
}
.box:before {
content:"";
position:absolute;
top:0;
left:0;
width:10px;
height:10px;
background:red;
z-index:1;
animation:change 5s infinite linear alternate;
}
@keyframes back{
to{background-position:200px 200px;}
}
@keyframes change{
to{top:200px; left:200px;}
}
<div class="box">
</div>
При использовании процентных значений ссылка отличается от использования пиксельных значений; это больше не верхний / левый угол:
.box {
width:200px;
height:200px;
background-image:url(https://picsum.photos/100/100?image=1069);
border:1px solid;
background-position:30% 30%;
background-repeat:no-repeat;
position:relative;
animation:back 5s infinite linear alternate;
}
.box:before {
content:"";
position:absolute;
top:0;
left:0;
width:10px;
height:10px;
background:red;
z-index:1;
animation:change 5s infinite linear alternate;
}
@keyframes back{
to{background-position:100% 100%;}
}
@keyframes change{
to{top:200px; left:200px;}
}
<div class="box">
</div>
В этом случае нам нужно учесть два параметра: размер контейнера и размер изображения. Вот иллюстрация того, как это работает (я взял background-position
равно 30% 30%
):
Сначала мы рассмотрим изображение, чтобы найти контрольную точку, которую мы будем использовать для размещения изображения. Это точка внутри изображения, которая расположена на 30% 30%
из верхнего / левого угла, учитывая размер изображения (как определено зелеными линиями). Затем мы помещаем эту точку внутри контейнера в 30% 30%
из верхнего / левого угла с учетом размера контейнера.
Исходя из этой логики, мы можем четко определить тривиальные случаи, такие как 50% 50%
(центр), 100% 100%
(Нижний правый), 100% 0%
(вверху справа) и т.д.:
Теперь ясно, что если размер изображения равен размеру контейнера, то ничего не произойдет просто потому, что точка, которую мы рассчитали для размещения изображения, останется на своем первоначальном месте, таким образом, изображение не будет двигаться, потому что все позиции эквивалентны.
.box {
width:200px;
height:200px;
background-image:url(https://picsum.photos/200/200?image=1069);
border:1px solid;
background-position:0% 0%;
background-repeat:no-repeat;
position:relative;
animation:back 5s infinite linear alternate;
}
.box:before {
content:"";
position:absolute;
top:0;
left:0;
width:10px;
height:10px;
background:red;
z-index:1;
animation:change 5s infinite linear alternate;
}
@keyframes back{
to{background-position:100% 100%;}
}
@keyframes change{
to{top:calc(100% - 5px); left:calc(100% - 5px);}
}
<div class="box">
</div>
Приведенная выше логика одинакова при применении к градиентам, поскольку градиенты считаются изображениями, и по умолчанию, если вы не укажете background-size
Размер градиента будет размером его контейнера, в отличие от использования изображения.
Если мы ссылаемся на спецификацию background-size
Мы можем видеть, как возникает ваша проблема:
Замечания:
Если оба значения являются автоматическими, то должны использоваться собственная ширина и / или высота изображения, если таковые имеются, отсутствующий размер (если есть), ведущий себя как автоматический, как описано выше. Если изображение не имеет ни собственной ширины, ни внутренней высоты, его размер определяется как для содержимого.
А также:
содержать
Масштабируйте изображение, сохраняя его внутреннее соотношение сторон (если оно есть), до максимального размера, чтобы его ширина и высота могли поместиться в области позиционирования фона.
А также:
Растровое изображение (например, JPG) всегда имеет внутренние размеры и пропорции.
CSS
<gradient>
У них нет внутренних размеров или внутренних пропорций. ссылка
Изображение всегда имеет внутренние значения, поэтому в большинстве случаев оно не будет иметь тот же размер, что и его контейнер, поэтому background-position
с процентными единицами будет иметь эффект. Но градиенты не имеют внутренних значений, поэтому размеры градиента будут равны размеру его контейнера, и background-position
с процентными значениями никогда не будет работать, если мы не укажем background-size
отличается от размеров его контейнера.
Более подробно
Мы видели в приведенных выше примерах, как background-size
работает при использовании значений между 0%
а также 100%
, но как насчет использования отрицательных значений или значения больше, чем 100%
? Логика та же, но найти контрольную точку будет сложнее.
Отрицательные значения (< 0%)
Давайте предположим, что мы хотим разместить фон в -50% 0
, В этом случае контрольная точка будет находиться за пределами изображения. Вот пример:
.box {
width:200px;
height:200px;
border:1px solid;
background:url(https://picsum.photos/100/100?image=1069) -50% 0/100px 100px no-repeat;
}
<div class="box"></div>
Как мы можем видеть на иллюстрации, мы сначала рассмотрим -50%
изображения, которое -50px
, чтобы определить нашу контрольную точку (то есть, -50px от левого края изображения). Затем мы помещаем эту точку в -50%
учитывая размер контейнера (-100px от левого края контейнера). Затем мы рисуем изображение и получаем приведенный выше результат. Только 100px
изображения видно.
Мы также можем заметить, что отрицательные процентные значения будут вести себя так же, как отрицательные фиксированные значения, когда размер изображения меньше размера контейнера (оба сместят изображение влево). В этом случае -50% 0
такой же как -50px 0
,
.box {
width:200px;
height:200px;
border:1px solid;
background:url(https://picsum.photos/100/100?image=1069) -50% 0/100px 100px no-repeat;
}
.alt{
background:url(https://picsum.photos/100/100?image=1069) -50px 0/100px 100px no-repeat;
}
<div class="box">
</div>
<div class="box alt">
</div>
Если, например, мы увеличиваем размер изображения до 150px 150px
, -50% 0
будет так же, как -25px 0
,
Когда мы сделаем размер больше контейнера, отрицательные значения начнут смещать изображение вправо (как с положительными значениями пикселей), что логично, так как 50%
изображения будет увеличиваться в то время как 50%
контейнера останется прежним.
Если мы рассмотрим предыдущую иллюстрацию, это похоже на увеличение верхней зеленой линии, пока она не станет больше нижней. Так что одного знака недостаточно, чтобы знать, как будет смещено фоновое изображение; нам нужно также учитывать размер.
.box {
width:200px;
height:200px;
border:1px solid;
background:url(https://picsum.photos/300/300?image=1069) -50% 0/150px 150px no-repeat;
animation:change 2s linear infinite alternate;
}
@keyframes change{
from {background-size:50px 50px}
to {background-size:300px 300px}
}
<div class="box">
</div>
Большие значения (> 100%)
Та же логика, что и раньше: если мы определим фон в 150% 0
, тогда мы рассмотрим нашу точку отсчета 150%
от левого края (или 50%
с правого края), тогда мы размещаем его 150%
от левого края контейнера.
.box {
width:200px;
height:200px;
border:1px solid;
background:url(https://picsum.photos/100/100?image=1069) 150% 0/100px 100px no-repeat;
}
<div class="box">
</div>
В этом случае, 150% 0
эквивалентно 150px 0
и если мы начнем увеличивать размер фона, мы будем вести себя так же, как было продемонстрировано ранее:
.box {
width:200px;
height:200px;
border:1px solid;
background:url(https://picsum.photos/300/300?image=1069) 150% 0/100px 100px no-repeat;
animation:change 2s infinite linear alternate;
}
@keyframes change {
from {background-size:50px 50px}
to {background-size:300px 300px}
}
<div class="box">
</div>
Особые случаи
Использование значений вне диапазона [0% 100%]
позволяет нам скрыть фоновое изображение, но как нам найти точные значения, чтобы полностью скрыть изображение?
Давайте рассмотрим следующую иллюстрацию:
Наше изображение имеет ширину Ws
а контейнер шириной Wp
и нам нужно найти значение p
, Из рисунка можно получить следующую формулу:
p * Wp = p * Ws + Ws --> p = Ws/(Wp - Ws)
Если размер контейнера 200px
и изображение 100px
затем p
является 1
так 100%
(мы добавляем, конечно, отрицательный знак, и это -100%
).
Мы можем сделать это более общим, если мы рассмотрим процентные значения с background-size
вместо фиксированных значений. Предположим, background-size
это с%. Тогда у нас будет Ws = Wp * s
и формула будет
p = Ws/(Wp - Ws) --> p = s / (1 - s)
Добавление отрицательного знака будет p = s / (s - 1)
,
Теперь, если мы хотим скрыть изображение справа, мы делаем ту же логику справа (мы рассматриваем зеркало предыдущей иллюстрации), но так как мы всегда будем рассматривать левое ребро, чтобы найти процент, который нам нужно добавить 100%
,
Новый процент 100% + p%
и формула будет p' = 1 + p --> p' = 1 + s / (1 - s) = 1 / (1 - s)
,
Вот анимация для иллюстрации приведенного выше расчета:
.box {
width:200px;
height:50px;
margin:5px;
border:1px solid;
background-image:linear-gradient(to right,red,blue);
background-position:0 0;
background-size:calc(var(--s) * 100%) 100%;
background-repeat:no-repeat;
animation:change 4s linear infinite alternate;
}
@keyframes change{
from { /*Hide on the left*/
background-position:calc(var(--s)/(var(--s) - 1) * 100%)
}
to { /*Hide on the right*/
background-position:calc(1/(1 - var(--s)) * 100%)
}
}
<div class="box" style="--s:0.5">
</div>
<div class="box" style="--s:0.8">
</div>
<div class="box" style="--s:2">
</div>
Давайте посчитаем несколько значений:
когда s=0.5
, у нас есть background-size
равно 50%
и процентные значения будут от -100%
в 200%
, В этом случае мы начинали с отрицательного значения и заканчивали положительным, потому что размер изображения меньше размера контейнера. Если мы рассмотрим последний случай (s=2
) background-size
равно 200%
и процентные значения будут от 200%
в -100%
, Мы начали с положительного значения и закончили отрицательным, потому что размер изображения больше, чем размер контейнера.
Это подтверждает то, что мы сказали ранее: чтобы сместить изображение влево, нам нужны отрицательные значения, если размер маленький, но нам нужны положительные значения, если размер большой (то же самое справа).
Соотношение между значениями пикселей и процентов
Давайте определим способ вычисления процентных значений на основе значений пикселей или наоборот (то есть формула для преобразования между ними). Для этого нам просто нужно рассмотреть опорные точки.
При использовании значений пикселей мы будем рассматривать синие линии и получим background-position:X Y
,
При использовании процентных значений, мы будем рассматривать зеленые линии, и мы будем иметь background-position:Px Py
,
Формула будет выглядеть следующим образом: Y + Py * Ws = Py * Wp
где Ws
ширина изображения и Wp
ширина контейнера (та же формула для оси X с учетом высоты).
Мы будем иметь Y = Py * (Wp - Ws)
, Из этой формулы мы можем проверить две точки, как объяснено ранее:
- когда
Wp = Ws
формула больше не действительна, что подтверждает, что процентные значения не действуют, если размер изображения совпадает с размером контейнера; таким образом, нет никакого отношения между пиксельными и процентными значениями. Y
а такжеPy
будет иметь тот же знак, когдаWp > Ws
и будет иметь противоположный знак, когдаWp < Ws
, Это подтверждает, что процентное значение ведет себя по-разному в зависимости от размера изображения.
Мы также можем выразить формулу по-разному, если мы рассмотрим процентное значение background-size
, Мы будем иметь Y = Py * Wp * (1-s)
,
Вот анимация для иллюстрации приведенного выше расчета:
.box {
width:200px;
height:50px;
margin:5px;
border:1px solid;
background-image:linear-gradient(to right,red,blue);
background-position:0 0;
background-size:calc(var(--s) * 100%) 100%;
background-repeat:no-repeat;
animation:percentage 2s linear infinite alternate;
}
.box.alt {
animation-name:pixel;
}
@keyframes percentage{
from { background-position:-50%;}
to { background-position:150%;}
}
@keyframes pixel{
from { background-position:calc(-0.5 * 200px * (1 - var(--s))) }
to { background-position:calc(1.5 * 200px * (1 - var(--s)));}
}
<div class="box" style="--s:0.5">
</div>
<div class="box alt" style="--s:0.5">
</div>
<div class="box" style="--s:2">
</div>
<div class="box alt" style="--s:2">
</div>
Изменение ссылки
В приведенных выше расчетах мы всегда учитывали верхний / левый угол изображения и контейнер, чтобы применять нашу логику для значений пикселей или процентных значений. Эту ссылку можно изменить, добавив дополнительные значения в background-position
,
По умолчанию background-position: X Y
эквивалентно background-position: left X top Y
(позиция в X
от left
и в Y
от top
). Регулируя top
и / или left
мы меняем ссылку и способ размещения изображения. Вот некоторые примеры:
.box {
width:150px;
height:150px;
display:inline-block;
background-image:url(https://picsum.photos/70/70?image=1069);
border:1px solid;
background-position:0 0;
background-repeat:no-repeat;
position:relative;
}
body {
margin:0;
}
<div class="box"></div>
<div class="box" style="background-position:left 0 bottom 0"></div>
<div class="box" style="background-position:right 0 bottom 0"></div>
<div class="box" style="background-position:right 0 top 0"></div>
<div class="box" style="background-position:right 10% top 30%"></div>
<div class="box" style="background-position:right 10% bottom 30%"></div>
<div class="box" style="background-position:right 10px top 20px"></div>
<div class="box" style="background-position:left 50% bottom 20px"></div>
Понятно, что для X
значение, которое мы можем использовать только left
а также right
(горизонтальное положение) и с Y
значение, которое мы можем использовать только bottom
а также top
(вертикальное положение). Со всеми различными комбинациями мы можем логически получить 4 разных угла.
Эта функция также полезна для оптимизации некоторых расчетов. В примере с разделом особых случаев мы сделали первый расчет, чтобы скрыть изображение слева, а затем еще один, чтобы скрыть его справа. Если мы рассмотрим изменение ссылки, нам нужно сделать только один расчет. Мы берем формулу, используемую для левой стороны, и используем то же самое для правой стороны.
Вот новая версия:
.box {
width:200px;
height:50px;
margin:5px;
border:1px solid;
background-image:linear-gradient(to right,red,blue);
background-position:0 0;
background-size:calc(var(--s) * 100%) 100%;
background-repeat:no-repeat;
animation:change 4s linear infinite alternate;
}
@keyframes change{
from { /*Hide on the left*/
background-position:left calc(var(--s)/(var(--s) - 1) * 100%) top 0
}
to { /*Hide on the right*/
background-position:right calc(var(--s)/(var(--s) - 1) * 100%) top 0
}
}
<div class="box" style="--s:0.5">
</div>
<div class="box" style="--s:0.8">
</div>
<div class="box" style="--s:2">
</div>
За s=0.5
мы больше не будем оживлять -100%
в 200%
НО это будет из left -100%
в right -100%
,
Вот еще один пример использования значений пикселей, где мы можем ясно увидеть, как легко справиться с вычислениями при изменении ссылки:
.box {
width:200px;
height:200px;
background-image:url(https://picsum.photos/100/100?image=1069);
border:1px solid;
background-position:0 0;
background-repeat:no-repeat;
animation:change 2s infinite linear;
}
@keyframes change{
0%{background-position:left 20px top 20px;}
25%{background-position:right 20px top 20px;}
50%{background-position:right 20px bottom 20px;}
75%{background-position:left 20px bottom 20px;}
100%{background-position:left 20px top 20px;}
}
<div class="box"></div>
Было бы сложно добиться такой же анимации, сохраняя ту же ссылку. Поэтому, если мы хотим создать симметричную анимацию, мы используем логику с одной стороны и используем то же самое с другой стороны, изменяя ссылку.
Объединение значений пикселей и процентов
В CSS3 мы можем использовать calc()
чтобы сделать какой-то сложный расчет, который включает в себя различные единицы. Например, мы можем написать width:calc(100px + 20% + 12em)
и браузер вычислит вычисленное значение с учетом того, как работает каждая единица, и мы получим значение в пикселях (для этого случая).
Как насчет background-position
? Если мы напишем calc(50% + 50px)
Будет ли это оцениваться в процентах или в пикселях? будет ли значение пикселя преобразовано в процент или наоборот?
Результат не будет преобразован в значение в пикселях или в процентах, а скорее будет использоваться вместе! background-position
имеет особое поведение при смешивании процентных и пиксельных значений внутри calc()
и логика выглядит следующим образом:
- Сначала мы используем процентное значение для позиционирования изображения, применяя всю логику, связанную с процентными значениями.
- Мы рассматриваем положение (1) в качестве эталона и используем значение пикселя, чтобы снова позиционировать изображение, применяя всю логику, связанную со значениями пикселей.
Так calc(50% + 50px)
означает: отцентрируйте изображение, затем сдвиньте его на 50 пикселей влево.
Эта функция может значительно упростить расчет. Вот пример:
.box {
width:200px;
height:200px;
border:1px solid;
background-image:
linear-gradient(red,red),
linear-gradient(red,red),
linear-gradient(red,red),
linear-gradient(red,red);
background-size:20px 20px;
background-position:
calc(50% + 20px) 50%,
calc(50% - 20px) 50%,
50% calc(50% - 20px),
50% calc(50% + 20px);
background-repeat:no-repeat;
}
<div class="box"></div>
<div class="box" style="width:100px;height:100px;"></div>
Было бы утомительно находить правильные значения в процентах или пикселях для размещения 4 красных квадратов, как указано выше, но смешивая оба, используя calc()
это довольно легко.
Теперь давайте предположим, что у нас есть что-то вроде этого: calc(10% + 20px + 30% + -10px + 10% + 20px)
, Как браузер справится с этим?
В таком случае браузер сначала оценит каждую единицу, чтобы получить упрощенную форму calc(X% + Ypx)
затем примените вышеуказанную логику для позиционирования изображения.
calc(10% + 20px + 30% + -10px + 10% + 20px)
calc((10% + 30% + 10%) + (20px + -10px +20px))
calc(50% + 30px)
.box {
display:inline-block;
width:200px;
height:200px;
background-image:url(https://picsum.photos/100/100?image=1069);
border:1px solid;
background-position:calc(10% + 20px + 30% + -10px + 10% + 20px) 0;
background-repeat:no-repeat;
}
.alt {
background-position:calc(50% + 30px) 0;
}
<div class="box"></div>
<div class="box alt"></div>
Какова бы ни была сложность формулы, браузер всегда будет оценивать процентное и пиксельное значения отдельно.
Используя больше единиц
Кроме того px
единица, которую мы можем также использовать все общие единицы в background-position, как em
, ch
, ex
, rem
, cm
и т.д. Все будут вести себя так же, как значения пикселей.
.box {
display:inline-block;
width:200px;
height:200px;
font-size:25px;
background-image:url(https://picsum.photos/100/100?image=1069);
border:1px solid;
background-position:50px 0;
background-repeat:no-repeat;
}
.em {
background-position:2em 0;
}
.ch {
background-position:2ch 0;
}
:root { font-size:50px}
.rem {
background-position:1rem 0;
}
<div class="box"></div>
<div class="box em"></div>
<div class="box ch"></div>
<div class="box rem"></div>
Таким образом, мы можем использовать процентные значения или значения длины (px
, em
, ch
, так далее)
Использование background-origin
Вот еще одно важное свойство, которое можно использовать для изменения положения фонового изображения. Это свойство опирается на блочную модель, поэтому давайте быстрое напоминание о том, как это работает:
Каждый элемент имеет 3 разных блока: внутри рамки, поля заполнения и поля содержимого. background-origin
определяет, какое поле нам нужно рассмотреть, чтобы выполнить всю нашу предыдущую логику.
Вот очевидный пример:
.box {
display:inline-block;
width:200px;
height:200px;
font-size:25px;
background-image:url(https://picsum.photos/100/100?image=1069);
border:20px solid rgba(0,0,0,0.1);
padding:20px;
background-position:0 0;
background-repeat:no-repeat;
box-sizing:border-box;
}
.border {
background-origin:border-box;
}
.padding {
background-origin:padding-box; /*the default value*/
}
.content {
background-origin:content-box;
}
<div class="box border"></div>
<div class="box padding"></div>
<div class="box content"></div>
Теперь должно быть ясно, что, когда у нас нет заполнения content-box
эквивалентно padding-box
и когда у нас нет границы border-box
эквивалентно padding-box
,
Официальная ссылка: https://www.w3.org/TR/css-backgrounds-3/
В большинстве случаев я рассмотрел только одну ось для объяснения / расчета, но одни и те же правила применяются к обеим осям, и обе они независимы.
Наконец нашел решение, которое не использует фиксированную меру или javascript.