Is there any way to make background-position
take percentage values? Currently my button only works with an explicit values for width
and 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>
TL;DR
All the percentage values used with
background-position
are equivalent when using a gradient as background, so you won't see any difference. You need to specify abackground-size
different from the container size:How does background position work?
Let's use a classic image in order to explain how
background-position
works.When using pixel values, the reference is the top/left corner of the image, whatever the size is. It's like using
top
/left
with a positioned element:When using percentage values, the reference is different from when you use pixel values; it's no longer the top/left corner:
In this case, we need to consider two parameters: the size of the container AND the size of the image. Here is an illustration of how it works (I took a
background-position
equal to30% 30%
):First, we consider the image to find the reference point we will use in order to place the image. It's the point inside the image that is positioned at
30% 30%
from the top/left corner considering the size of the image (like defined with the green lines). Then, we place that point inside the container at30% 30%
from the top/left corner considering the size of the container.From this logic, we can clearly identify some trivial cases like
50% 50%
(center)100% 100%
(bottom right)100% 0%
(top right)Now it's clear that if the size of the image is equal to the size of the container then nothing will happen simply because all the positions are equivalent. The top/left of the image is already at the top/left (0% 0%) of the container, the center is already at the center (50% 50%) etc.
The above logic is the same when applied to gradients since gradients are considered images, and by default, if you don't specify a
background-size
, a gradient's size will be the size of its container, unlike when using an image.If we refer to the specification of the
background-size
, we can see how your problem arises:And:
And also:
An image always has intrinsic values, so in most cases it won't have the same size as its container, so
background-position
with percentage units will have an effect. But gradients don't have intrinsic values, thus a gradient's dimensions will be equal to the size of its container, andbackground-position
with percentage values will never work unless we specify abackground-size
different from its container's dimensions.More in-depth
We saw in the above examples how
background-size
works when using values between0%
and100%
, but what about using negative values or a value bigger than100%
? The logic is the same, but finding the reference point will be more tricky.Negative values (< 0%)
Let's suppose we want to place a background at
-50% 0
. In this case the reference point will be outside the image. Here is an example:As we can see in the illustration, we first consider
-50%
of the image, which is-50px
, in order to define our reference point (i.e., -50px from the left edge of the image). Then we place that point at-50%
considering the size of the container (-100px from the left edge of the container). Then we draw the image, and we obtain the above result. Only100px
of the image is visible.We may also notice that negative percentage values will behave the same as negative fixed values when the size of the image is smaller than the size of the container (both will shift the image to the left). In this case
-50% 0
is the same as-50px 0
.If for example we increase the image size to
150px 150px
,-50% 0
will be the same as-25px 0
.When we make the size bigger than the container, negative values will start shifting the image to the right (like with positive pixel values), which is logical since the
50%
of the image will increase while the50%
of the container will remain the same.If we consider the previous illustration, it's like increasing the top green line until it's bigger than the bottom one. So the sign only is not enough to know how the background image will be shifted; we need to also consider the size.
The same will logically happen for gradients:
Big values (> 100%)
Same logic as previously: if we define a background at
150% 0
, then we consider our reference point150%
from the left edge (or50%
from the right edge), then we place it150%
from the left edge of the container.In this case,
150% 0
is equivalent to150px 0
, and if we start increasing the background size we will have the same behavior as previously demonstrated:Special cases
Using values outside the range
[0% 100%]
allows us to hide the background image, but how do we find the exact values in order to completely hide the image?Let's consider the below illustration:
Our image has a width
Ws
and the container a widthWp
and we need to find the value ofp
. From the figure we can obtain the following formula:If the container size is
200px
and the image is100px
thenp
is1
so100%
(we add of course the negative sign and it's-100%
).We can make this more generic if we consider percentage values with
background-size
instead of fixed values. Suppose thebackground-size
isS%
. Then we will haveWs = Wp * s (s in [0,1] and S=s*100%)
, and the formula will beAdding the negative sign it will be
p = s / (s - 1)
.Now if we want to hide the image on the right side, we do the same logic on the right (we consider a mirror of the previous illustration), but since we will always consider the left edge to find the percentage we need to add
100%
.The new percentage
p'%
is100% + p%
, and the formula will bep' = 1 + p --> p' = 1 + s / (1 - s) = 1 / (1 - s)
.Here is an animation to illustrate the above calculation:
Let's calculate some values:
When
s=0.5
, we have abackground-size
equal to50%
, and the percentage values will be from-100%
to200%
. In this case, we started with a negative value and ended with a positive one because the size of the image is smaller than the size of the container. If we consider the last case (s=2
) thebackground-size
is equal to200%
, and the percentage values will be from200%
to-100%
. We started with a positive value and ended with a negative one because the size of the image is bigger than the size of the container.This confirms what we said previously: to shift an image to the left we need negative values if the size is small, but we need positive values if the size is big (same thing for the right).
Relation between pixel and percentage values
Let's define a way to calculate percentage values based on pixel values, or vice versa (i.e. the formula to convert between both). To do this we simply need to consider the reference points.
When using pixel values, we will consider the blue lines and we will have
background-position:X Y
.When using percentage values, we will consider the green lines and we will have
background-position:Px Py
.The formula will be like follow :
Y + Py * Ws = Py * Wp
whereWs
is the width of the image andWp
is the width of the container (same formula for the X axis considering height).We will have
Y = Py * (Wp - Ws)
. From this formula we can validate two points as explained previously:Wp = Ws
, the formula is no longer valid, which confirms that percentage values have no effect when the size of the image is the same as the container; thus there is no relation between pixel and percentage values.Y
andPy
will have the same sign whenWp > Ws
and will have opposite sign whenWp < Ws
. This confirms that percentage value behave differently depending the size of the image.We can also express the formula differently if we consider percentage value of
background-size
. We will haveY = Py * Wp * (1-s)
.Here is an animation to illustrate the above calculation:
Changing the reference
In the above calculations, we always considered the top/left corner of the image and the container in order to apply our logic either for pixel values or percentage values. This reference can be changed by adding more values to
background-position
.By default
background-position: X Y
is equivalent tobackground-position: left X top Y
(position atX
from theleft
and atY
from thetop
). By adjustingtop
and/orleft
we change the reference and how the image is placed. Here are some examples:It's clear that for the
X
value we can only useleft
andright
(the horizontal position) and with theY
value we can only usebottom
andtop
(the vertical position). With all the different combinations we can logically obtain the 4 different corners.This feature is also useful in order to optimize some calculation. In the example of the special cases section, we did a first calculation to hide the image on the left then another one to hide it on the right. If we consider changing the reference we only need to do one calculation. We take the formula used for the left side and we use the same on the right side.
Here is the new version:
For
s=0.5
we will no more animate from-100%
to200%
BUT it will be fromleft -100%
toright -100%
.Here is another example using pixel values where we can clearly see how easy is to deal with the calculation when changing the reference:
It would be tricky to achieve the same animation by keeping the same reference. So if we want to do a symmetrical animation we do our logic on one side and use the same on the other side by changing the reference.
Combining pixel and percentage values
In CSS3 we can use
calc()
in order to do some complex calculation that involves different units. For example, we can writewidth:calc(100px + 20% + 12em)
and the browser will calculate the computed value considering how each unit works and we will end with a pixel value (for this case).What about
background-position
? If we writecalc(50% + 50px)
, will this be evaluated to a percentage value or a pixel value? will the pixel value be converted to percentage or the opposite?The result will not be converted to a pixel value or a percentage value, but rather both will be used together!
background-position
has a special behavior when mixing percentage and pixel values insidecalc()
and the logic is as follows:So
calc(50% + 50px)
means: center the image, then shift it by 50px to the left.This feature can simplify a lot of calculation. Here is an example:
It would be tedious to find the correct percentage or pixel values to place the 4 red squares like above, but by mixing both using
calc()
it is pretty easy.Now, let's suppose we have something like this:
calc(10% + 20px + 30% + -10px + 10% + 20px)
. How will the browser handle this?In such case, the browser will first evaluate each unit to obtain the simplified form
calc(X% + Ypx)
then apply the above logic to position the image.Whatever the complexity of the formula is, the browser will always evaluate percentage and pixel values separately.
Using background-origin
Here is another important property that can be used to alter the position of background image. This property relies on the box model so lets get a quick reminder about how that works:
Each element has 3 different boxes inside it: border-box, padding-box and the content-box.
background-origin
specifies which box we need to consider in order to do all our previous logic.Here is a self-explanatory example:
It should be clear now that when we don't have padding
content-box
is equivalent topadding-box
, and when we don't have borderborder-box
is equivalent topadding-box
.Make percentage behave differently
In case we really need to have the size of the image equal to the container size and move it using percentage like pixel we can consider the below ideas.
We should note that translate consider the size of the pseudo element but since it's the same as the container we won't have any issue. We can also use
left
/top
buttransform
will have better performance.background-origin
The trick is to have padding, restrict the origin to content-box and make the size bigger than
100%
to cover the padding and have the image fill the container.In the above, I made the padding half the size so logically I need to use
200%
inbackground-size
to rectify. For thebackground-position
, it's now easy to find the needed value based on the above explanation.Another example:
Note that other units like
em
,ch
,ex
,rem
,cm
etc, behave the same as pixel values.