eleqtriq

In our ongoing quest to find new "liquid" SVG techniques, this post will be about the "holy grail" of image scaling, the 9-sliced scaling grid and how to apply it to SVG images. If you haven't done so, you should learn about the 4-slice scaling technique first and then come back.

What is 9-Slice Scaling?

Figure 1: Architecture of a 9-slice scaling grid.

The 9-slice scaling grid has been around in desktop graphics software for some years already and is well known to users of Flash and Illustrator. It's a brilliant technique for scaling images by dividing them into sections that have a fixed size (the corners) and sections that are flexible (the edges and the center). In fact this method is so powerful that I would love to see it as a part of an upcoming CSS specification. Syntax could be something like:

div{ background-grid: 23px 23px 40px 20px; /*left-x-coordinate right-x-coordinate top-y-coordinate bottom-y-coordinate*/ }

I'd rather like to have this than the hilarious corner-shape property. But at the moment this is only wishful thinking and we must make the best out of the tools we do have at hand right now.

I developed 2 techniques to apply a 9-slice scaling grid to an SVG. One is the "almost" technique. This method is straightforward and will work in every SVG capable modern browser (at least every one I tested). And then there is a "perfect" solution. This method is quite complicated to implement and unfortunately doesn't work in Internet Explorer.

The "Almost" Approach

As always, the example below will open in a new window. Rescale the window or turn your mobile device. Watch how the size of the background image changes, how the edges rescale and the corners reposition but keep their size.

Open an example for the "almost" 9-slice scaling grid

The "almost" technique works by "gluing" shapes into the 4 corners of the SVG. Edges do have a fixed height/flexible width resp. fixed width/flexible height, the background is completely flexible. By applying some clever SVG masks, we make sure that the edges do not overlap the corners, so that we can use the element even on non-monochrome backgrounds. This Illustration explains the way it is constructed:

Figure 2: how the "almost" 9-slice scaling grid is constructed.

Let's take a look at how to construct one side of our image this way. To make things easy I assume we have SVG symbols for the corners and the edges for reuse, but of course any other SVG can be used instead.

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- Left corner --> <use xlink:href="#corner" width="100px" height="100px" /;> <!-- Edge --> <use xlink:href="#edge" width="100%" height="100px" /;> <!-- Right corner --> <use xlink:href="#corner" transform="scale(1, -1)" y="-100%" width="100px" height="100px" /;> </svg>

As you can see there are two problems with the "almost" technique:

Problem one can not be solved with this technique, problem two can be addressed with an SVG mask:

<!-- XML declaration and doctype left out for simplicity --> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <style type="text/css"> <![CDATA[> .mask{ fill:white; stroke:black; stroke-width:200px; } <]]> </style> <mask id="mask-top-bottom"> <rect class="mask" width="100%" height="300px" y="-100px> </mask> </defs> <!-- Edge --> <use xlink:href="#edge" mask="url(#mask-top-bottom)" width="100%" height="100px" x="0" y="0"/;> ... </svg>

Our mask is simply a flexible rectangle, with a very thick black stroke (black = opaque) and a white fill (white = transparent). As the stroke width will not change, no matter what the current size of the rectangle is, we created a mask with a flexible opaque area (for the region beween the corners) and a fixed sized transparent area (preventing that the edge shape will overlap the corner shape). Strokes in SVG are positioned 50% to the inside and 50% to the outside of a shape, so it must be twice as thick as the width of the corner shape. To prevent it from masking the top and bottom of the edge we push the mask outside of the top and bottom of the SVG by a negative vertical position and tripling it's height:

Figure 3: masking an edge shape.

Now that we know how to create one side of our grid it's fairly easy to build the remaining 3 sides. I made a pen for this technique to play, you'll find it here.

The "Perfect" Technique:

Let's push the limits even further by implementing a perfect solution where an edge will stretch exactly from the end of one corner to the beginning of the next corner shape. As I already told you, this technique is complicated and can not be used in Internet Explorer. It's still an interesting experiment and even out of the world techniques can help us to understand things better.

Figure 4: constructing the "perfect" grid.

The main problem with the "perfect" solution is that we must find a way to make the edge shape start at x pixels from the left/top and stop at y pixels from the right/bottom. In traditional html/CSS we do this all day by applying a padding or a margin, but SVG doesn't offer anything like this. But why not "borrow" a margin from some HTML element? Only thing to to is to "inject" this element into our SVG via "foreignObject", give it a margin, and then put our edge in there:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <foreignObject width="100%" height="100px"> <div xmlns="http://www.w3.org/1999/xhtml" style="margin: 0 100px;"> <svg width="100%" height="100" preserveAspectRatio="none" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <use xlink:href="#topBottom" class="slice" width="100%" height="100px" x="0" y="0" style="overflow:visible;"> </svg> </div> </foreignObject> </svg>

That's it, we found our perfect solution! If we repeat this method on every side we will end with a nice, seamless 9-sliced scaling grid on our image.

Open "perfect" 9-Slice example

Unfortunately Internet Explorer can't handle foreignObject in SVG. SVG gives us the "switch" element to inject alternative content, so theoretically we should be able to provide a fallback for IE this way:

<switch> <foreignObject ...> </foreignObject> <!-- "Almost" solution, will appear only in browsers that don't support foreignObject --> <use xlink:href="#almost"> </switch>

Above code works, but only on elements that will never be resized after rendering. A bug in IE causes the fallback elements to stay in place in case the container is rescaled. Too sad, but at this point I'm stuck, IE has beaten me on this. Hopefully one of my readers will crack this nut.

Of course I made a pen in case you want to experiment with the code, you can find it here.

Final Note

SVG can do much more than images with fixed proportions. But SVG as a format is not meant to be hand coded. Unfortunately there aren't any editors that are able to output this kind of assets. You will need to export every single element from your graphics software and then assemble everything in a text editor. Hopefully, if more designers learn about these techniques and use them, vendors one day will provide us with improved export functions to create these wonderful flexible elements out of the box.

Trackback

3 Responses to “The Holy Grail of Image Scaling”

  1. Pingback by Best Web Design and Web Development Articles Roundup #7 »Css Author » Web / UI Design Resource Mon Apr 7th, 2014, 09:16

    […] Go To Article Page […]

  2. Comment by GreLI Tue Apr 29th, 2014, 22:07

    > In fact this method is so powerful that I would love to see it as a part of an upcoming CSS specification.

    Actually, there is already a CSS specification for this: CSS Backgrounds and Borders Module Level 3 http://www.w3.org/TR/css3-background/#border-images contains a property named `border-image`. It’s even supported in IE11 (see http://caniuse.com/border-image).

  3. Comment by Dirk Fri May 9th, 2014, 10:35

    Hi, thanks for your comment. I wrote about border-image here.

Chime in!