One of the more powerful new CSS properties, border-image
is also one of the best supported, with the exception of (all together now) IE9. It is, unfortunately, also one of the most obtuse and difficult to understand.
The explanation of how
border-image
works is perhaps best illustrated graphically. First, let's break down any border into its components: let's imagine an ormolu picture frame, divided up into nine tiles using two horizontal and two vertical lines. In the illustration to the right I've labeled the vertical divider lines V1 and V2, with H1 and H2 for the horizontal.
Rather than allowing you to specify a separate image for each part of a border, border-image
insists that you create one image, with all the parts in place, and then slice that image up using CSS values that map to the positions of these lines.
Let's imagine that we're playing Battleship, and reference each tile created by our divisor lines. The top left corner could be referred to as A1, the middle right section as B3, and so on. (The middle section, B2, will be ignored by the CSS after we specify the slices for our border image.) Where the slices are placed determines our tiles. A3 will always be placed as the top right corner image for our border; C2 will be used for the bottom edge, etc.
Let's consider the sides and top of our frame, because they have rather particular, and changeable, conditions. When we apply a border to an HTML element, we don't know how big the box will be. After they are applied to our border, our corner tiles will be unaffected by size changes, but the sides, top and bottom will definitely change. We have to make a decision about the tiles that will be placed there: will they repeat as the box gets bigger, or will they stretch? This consideration will have to be part of the design of our frame, and we'll have to specify our choice when it comes to writing our CSS.
Finally, let's think about the lines that created our tiles: two horizontal, and two vertical. We need to tell CSS where those imaginary divisors are, so that it can use the information to create the tiles. We could reference their positions as either percentages or pixels away from the corners. (I would suggest in most cases that the latter is easiest, except in the case of SVG images used for border image).
Note that the chevrons on the corners of this frame means that the slices must be large enough to include those details, which increases the size of our border, and will produce a physical “matte” effect inside the physical frame, which is actually realistic for a professional mounted photograph or painting. I’ve made the interior of the frame transparent, meaning that the background-color
I specify will show through.
The way in which the position of the divisors is measured and entered into the declaration is also odd: H2 is measured from the bottom of the image, H1 from the top, V1 from the left, and V2 from the right. They are entered into the declaration in this order:
border-image: url(image) H1 V2 H2 V1
Oddly, when the values are entered as pixels, they are not followed by a px suffix, unlike almost everything else in CSS.
The image is inserted into the page:
<img src="teracotta-statue.jpg" alt="Terracotta Statue" class="frame">
And given the border image above, the CSS applied would be something like this:
img.frame {
border-image: url('frame.png') 93 92 87 92;
background-color: #ffe;
}
(Note that this code won't work yet: there's a few things we have to add).
You have several choices as to how the sides are treated. stretch
does exactly that; repeat
will repeat the tiles to fit, and round
is a hybrid of both, attempting to use complete versions of the tiles for the sides, but stretching where necessary.
Importantly, you must also provide a separate, border-width
declaration, to tell the browser how wide each side is so that the images can be fit in. Most resources will tell you that the dimensions used in border-width
must be the same as those used for the position of the slices, i.e:
border-width: 93px 92px 87px 92px;
But I've found that using a single border-width
value will effectively scale the border dimensions:
border-width: 60px;
Remember that you'll also need a plain border to fall back on should the image for the tiles not load or your visitors use IE: I'd suggest specifying a border approximating the thickness of the image tiles, and using its dominant color, with perhaps a lesser-used border-style
thrown in.
So the complete code for the effect shown at the top of this page would be:
img.frame {
border-image: url('frame.png') 93 92 87 92 stretch stretch;
border-color: #f4be52;
border-style: inset;
border-width: 60px;
width: 500px;
height: 333px;
background-color: #ffe;
}
Design Tips
I'd suggest using PNG images for borders where possible, due to the inevitable stretching and distortion that any image applied via border-image
must endure. PNG-24 also provides the option of making parts of each tile transparent, which is important when you have large corner decorations on each corner, as in our ormolu frame example. Without transparency on the inner sections of the image, our border has the possibility of biting into whatever content is inside the box.
Delightfully, box-shadow
will continue to work as expected. Of course, the shadow will reflect the shape of the CSS box, not the frame per se; if the outside of your frame is irregular, you could create a realistic shadow by using a true drop shadow CSS filter or by altering the bitmap in an editor such as PhotoShop.
I'd also suggest that you use guides, the marquee tool and the Info
window to determine the measurements of the slices. Alternatively, you can use an online tool, like Kevin Decker's Border Image Generator, to create the CSS.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/yyZpeX