Advertisement

Cross-Browser CSS Reflections, Glows and Blurs

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Reflections can add an interesting dimension to objects, lending a touch of realism and giving them context within their surroundings. Let's take a look at achieving reflections with CSS and we'll examine glowing and cross-browser blurring for good measure too.


Assumptions

Following this tutorial works on a number of assumptions:

  • You know your HTML.
  • You are familiar with CSS and CSS2 selectors.
  • If you want to recreate the example exactly, you should know how box-shadow works. It isn't the key to this tutorial, so you'll need to figure it out yourself. If you need an explanation, I suggest you take a look at CSS3 vs. Photoshop: Rounded Corners and Box Shadows.

Box-reflect vs. 'My Method'

Box-reflect is a CSS property which is meant for precisely this purpose: making reflections. We won't use box-reflect and mask-image, because:

  • At the time of writing both properties are only supported by webkit (Chrome and Safari).
  • The implementation is buggy to say the least.

When Microsoft introduced their filter effects (think of the IE 4 era, nested tables for layout and images for reflections), the filthy filters didn't apply to the whole of any given element. My guess is that they used some kind of boundary box from the top left corner to the bottom right corner of the element, which made sense, because to my knowledge, there wasn't a way to exceed this box. However, they never corrected it.

Now, in the Chrome 17 era, webkit has made the same mistake. Mask images, the key to -webkit-box-reflect, make some kind of dynamic snapshot within the boundary box, pastes it below and changes its opacity. I tried to recreate the demo using -webkit-box-reflect:

Naughty reflections...

I haven't finished it, but the problem is clear. The ETBR has border-radius and box-shadow. The box-shadow is visible within, but not outside the boundary box.

In my method matrix transforms, inset box-shadow and opacity will replace box-reflect and mask-image. The limitations:

  • The biggest limitation is that inset box-shadow doesn't make the reflection fully transparent. It's a combination of real and fake transparency that will work most of the time, but not always.
  • Text in the ETBR isn't affected by inset box-shadow. This happens if the color of the text isn't the same as the background color of the surface.

Fiddle | Full screen

The text color doesn't get darker where the background does. So it seems you can't have a different color and the box-shadow together.


Choices…

While we're on the topic of matrices and reflections..

Awesome reflections...

You take the blue pill – I show you how to recreate the demo word for word. You take the red pill – I'll show you how to make reflections of anything and I'll tell you where you can buy these reflective shades for a very interesting price.

Put simply; some of the following steps are optional, depending on whether you want to recreate the demo pixel for pixel.


Optional: Preparations

The demo starts with an HTML5 template...

index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
		<title>CSS3 Reflections</title>
		<meta name="description" content="Reflection..." />
		<meta name="author" content="Bob de Ruiter" />
		<meta name="viewport" content="width=device-width; initial-scale=1.0" />
		<link rel="shortcut icon" href="/favicon.ico" />
		<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
	</head>
    <body>
	</body>
</html>

...and this CSS file.

style.css

html {
	height: 100%;
}
body {
	text-align: center;
	line-height: 1;
	margin: 0;
	padding: 0;
	height: 100%;
}
p {
	line-height: 1.2;
}

And, of course, we'll add a link to the stylesheet:

	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
		<title>CSS reflections!</title>
		<meta name="description" content="" />
		<meta name="author" content="Bob" />
		<meta name="viewport" content="width=device-width; initial-scale=1.0" />
		
		<link rel="stylesheet" href="style.css" />
		
		<link rel="shortcut icon" href="/favicon.ico" />
		<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
	</head>

2D, but 3D

The demo is 2D. Not a single 3D element. We know it, but our goal is to make them think we built this and then photographed the front view.

The reflection in 3D...

If we want them to believe it's 3D, we should determine how the objects are positioned and rotated in 3D space. The demo has a horizon. The top 10% is black 'sky', the other 90% is a fake 3D plane. The Element to be Reflected (henceforth referred to in our demo as ETBR) and the plane are perpendicular to each other (the angle is 90°), the reflection is to be parallel to the ETBR.


Step 1: Optional The Plane & the ETBR

The HTML:

<body>  
	<div id="plane">
		<header><span id="ETBR">reflection</span></header>
	</div>
</body>

The ETBR is a child of the plane. This way, it remains in the same position seen from the plane and the glow stays in the plane.

body {
	text-align: center;
	line-height: 1;
	margin: 0;
	padding: 0;
	background-color: #000;
	height: 100%;
}
p {
	line-height: 1.2;
}
#plane {
	left: 0;
	top: 10%;
	width: 100%;
	bottom: 0;
	min-width: 1080px;
	min-height: 600px;
	position: absolute;
	overflow: hidden;
	overflow-x: visible;
}

Note:

  • The plane has a min-width and min-height, so the ETBR is always visible.
  • The overflow-y is set to hidden, so the glow of the ETBR stays in the plane. (The sky must be pitch black.)
  • The plane doesn't have a background color, but gets its color from the glow, which doesn't make sense on any level, but looks the best.

Step 2: The Reflection Markup

Locate the object (ETBR), copy it, paste it within itself and rename it to refl(ection):

<span id="ETBR">reflection<span id="refl">reflection</span></span>

Now within the css create the selector of the object. Add #refl to the selector so our styles are applied to both elements:

#ETBR, #refl {
	
}

Basically, the CSS and HTML of the reflection must be the same as the CSS and HTML of the ETBR. We'll position, mirror and prettify the reflection from step 5. But, for the time being, we'll hide the reflection.

#ETBR, #refl {
	
}
#refl {
	display: none;
}

Internet Explorer being Internet Explorer, we have to add some conditional HTML:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
		<title>CSS3 Reflections</title>
		<meta name="description" content="Reflection..." />
		<meta name="author" content="Bob de Ruiter" />
		<meta name="viewport" content="width=device-width; initial-scale=1.0" />
		
		<link rel="stylesheet" href="style.css" />
		
		<link rel="shortcut icon" href="/favicon.ico" />
		<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
	</head>
	<!--[if lt IE 9 ]> <body class="oldie"> <![endif]-->
	<!--[if IE 9 ]> <body class="ie9"> <![endif]-->	
	<!--[if (gt IE 9)|!(IE)]><!--> <body class="modern"> <!--<![endif]-->   
		<div id="plane">
			<header><span id="ETBR">reflection<span id="refl">reflection</span></span></header>
		</div>
	</body>
</html>

You might wonder why, just bare with me. Because you're inpatient, this first Fiddle will be completely black. Do you want to see the influence of the plane? Resize it.

Fiddle | Full screen


Step 3: Optional Basic Styling

This needs no explanation:

#ETBR, #refl {
	color: #004;
	
	font-family: Impact, 'Arial Black', Helvetica, Arial, sans-serif;
	text-transform: uppercase;
	font-size: 200px;
	background-color: #FFE;
	font-weight: bold;
	padding: 30px;
	display: inline-block;
}
#refl {
	display: none;
}

OK, a little explanation. The combination of text-align: center; and display: inline-block; centers an element old skool, but it isn't the holy grail; inline items can't contain block items. I used it, because I didn't want to add thousands of floating containers for this example. Generally though, this is the way to go.

Fiddle | Full screen


Step 4: Optional Rounded Corners & Glow

The glow consists of three shadows: a big, stretched shadow (which looks more like the reflection of the light on the plane), the real glow (a white shadow with a shorter blur range), and a black inset shadow (which makes the ETBR look like it's glowing). As I mentioned before, I'm not going to explain how box-shadow works and border-radius speaks for itself these days.

#ETBR, #refl {
	color: #004;
	
	font-family: Impact, 'Arial Black', Helvetica, Arial, sans-serif;
	text-transform: uppercase;
	font-size: 200px;
	background-color: #FFE;
	font-weight: bold;
	padding: 30px;
	display: inline-block;
	
	box-shadow: rgba(255,255,240,.2) 0 0 200px 100px, rgba(255,255,240,.3) 0 0 40px, inset rgba(0,0,0,.8) 0 0 20px;
	border-radius: 30px;
}

Fiddle | Full screen


Step 5: Reflection Positioning

Empty the #refl selector to make the reflection visible. Since reflections aren't 'real', our reflection won't be part of the document flow; it should float. The obvious choice is to set the position to absolute, which means the element is positioned relative to its first positioned (not static) ancestor element. Because there is no first positioned ancestor of the reflection, it will be positioned relative to the body tag.

It's much easier to position the reflection relative to the real text, which is also an ancestor of the reflection (coincidence?). To do so, we first have to "position" the ETBR:

#ETBR, #refl {
	color: #004;
	
	font-family: Impact, 'Arial Black', Helvetica, Arial, sans-serif;
	text-transform: uppercase;
	font-size: 200px;
	background-color: #FFE;
	font-weight: bold;
	padding: 30px;
	display: inline-block;
	
	box-shadow: rgba(255,255,240,.2) 0 0 200px 100px, rgba(255,255,240,.3) 0 0 40px, inset rgba(0,0,0,.8) 0 0 20px;
	border-radius: 30px;
	
	position: relative;
}

This doesn't actually change the position, but it is positioned. Now:

#refl {
	position: absolute;
	
	top: 100%;
	left: 0;
}

The top of the reflection is relative to the height and position of the ETBR. 0% will give them the same top, 200% will leave a gap as big as the height of the ETBR and 100% puts the top of the reflection at the bottom of the ETBR. Left is the same, but takes the percentage of the width.

Fiddle | Full screen


Step 6: Modern Mirroring, Blurring & Transparency

Almost every commonly used browser has these features, from IE6 to Google Chrome. The implementation is different however. Modern browsers are very straight-forward, for every feature one property: transform, blur and opacity. We'll use box-shadow too, but mirroring first.

Mirroring

#refl {
	position: absolute;
	
	top: 100%;
	left: 0;
	
	-moz-transform: scaleY(-1);
	-webkit-transform: scaleY(-1);
	-ms-transform: scaleY(-1);
}

With transform we can modify an element to every parallelogram you can imagine. A lot of parallelograms, I know. But we only need to mirror the reflection, so we'll leave most transform functions unused.

Any designer should know scaling by -1 on the Y-axis is the same as mirroring. Scaling on the Y-axis is multiplying the distance between the top (and bottom) and the center. If we scale by 2 on the Y-axis, the distance of the top to the center will be twice as big. If you scale by -1, the distance will be the same, but top is where the bottom was and the bottom is where the top was. Congratulations, you know how to mirror!

Blur

All modern browsers will support filters very soon, -webkit-filter or SVG filters. FF, Opera & IE10 already support the latter, Safari and Chrome are just building the tension before releasing it. One of the SVG filters is the good old Gaussian Blur. It's slow, but it's something...

Create a file "filter.svg" in the same folder as style.css. Its content:

<?xml version="1.0" standalone="no"?>
<svg width="1" height="1" version="1.1" 
xmlns="http://www.w3.org/2000/svg" 
xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <filter id="blur">
      <feGaussianBlur in="SourceGraphic" stdDeviation="2 3" />
    </filter>
  </defs>
</svg>

Formalities, except for rules 6 to 8. The filter called "blur" (you may call it anything) blurs 2 pixels horizontally and 3 pixels vertically. Back to the stylesheet!

#refl {
	position: absolute;
	
	top: 100%;
	left: 0;
	
	-moz-transform: scaleY(-1);
	-webkit-transform: scaleY(-1);
	-ms-transform: scaleY(-1);
	
	filter: url(filter.svg#blur); /* FF, IE10 & Opera */
	-webkit-filter: blur(2px);
}

filter.svg#blur means the element 'blur' in filter.svg. If the ID of the filter was 'anything', the url would be filter.svg#anything. The blur radius is defined in the SVG file. Webkit browsers put all of this in one rule.

Transparency

Transparency has been around much longer, so we only have to specify one property:

#refl {
	position: absolute;
	
	top: 100%;
	left: 0;
	
	-moz-transform: scaleY(-1);
	-webkit-transform: scaleY(-1);
	-ms-transform: scaleY(-1);
	
	filter: url(filter.svg#blur); /* FF, IE10 & Opera */
	-webkit-filter: blur(2px);
	
	opacity: .25;
}

I can't put the SVG file in the fiddle. (I must lead by example so I can't mix semantics and presentation. It would make things more complicated anyway...) But I'm not making SVG blur up…

Fiddle | Full screen

It isn't complete. When Apple created the reflection on the third day, it was intended to be less reflective. The reflection should be less clear farther away from the surface. Let there be darkness!

This only works if the surface has a solid color and the font color is similar to this color: we'll darken it by adding another box-shadow. This box-shadow overwrites the old one in the #ETBR, #refl selector. We'll counteract this by redeclaring the inset shadow of the old selector. The second shadow is the new shadow. Remember every shadow we add to the reflection is mirrored.

#refl {
	position: absolute;
	
	top: 100%;
	left: 0;
	
	-moz-transform: scaleY(-1);
	-webkit-transform: scaleY(-1);
	-ms-transform: scaleY(-1);
	
	filter: url(filter.svg#blur); /* FF, Opera + IE10*/
	-webkit-filter: blur(2px); /* webkit */
	
	opacity: .25;
	
	box-shadow: inset rgba(0,0,0,.8) 0 0 20px, inset #000 0 50px 100px; /* first shadow is old */
}

OffsetY of the second shadow is 50 pixels, but the element is always mirrored after the shadow is applied. The shadow doesn't move down, it moves up.

Take a look at this:

Fiddle | Full screen

Great! Now take a look at it with IE8 (or take my word for it): it's basically two copies of the ETBR above one another, which aren't mirrored, transparent or blurred.


Step 7: Filthy Filtering

We added some conditional comments when we started; three groups.

  • The modern browsers and IE10, which don't support the old IE filters (IE10 dropped support), but do support the new ones.
  • IE8 and lower, the oldIEs, support the filthy filters and don't support the new ones.
  • IE9, which supports a little bit of both.

If IE9 didn't support the new stuff, it would be much easier. (Don't get me wrong, I'm happy Internet Explorer is trying to change. But it would be easier...) And to make it more complicated, there are differences between filthy filters in IE9 and IE8. Enough of the complaining, let's do something about it. Add for every group a #refl selector and move the opacity property to modern.

#refl {
	position: absolute;
	
	top: 100%;
	left: 0;
	
	-moz-transform: scaleY(-1);
	-webkit-transform: scaleY(-1);
	-ms-transform: scaleY(-1);
	
	filter: url(filter.svg#blur); /* FF, Opera + IE10*/
	-webkit-filter: blur(2px); /* webkit */
	
	box-shadow: inset rgba(0,0,0,.8) 0 0 20px, inset #000 0 50px 100px;
}
.modern #refl {
	opacity: .25;
}
.ie9 #refl {
	
}
.oldie #refl {
	
}

Filthy filters don't like CSS2 opacity. And speaking of filthy filters, we're going to use these:

  • DXImageTransform.Microsoft.BasicImage enables us to mirror the image and change its opacity
  • DXImageTransform.Microsoft.MotionBlur and DXImageTransform.Microsoft.MotionBlur is awesome.
  • DXImageTransform.Microsoft.Gradient as a replacement for the second box-shadow.

Mirroring

The first filter, BasicImage, actually has the property "mirror"! YES! Unfortunately, setting this property to 1 mirrors the content horizontally, not vertically. But that doesn't mean it's useless for us. Rotating an element 180° and mirroring it horizontally is the same as mirroring it vertically. And this is possible:

.oldie #refl {
	filter: progid:DXImageTransform.Microsoft.BasicImage(mirror=1, rotation=2);
}

This rotation isn't measured in degrees: 0 is 0°, 1 is 90°, 2 is 180° and 3 is 270°. Only the oldies need this. IE9 supports -ms-transform.

Opacity

We can specify the opacity in the same filter...

.ie9 #refl {
	-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(opacity=.25);"
}
.oldie #refl {
	filter: progid:DXImageTransform.Microsoft.BasicImage(mirror=1, rotation=2,  opacity=.35);
}

IE9 (and IE8, but I think Internet Explorer gets enough attention already) supports -ms-filter. The value is one big string. This creates understanding by the other browsers (Firefox goes to a psychiatrist every Friday because of Internet Explorer).

Blur

...but we need a new filter for the blur. Multiple filters, if we want to make it look good. No commas, no filter:s between the filters, just a space or a new line. -ms-filter doesn't get new lines anymore...

.ie9 #refl {
	-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(opacity=.25) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false') progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');"
}
.oldie #refl {
	filter: progid:DXImageTransform.Microsoft.BasicImage(mirror=1, rotation=2, opacity=.35)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false')
	progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');
}

If we blur it once with a 6 pixel radius, it looks like someone took 4 copies of the reflection and moved one 6 pixels above, one 6 pixels below, one 6 pixels to the left and one 6 pixels to the right. If you want to show-off at the pub, you should know the name: Box Blur. Adding iterations (blurring the blurred) makes Box Blur appear like a normal blur. The second iteration is motion blur, because it looks good.

Fiddle | Full screen

This time, not one, but two things are wrong:

  1. The blur moved the reflection a few pixels up and to the right in IE9, but in IE8, it moved the reflection down. Why? It's a mystery.
  2. In IE7 & IE8, the reflection is equally transparent everywhere.

Second problem first: because the oldies don't support box-shadow, we'll use a filthy alpha gradient as replacement. These are, to my knowledge, the first RGBA-like values in CSS, but if the w3c specs said anything at all about rgba values in those days, Internet Explorer didn't listen. They used 8 hexadecimal places instead of 6. The first two represent the alpha; filthy gradients take ARGB instead of RGBA, which, I must admit, sounds much better:

.ie9 #refl {
	-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(opacity=.25) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false') progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');"
}
.oldie #refl {
	filter: progid:DXImageTransform.Microsoft.Gradient(startColorstr=#99000000, endColorstr=#00000000)
	progid:DXImageTransform.Microsoft.BasicImage(mirror=1, rotation=2, opacity=.35)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false')
	progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');
}

The gradient must be blurred too, so that's first. Filthy Filter Gradients are vertical by default. startColorstr is the gradient color at the top and startColorstr the color at the bottom, but it's mirrored! #99000000 is the same as rgba(0, 0, 0, 153). Remember: the higher we set the opacity of the gradient, the lower the opacity of the reflection seems to be.

The positioning is just a matter of trial and error. Because the reflection already has its position set in percentages, we can't adjust it to within the pixel using the left and top properties. Instead, we'll use the margin:

.ie9 #refl {
	margin-top: 20px;
	margin-left: -10px;
	
	-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(opacity=.25) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false') progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');"
}
.oldie #refl {
	margin-top: -20px;
	margin-left: -7px;
	
	filter: progid:DXImageTransform.Microsoft.Gradient(startColorstr=#99000000, endColorstr=#00000000)
	progid:DXImageTransform.Microsoft.BasicImage(mirror=1, rotation=2, opacity=.35)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false')
	progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');
}

Fiddle | Full screen


Step 8: Finishing Touches

We'll change two little things before you go spending your time on something better, like watching sneezing pandas on youtube.

  • Placing the reflection beyond the ETBR (in z-space), because the blurred reflection lays above the ETBR, and above the glow.
  • Move the ETBR down. The top of the ETBR aligns perfectly with the horizon, which looks very strange.

The solutions:

  • Normally, we would set the z-index of the ETBR and the reflection respectively to 2 and 1. But because there are no other elements behind the reflection in this demo (again, in z-space), we can simply set the z-index of the reflection to -1.
  • Add padding to the top of the plane. Since the ETBR is a child of the plane, it will move down.

Here's the CSS file. The final changes are highlighted:

style.css

html {
	height: 100%;
}
body {
	text-align: center;
	line-height: 1;
	margin: 0;
	padding: 0;
	background-color: #000;
	height: 100%;
}
p {
	line-height: 1.2;
}
#plane {
	padding-top: 5%;
	
	left: 0;
	top: 10%;
	width: 100%;
	bottom: 0;
	min-width: 1080px;
	min-height: 600px;
	position: absolute;
	overflow: hidden;
	overflow-x: visible;
}
#ETBR, #refl {
	color: #004;
	
	font-family: Impact, 'Arial Black', Helvetica, Arial, sans-serif;
	text-transform: uppercase;
	font-size: 200px;
	background-color: #FFE;
	font-weight: bold;
	padding: 30px;
	display: inline-block;
	
	box-shadow: rgba(255,255,240,.2) 0 0 200px 100px, rgba(255,255,240,.3) 0 0 40px, inset rgba(0,0,0,.8) 0 0 20px;
	border-radius: 30px;
	
	position: relative;
}
#refl {
	position: absolute;
	z-index: -1;
	
	top: 100%;
	left: 0;
	
	-moz-transform: scaleY(-1);
	-webkit-transform: scaleY(-1);
	-ms-transform: scaleY(-1);
	
	filter: url(filter.svg#blur); /* FF, Opera + IE10*/
	-webkit-filter: blur(2px); /* webkit */
	
	box-shadow: inset rgba(0,0,0,.8) 0 0 20px, inset #000 0 50px 100px;
}
.modern #refl {
	opacity: .25;
}
.ie9 #refl {
	margin-top: 20px;
	margin-left: -10px;
	
	-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(opacity=.25) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false') progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0) progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');"
}
.oldie #refl {
	margin-top: -20px;
	margin-left: -7px;
	
	filter: progid:DXImageTransform.Microsoft.Gradient(startColorstr=#99000000, endColorstr=#00000000)
	progid:DXImageTransform.Microsoft.BasicImage(mirror=1, rotation=2, opacity=.35)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false')
	progid:DXImageTransform.Microsoft.MotionBlur(strength=15, direction=0)
	progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='false');
}

index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
		<title>CSS3 Reflections</title>
		<meta name="description" content="Reflection..." />
		<meta name="author" content="Bob de Ruiter" />
		<meta name="viewport" content="width=device-width; initial-scale=1.0" />
		
		<link rel="stylesheet" href="style.css" />
		
		<link rel="shortcut icon" href="/favicon.ico" />
		<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
	</head>
	<!--[if lt IE 9 ]> <body class="oldie"> <![endif]-->
	<!--[if IE 9 ]> <body class="ie9"> <![endif]-->	
	<!--[if (gt IE 9)|!(IE)]><!--> <body class="modern"> <!--<![endif]-->   
		<div id="plane">
			<header><span id="ETBR">reflection<span id="refl">reflection</span></span></header>
		</div>
	</body>
</html>

filter.svg

<?xml version="1.0" standalone="no"?>
<svg width="1" height="1" version="1.1" 
xmlns="http://www.w3.org/2000/svg" 
xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <filter id="blur">
      <feGaussianBlur in="SourceGraphic" stdDeviation="2 3" />
    </filter>
  </defs>
</svg>

Conclusion

And that's all! This is the final Fiddle, without the SVG filter blur.

Fiddle | Full screen

As something extra, here's a pure CSS (except for the stars in the background) Mac Dock using this technique.

I hope you've enjoyed this tutorial and I hope you learned something new. Feel free to leave any questions in the comments!