How does border-image work with linear-gradient?
TL;DR
When using a gradient, the size of the image is the size of the element. The border-image-width
will define the 9 regions where we will place the slices (if not defined, border-width
is used). The border-image-slice
will consider the initial image to create the slices. A unitless value is considered as a pixel value and a percentage value is resolved against the size of the element.
To have a perfect result we should have the slices equal to regions, and for this we need to have the border-image-slice
equal to border-image-width
(or border-width
) when used without a unit. Using a percentage, the computed value should be the same.
In your case 80
in the slice means 80px
and you have a border of 5em
which is 5x16px = 80px
.
Let's take an easy example.
div {
width: 100px;
height: 100px;
display: inline-block;
border: 10px solid transparent;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 50 fill;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
In the above I tried to create two divs with the same output using different techniques (background and border). Notice how in the second example I use the keyword fill
and I specified a border-image-width
different from the border width and used a slice equal to that border width.
Note that 50
in the slice is considered as pixels here since we're dealing with a non-vector image (a gradient).
Numbers represent pixels in the image (if the image is a raster image) or vector coordinates (if the image is a vector image). ref
Let's remove the fill
keyword:
div {
width: 100px;
height: 100px;
display: inline-block;
border: 10px solid transparent;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 50;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
The fill keyword, if present, causes the middle part of the border-image to be preserved. (By default it is discarded, i.e., treated as empty.) ref
By default, the border image is not painted in the middle but only in the border. We can clearly see from the example that we have 50px
on each side, with our custom border likewise defined by border-image-width
.
And if we don't specify border-image-width
the default value is 1
which means:
Numbers represent multiples of the corresponding computed
border-width
.
So either we explicitely specify border-image-width
or we simply use border-width
as reference. In most cases only border-width
is needed since in most cases we want to only cover the border area and not more.
Now the slice will split the image in 9 parts:
This property specifies inward offsets from the top, right, bottom, and left edges of the image, dividing it into nine regions: four corners, four edges and a middle
ref
Here are the steps that will better show how it's done for our example:
div {
width: 100px;
height: 100px;
border: solid 10px transparent;
display: inline-block;
position: relative;
}
div:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(green,green) left 0 top 50px/100% 1px no-repeat,
linear-gradient(green,green) left 0 bottom 50px/100% 1px no-repeat,
linear-gradient(green,green) top 0 left 50px/1px 100% no-repeat,
linear-gradient(green,green) top 0 right 50px/1px 100% no-repeat;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 50;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
The left image is the original one that we divide into 9 parts, then we put each one in the 9 regions of the right one. The middle one is empty because we didn't use fill
. In this example we will notice nothing because the slices fit the regions.
Now let's reduce the slice to 25
:
div {
width: 100px;
height: 100px;
border: solid 10px transparent;
display: inline-block;
position: relative;
}
div.box:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(blue,blue) left 0 top 25px/100% 1px no-repeat,
linear-gradient(blue,blue) left 0 bottom 25px/100% 1px no-repeat,
linear-gradient(blue,blue) top 0 left 25px/1px 100% no-repeat,
linear-gradient(blue,blue) top 0 right 25px/1px 100% no-repeat;
}
div.border:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat,
linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat,
linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat,
linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 25;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
It's a bit tricky but the same logic applies. From the left image we cut using 25px
from each side to get our 9 portions that we will put in the right one, where the border-width is still the same (50px
). You can clearly notice how the parts in the corners are simply scaled and the edges are distorted.
In each corner we are using a 25px 25px
image inside a 50px 50px
area and in the top edge for example we are using a 60px 25px
image inside a 10px 50px
area.
You can also define different values for each side to have something like below:
div {
width: 100px;
height: 100px;
border: solid 10px transparent;
display: inline-block;
position: relative;
}
div.box:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(blue, blue) left 0 top 20px/100% 1px no-repeat,
linear-gradient(blue, blue) left 0 bottom 30px/100% 1px no-repeat,
linear-gradient(blue, blue) top 0 left 20px/1px 100% no-repeat,
linear-gradient(blue, blue) top 0 right 60px/1px 100% no-repeat;
}
div.border:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat,
linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat,
linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat,
linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px);
border-image-slice: 20 60 20 30;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
Now it's more clear how we slice the image then we put them in the different region by scaling stretching them. It's also clear that the best value is to have the slices in all the sides equal to the border-width
which is the case in your example since 5em
is 5x16px = 80px
thus a slice of 80
From the specification we can also read:
The regions given by the border-image-slice values may overlap. However if the sum of the right and left widths is equal to or greater than the width of the image, the images for the top and bottom edge and the middle part are empty, which has the same effect as if a nonempty transparent image had been specified for those parts. Analogously for the top and bottom values.
If you specify a left and right slice bigger than the image width then logically you will get nothing to put on the top/bottom/middle part:
div {
width: 100px;
height: 100px;
border: solid 10px transparent;
display: inline-block;
position: relative;
}
div.box:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(blue, blue) left 0 top 20px/100% 1px no-repeat,
linear-gradient(blue, blue) left 0 bottom 30px/100% 1px no-repeat,
linear-gradient(blue, blue) top 0 left 60px/1px 100% no-repeat,
linear-gradient(blue, blue) top 0 right 60px/1px 100% no-repeat;
}
div.border:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat,
linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat,
linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat,
linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px);
border-image-slice: 20 60 20 60;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
The same logic applies to top/bottom too.
Here is an example where we will only have the corners:
div {
width: 100px;
height: 100px;
border: solid 10px transparent;
display: inline-block;
position: relative;
}
div.box:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(blue, blue) left 0 top 20px/100% 1px no-repeat,
linear-gradient(blue, blue) left 0 bottom 100px/100% 1px no-repeat,
linear-gradient(blue, blue) top 0 left 60px/1px 100% no-repeat,
linear-gradient(blue, blue) top 0 right 60px/1px 100% no-repeat;
}
div.border:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat,
linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat,
linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat,
linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px);
border-image-slice: 20 60 100 60;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
Using a percentage value will also give the same result. We simply need to find the reference and since we are dealing with a gradient, the size of the gradient is simply the size of the element. A slice of 50
in our example is equal to 41.666%
since the width/height is equal to 100px 2 * 10px = 120px
div {
width: 100px;
height: 100px;
border: solid 10px transparent;
display: inline-block;
position: relative;
}
div:before {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
background:
linear-gradient(blue, blue) left 0 top 50px/100% 1px no-repeat,
linear-gradient(blue, blue) left 0 bottom 50px/100% 1px no-repeat,
linear-gradient(blue, blue) top 0 left 50px/1px 100% no-repeat,
linear-gradient(blue, blue) top 0 right 50px/1px 100% no-repeat;
}
div.box {
background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}
div.border {
border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 41.666%;
border-image-width: 50px;
background: red;
}
<div class="box"></div>
<div class="border"></div>
In the next example I'm using px instead of em because I think is clearer.
This is the image used for the border image.
div{ width: 416px; height: 368px;
background:repeating-linear-gradient(45deg,
#000, #000 1.5%,
transparent 1.5%, transparent 5%);
}
<div></div>
This image will be sliced in 9 squares something like a grid.
The image is from this article: border-image-slice
If the value for border-image-slice
is 80 this means that the offset is 80, i.e. C1, C2, C3 and C4 size is 80/80. All C slices are used for the corners of the border image. The E1,E2,E3 and E4 are used to draw the edges.
If instead of 80 you are using 208 or 50% the border image will get corners but no edges because there is nothing left for the edges.
Next comes a demo where you can see the evolution of the slices on the image used to draw the border image. I've changed the width of the div at 300 because I wanted to see both the div with the border image and the image used for the border one next to the other. In this case the edges of the border image are disappearing at border-image-slice:150;
itr.addEventListener("input",()=>{
let v = itr.value;
border.style.borderImageSlice = v;
itrspan.innerHTML = v;
let d = `M${v},0v300M${300-v},300v-300M0,${v}h300M300,${300-v}h-300`
thePath.setAttributeNS(null,"d",d)
})
div{display:inline-block;}
#border {
box-sizing: border-box;
position: relative;
border: solid 5em #000;
border-image: repeating-linear-gradient(45deg,
#000, #000 1.5%,
transparent 1.5%, transparent 5%);
border-image-slice:80;
padding: 2em;
width: 300px; height: 300px;
}
#image{
width: 300px; height: 300px;
background: repeating-linear-gradient(45deg,
#000, #000 1.5%,
transparent 1.5%, transparent 5%);}
input{width:300px;}
<input id="itr" type="range" min="0" max="300" value="80" ><span id="itrspan">80</span>
<br>
<div id="border"></div>
<svg id="image" viewBox="0 0 300 300">
<path id="thePath" fill="none" stroke="red" d="M80,0v300M220,300v-300M0,80h300M300,220h-300" />
</svg>