In my last post I talked about a technique that makes it possible to change the position and relation of an SVGs content on rescale, but keep their proportions at the same time. In this post I will demonstrate the technique of the "4 Way Sliced Image". This method opens up possibilities that can not be achieved with traditional CSS. Once you applied this technique to a CSS background image you will never want to go back to traditional background images. We will be able to create irregularly shaped outlines around elements and create arbitrary corner styles. In "traditional" CSS techniques "border-image" is what comes closest to the 4 slicing method. But contrary to "border-image", which is haunted by browser inconsistencies and not supported in IE<11, this method works in most modern browsers, even in IE9. And unlike "border-image" we will be able to apply background patterns. When I researched this topic I never found a reference to this technique, so maybe this is a premiere:
Meet the 4 Slice Scaling Method for SVG
Again we will start with an example: the link below will open a new window. You see one div with one(!) background image. Rescale the window, or when you're on a mobile device, turn it. Watch how the outline doesn't get thicker and the corner shape keeps its size. Observe the background pattern:
Anyone remember Doug Bowmans "sliding doors" technique? Those were the days... The 4 slice scaling method is the SVG equivalent to this classical CSS trick. Basically it means that we will take 4 tiles, position one in every corner of our image and when the image itself is resized, the tiles will stay glued to the corners. This way we can simulate an irregularly sized border around a block element. Of course we must take care that a tile is big enough, otherwise a gap will appear when the image is scaled too much. But contrary to the "sliding doors" technique we can be sure that the images will never overlap.
Unfortunately there's a catch: SVG is good at fixed positions and sizes. It's easy to scale an SVG as a whole, its easy to position elements relative to the origin of the coordinate system (which will be always relative to the upper left corner). When we want to position elements relatively and want them to scale independently, we must "cheat".
The container SVG
We start out with a container SVG and symbols that contain the shapes for the corner tiles: one for the top left & right corners, one for the bottom corners. A tile will be 700px x 700px so we will apply a viewbox attribute of the same size (learn more about the SVG viewbox at MDN). As we want our SVG to be freely scalable we will not apply any attributes for size, viewbox or positioning to the outermost svg:
<!-- 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>
<symbol id="top" viewBox="0 0 700 700">
<!-- code for the tile (SVG shape or bitmap) goes here. -->
</symbol>
<symbol id="bottom" viewBox="0 0 700 700">
<!-- code for the tile goes here. -->
</symbol>
</defs>
</svg>
Building structure
Now we insert 4 child SVGs that will be the containers for the tiles, 50% height and 50% width. Why use nested SVG? SVG are some of the few elements that are allowed to have coordinate systems, viewboxes and aspect ratios independent from the parent element. Additionally the content of an SVG will not "bleed outside" its parents borders (as long as you don't allow "style = overflow : visible"), so we can be sure our tiles won't overlap. Perfect!
<!-- top left: -->
<svg width="50%" height="50%">
<use xlink:href="#top" width="700" height="700" />
</svg>
<!-- top right: -->
<svg width="50%" height="50%">
<use xlink:href="#top" width="700" height="700" />
</svg>
<!-- bottom left: -->
<svg width="50%" height="50%">
<use xlink:href="#bottom" width="700" height="700" />
</svg>
<!-- bottom right: -->
<svg width="50%" height="50%">
<use xlink:href="#bottom" width="700" height="700" />
</svg>
Unlike in CSS, there exists no option in SVG for positioning something to the left side or the bottom of a container. Hence the four SVG will now sit in the left top corner, above each other, which isn't exactly what we wanted. Solution: apply a transform and flip the coordinate system of the element completely. E. g. "transform = 'scale(1, -1)'" will flip an element and its coordinate system horizontally. As transforms can not be applied to SVG elements, we will wrap them into a "dimensionless" group (<g>) tag, then apply a transform to the group. When doing this operations we must take into account that:
- a flipped SVG will now be invisible because it's positioned outside the boundaries of the parent, so we have to reposition it back.
- the symbol inside the tile will appear flipped. If that is not desired, we should take care that the shape is already flipped or flip it back with a second transform (when doing this, a bug in IE9 will show up, which must be fixed separately, see below.).
<!-- bottom right: -->
<g transform="scale(-1, -1)">
<svg width="50%" height="50%" x="-100%" y="-100%"
<use xlink:href="#bottom" width="700" height="700" y="-700" transform="scale(1, -1)"/>
</svg>
</g>
If you would like to try this yourself, here's a pen I made.
As I said earler, Internet Explorer does not like flipped elements inside flipped elements. The flipped child will "bleed" over the boundaries of it's container, overlapping everything else. Therefore we must add a clippath for IE:
<!-- define a clipath for IE -->
<defs>
<clipPath id="crop">
<rect width="100%" height="100%" x="0"/>
</clipPath>
...
<defs>
<g transform="scale(-1, -1)">
<svg width="50%" height="50%" x="-100%" y="-100%"
<use xlink:href="#bottom" width="700" height="700" y="-700" transform="scale(1, -1)" clip-path="url(#crop)/>
</svg>
</g>
Applying a Background Pattern
And now for something that is *impossible* to achieve with classical CSS techniques. We will apply a uniform background pattern.The trick is to position a rectangle in the background that contains the pattern and use a four slice scaled mask element on it:
<svg>
<defs>
<symbol id="topleft" viewBox="0 0 700 700" >
<!-- code for topleft tile goes here -->
</symbol>
<symbol id="topright" viewBox="0 0 700 700">
<use xlink:href="#topleft"/>
</symbol>
<symbol id="bottomleft" viewBox="0 0 700 700">
<!-- code for bottomleft tile goes here -->
</symbol>
<symbol id="bottomright" viewBox="0 0 700 700">
<use xlink:href="#bottomleft"/>
</symbol>
<pattern id="pattern1" x="0" y="0" width="60" height="60" patternUnits="userSpaceOnUse" >
<!-- code for pattern goes here -->
</pattern>
<mask id="mask">
<!-- top left: -->
<svg width="50%" height="50%">
<use xlink:href="#topleft" width="700" height="700" class="mask"/>
</svg>
<!-- top right: -->
<g transform="scale(-1, 1)">
<svg width="50%" height="50%" x="-100%" y="0">
<use xlink:href="#topright" width="700" height="700" class="mask"/>
</svg>
</g>
<!-- bottom left: -->
<g transform="scale(1, -1)">
<svg width="50%" height="50%" x="0" y="-100%" clip-path="url(#crop)">
<use xlink:href="#bottomleft" width="700" height="700" y="-700" transform="scale(1, -1)" class="mask"/>
</svg>
</g>
<!-- bottom right: clip-path="url(#crop)" -->
<g transform="scale(-1, -1)">
<svg width="50%" height="50%" x="-100%" y="-100%" clip-path="url(#crop)">
<use xlink:href="#bottomright" width="700" height="700" y="-700" transform="scale(1, -1)" class="mask"/>
</svg>
</g>
</mask>
</defs>
<rect class="backgroundPattern" width="100%" height="100%" mask="url(#mask)"/>
<!-- remaining SVG code here -->
</svg>
Be aware that SVG masks can bring your CPU to it's knees, therefore test thoroughly. If you want to play with the code, here's a pen.
As you can see, the four sliced SVG is a very powerful technique. It can replace multiple CSS background images or CSS border images. We only scratched the surface here, as we are not limited to 4 slices, we can use more tiles or less, can clip them or allow them to overlap, scale some of them and let others keep their size, use masks that scale just a fraction of the outermost SVG... There are endless possibilities. Countless use cases exist, we can create flexible buttons that always stretch to the size of their content, or frames for image galleries that adapt to different image sizes.
A final note: some time ago I wrote about using bitmaps in SVG. Most in this article is still relevant. So if you want to use this technique in combination with bitmaps, be aware that at the time of this writing (17.02.2014) the xlink bitmap bug, while being fixed in Blink, still exists in Webkit. Webkit needs to "see" a bitmap at document level before it will display it within an SVG
And in my next article I will show how far we can come when trying to do the crown jewel of image slicing: the 9 sliced scaling grid.
[…] The 4 Slice Scaling Technique for SVG […]
This is awesome! I am looking forward to your next on the 9 slice scaling grid.
Om
[…] Go To Article Page […]
[…] Go To Article Page […]