I came across this nice Photoshop tutorial the other day. Looking at this, and the other tutorials available there made me want to try to recreate the same effects, not using Photoshop however, just SVG. Read on for my take on the above tutorial. To better follow the steps in my article I recommend reading these side by side.
Note that the original version of this article can be found on my blog.
Create a new SVG file, and create an svg canvas like so:
By adding a viewBox
with the dimensions 600 x 335 you can get the SVG to scale to whatever size you want later on.
Next, create some rectangles and a couple of gradients by adding the following inside the svg
element.
The linear gradients gradiate between the first color and the second color at a 90 degree angle. 90 degree angles (sometimes called straight angles) are easily translated into a vector from point (x1,y1) to point (x2,y2), where the value '1' maps to 100% of the boundingbox of the shape that gets painted with the gradient, and the value '0' maps of course to 0% of the same. I've added the gradient stops with the colors we want, and set the offsets. The offset is where the color will be mapped onto the gradient vector specified with x1, y1, x2 and y2.
In case you didn't follow the above, here is a little more explaination - any angle can be written as the vector (x1,y1) to (x2,y2) (the angle is the angle of that vector). When you create a gradient the default is that the boundingbox of the shape that the gradient is applied to is used. In other words, the attributes x1,x2,y1,y2 are mapped onto the bounding box that gradient is used on as follows: (x,y)=(0,0) means the upper left corner, (x,y)=(1,1) means the bottom right corner. The gradient vector is the line between the start point (x1,y1) and the end point (x2,y2).
The g
element groups the rects together and provides a shape-rendering
property to disable anti-aliasing so that the edges stay crisp. I've also added the 90% opacity to the g
layer. Then for each of the rects I've assigned a fill. Note that for better performance you should use fill-opacity
instead of opacity
.
Next, create a few straight lines - add these inside the g
element from the previous step, just below the closing
One thing to note here is that line
elements have no fill, only stroke. Everything else should be fairly easy to understand; you draw the line between (x1,y1) and (x2,y2).
Now it's time to draw the divider line (the vertical line in between each button) - add this as follows:
This first bit of code adds the gradient that goes on the bottom half of the divider. Next you need to add the following, which allows you to reuse the divider multiple times.
...
The divider group once is defined once, then re-used multiple times with the use
element. This is really convenient - if you change the look of the divider, all of the use
instances will be updated too. It's possible to optimize here by creating filled rects instead of lines, and using only one gradient. In this particular example that's hardly the most time consuming part of the SVG anyway, so I've opted to leave it as close to the original tutorial as possible.
Next, you will add the text for the navigation buttons, styling it with common properties, and align it. Add the following CSS to your document:
...Blog About Tutorials Contact
Instead of rewriting stuff multiple times you've assigned a class
attribute and then used CSS to style all the text. We've used Arial with a sans-serif fallback, and set the size and aligned the text using the text-anchor
property. The text will be aligned around the 'x' coordinate we specified. I've added a text-rendering
property to make the text look more readable on viewers that support it. Usually it means that the text renders a bit more crisp.
To add the background image, simply drop in an image
element, as follows.
The image should cover the entire canvas so we specify width
and height
as 100%. Note that you can also write width="600" height=335"
since you have defined the coordinate system using the viewBox
attribute on the root svg
element (in step #1). Finally you add a preserveAspectRatio
attribute so that if we decide to add a graphic that can't be scaled to fit exactly, it will still fill the canvas without being stretched. Now you can simply make the image point at an svg document instead whenever you want, meaning that you get a fully scalable result.
To get the blurred rounded rect you have to use SVG filters and clipping. Start by defining the clip region, which is a rounded rect, inside a defs
element, to be called upon as necessary:
<clipPath id="clip">
<rect id="blurrect" x="-10%" y="25%"
width="55%" height="60%" rx="20"/>
clipPath>
This is as simple as drawing any other SVG graphic; the only difference is that you have wrapped it inside a clipPath
element. This defines a clipping region that we can use on other elements. Next, create the blur filter as follows, again adding it inside the defs
element:
The filter is doing the following:
clip
SourceGraphic
) and names it blur
Here's how to create the clip-path and filter - create this below the code in the previous step:
The clip-path refers to the clipPath element created in a previous step. It is applied to a group so that the filter can first be applied to the image. If the clip-path is applied to the image then the filter result may get clipped - sometimes that's desirable, sometimes not. The entire group here should be added directly after the background image in the previous step. This is the "blurred rounded rect" effect and you want it drawn on top of the background image. Note that it looks like the background image is drawn twice, but this is actually intentional. By clipping the filter a bit we get better performance since that means we won't have to recalculate the filter as often when we do the hover effect (it means that we can draw the cheaper non-filtered image on those parts that are outside of the blurred rect). image-rendering="optimizeSpeed"
means you can draw the image quicker, and since we're filtering it won't show anyway. preserveAspectRatio="xMidYMid slice"
means you can use a background image that is not of the same aspect ratio as the svg area that you need; this means you can cover the entire area defined by the width
and height
attributes.
The image element uses the filter (filter="url(#blurpane)")
, and the group clips the result to the shape defined by (clip-path="url(#clip)")
; finally we add a drop shadow to that end result - (filter="url(#dropshadow)")
.
Now you have added a drop-shadow filter on the blurred region. The drop-shadow filter has its filter region limited so that we don't spend time filtering regions that aren't interesting; add this below the previous snippet:
The drop-shadow filter works like so:
A tip for visualising the filter region is to make a simple filter and use a feFlood
element to fill the region. It should furthermore be noted that I make no claims that the provided filter-chains are optimal - in fact I'm fairly sure they could be improved.
Add some text for the blurred rect, plus a gradient fill and a drop-shadow like so (add this below the previous code snippet)
SVG Example Shiny new web standards for all!
The gradient used here is nothing special, but I've included it here anyway along with a drop-shadow filter that we also apply on the text. Add the following snippet inside the defs
element.
Now you will add the hover effect, a blurred ellipse with clipping. This reuses some of the code you already have. Add the following at the bottom of the file:
There is a bit of style on the element itself here, but most of it is going to be put into the style
element - add the following in just before the closing style
tag:
#hover { fill:#5c94c5; filter:url(#bigblur); clip-path:url(#cliphover); }
The ellipse is clipped to the rect shape of the buttons.
Since this is SVG you can play with the result easily, and add dynamic effects. Add a hover effect by adding the following script
block inside the defs
element:
These functions are called on mouseover
on a few interesting areas. These areas can be defined separately from the actual graphic elements used (in this case the text labels). I've defined a few rectangles for regions that looked suitable for the mouseover
effects. The rects are invisible and exist just to listen to the events.
Note that if your browser doesn't support the SVG Basic 1.1 filters the example will display a static JPEG image instead. This uses a two-level fallback. First off it's using , then it's also adding a switch inside the SVG so that an SVG browser will show the same JPEG if the 1.1 basic filter feature is not supported. Here's what that looks like:
... main content of the svg ...
As of this moment, this only seems to be supported completely in Opera. Firefox 3 renders it strangely, and Safari and IE doesn't support it at all.
You can click here and select Ctrl/Right-click > Source
to see the source of the SVG.
Update, January 2010: There is also a Firefox-friendly version of the menu available, which doesn't have the same dependencies as the original.
Using modern web standards it's easy to create nice effects that were previously only possible in photo editing applications, such as Photoshop. By using stylesheets we can change the look of the graphics without needing to go back to an image editing application to re-color and re-export them and then fix all links to point to the new images. Instead we might decide that blue was a sucky color, and just add a style rule for changing that to a fresh lime green. No other changes required! If you don't fancy learning SVG techniques like this, then fine - while you're still stuck doing all that Photoshopping I'll just grab a beer in the mean time while the browser does the job for me instead! Mmm...beer.
Some other small points to consider are that it is also really easy to update changes in the text content, without having to mess with the graphics; also, being vector-based, it resizes very comfortably, without distortion.
This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.
The forum archive of this article is still available on My Opera.