Recreating the Windows Aero Desktop in HTML/CSS/JS
Hi everyone, it’s been a long time since I’ve written a tutorial. Today I’m gonna bring you a new one and it’s the biggest of them all: We are going to recreate the Windows Aero Desktop using HTML, CSS, jQuery and jQuery UI
This demo is not compatible with Internet Explorer!!!
This is not gonna be a perfect copy, I had to exclude a lot of features because this tutorial was already taking too long to finish, also some features such as Program Preview and the blurring behind the glass are impossible in HTML as far as I know (I tried making the blurring using the feGaussianBlur SVG filter, but it seems it doesn’t work in browsers when you use the attribute in=”BackgroundImage”, which is what we need)
Here is the features I had to cut due to time constraints: (I might do the later depending on how well this tutorial is received)
- Internet Explorer 8+ support
- Animated Sortable Programs in Taskbar
- Multiple Program instances per Icon in Taskbar
- Start Menu
- Desktop Icons
Note: It was a lot of work to make this tutorial: Coding everything, Creating all images in Inkscape, writing this post. If you plan on using this tutorial or the images in an open-source project, such as a jQuery plugin, please give me credit with a link to this post. Thanks!
Anyway, enough talking, let’s start the tutorial. First download yourself a copy of jQuery and jQuery UI with the Core, Draggable, Resizable, Sortable and Effects Core components. We will use them in the tutorial
As always, we start by defining our HTML code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <div id="desktop"> <div class="preload"></div> <div class="windows"></div> <div class="wallpaper"> <img src="images/wallpaper.jpg"/> </div> <div class="taskbar"> <ul class="apps"> <li data-app-id="1" data-app-name="Internet Explorer"><img src="icons/ie.png"/></li> <li data-app-id="2" data-app-name="Mozilla Firefox"><img src="icons/firefox.png"/></li> <li data-app-id="3" data-app-name="Google Chrome"><img src="icons/chrome.png"/></li> <li data-app-id="4" data-app-name="Opera"><img src="icons/opera.png"/></li> <li data-app-id="5" data-app-name="Inkscape"><img src="icons/inkscape.png"/></li> <li data-app-id="6" data-app-name="GNU Image Manipulation Program"><img src="icons/gimp.png"/></li> <li data-app-id="7" data-app-name="About"><img src="icons/about.png"/></li> </ul> </div> <div class="contents"> <div data-app-id="1"> <iframe src="bsod.html"></iframe> </div> <div data-app-id="2"> <iframe src="http://www.firefox.com"></iframe> </div> <div data-app-id="3"> <iframe src="http://www.google.com/chrome/intl/en/more/index.html"></iframe> </div> <div data-app-id="4"> <iframe src="http://www.opera.com"></iframe> </div> <div data-app-id="5"> <iframe src="http://www.inkscape.org"></iframe> </div> <div data-app-id="6"> <iframe src="http://www.gimp.org"></iframe> </div> <div data-app-id="7" data-glass-only="true"> <p>Windows Aero HTML Demo</p> <p>by Victor Cisneiros</p> <p>See the tutorial here: <a href="http://www.victorcisneiros.com">http://www.victorcisneiros.com</a></p> </div> </div> </div> |
The #desktop div is where we will call our desktop() jQuery function, it contains a .preload div that is invisible to the user and will be used to load images before they appear on the screen. We don’t want the user waiting for the images to load when he first opens a window.
The .windows div is where all the desktop windows will be stored.
The .wallpaper div will cover the entire screen and will show the windows wallpaper.
The .taskbar is where we’ll store all the icons, notice that I’m using attributes with the data prefix: they are custom HTML5 attributes that are invisible to the user and can be used to store private data that can be accessed via javascript.
Finally we have the .contents div that stores the initial content of the windows of each app in the taskbar, notice that it has the data-app-id attribute just like the icons in the taskbar, this app-id is what will link an icon to it’s respective content. The data-glass-only indicates that this window’s content will be transparent, it will not be covered by a white background
Now let’s do some CSS coding, first thing we will do is set the wallpaper to ocupy the whole screen. We do that by setting position: absolute, width: 100% and height: 100%
1 2 3 4 5 6 7 8 9 | body { font-family: Arial, Helvetica, sans-serif; margin: 0; padding: 0 } .preload, .contents { position: absolute; left: -9999px; top: -9999px } .contents iframe { width: 800px; height: 600px } .contents div { display: inline-block } .wallpaper img { position: absolute; top: 0; left: 0; width: 100%; height: 100% } .windows { position: absolute; width: 100%; height: 100%; overflow: hidden; left: 0; top: 0 } |
Notice that we are not using display: none to hide the .preload div. That’s because we want the browser to think that the .preload is visible, so we change it’s position to -9999px relative to the document. Same for .contents. We then set a default width and height for our .contents iframes and set display: inline-block on the divs because we don’t want them to ocupy the whole line, like block elements do
The .windows div has overflow set to hidden because we don’t want scrollbars to appear when the user moves the window out of the desktop area
Now let’s style the taskbar
1 2 3 4 | .taskbar { background: url('images/taskbar.png') repeat-x; position: absolute; height: 40px; width: 100%; bottom: 0; left: 0; z-index: 99999999 } .apps { margin: 0 0 0 60px; padding: 0; list-style: none; height: 40px } .apps li { background: url('images/sprites.png') repeat scroll 0 0 transparent; background-position: 0 0; color: #fff; font-size: 12px; font-weight: bold; float: left; width: 60px; height:40px } .apps li img { vertical-align: top; margin: 4px 14px 0 } |
Code is simple, we use position: absolute to position the taskbar relative to it’s first parent element that has a position other than static (in this case, the document). We use a really high value for z-index so that the taskbar stays above everything. And then we set float: left to the li so that they stay each beside the other, instead of below
We are ready to do some Javascript coding. Start by adding this to the end of the body
1 2 3 4 5 | <script type="text/javascript"> $(document).ready(function() { $('#desktop').desktop(); }); </script> |
Now let’s write our desktop() function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | (function($) { $.fn.desktop = function() { $(this).each(function() { $(this).addClass('desktop'); $(this).find('.wallpaper, .taskbar').disableSelection(); $(this).find('ul.apps').sortable({ axis: 'x', revert: 250, distance: 5 }); var preload = $(this).find('.preload'); $(preload).append('<img src="images/glass.png"/>'); $(preload).append('<img src="images/glass_inactive.png"/>'); $(preload).append('<img src="images/window.png"/>'); $(preload).append('<img src="images/window_inactive.png"/>'); $(preload).append('<img src="images/window_maximized.png"/>'); $(preload).append('<img src="images/glow_left.png"/>'); $(preload).append('<img src="images/glow_background.png"/>'); $(preload).append('<img src="images/glow_right.png"/>'); }); }; $.fn.disableSelection = function() { return $(this).attr('unselectable', 'on').css({ MozUserSelect: 'none', KhtmlUserSelect: 'none' }).bind('selectstart', function() { return false }); }; })(jQuery); |
In case you are wondering the first and last lines are the declaration of an anonymous function and invocation of it passing the jQuery object as argument, we then use the jQuery object as the $ parameter. This is a common practice when writing jQuery plugins because some others libraries also use the $ object, and here it is defined only inside our anonymous function
Our desktop() function iterates over each matched element (in our case, only the #desktop div), adds the class .desktop to it, then disables selection on both .wallpaper and .taskbar. The disableSelection is a method that we define below, it makes the wallpaper not able to be selected and dragged. Another useful case for disableSelection would be when dragging a div we would want the text contained on it not be able to be selected. There is a disableSelection that comes with jQuery UI that does that, but it was not working in Chrome so that is why defined our own
Then we call the sortable function on our ul.apps in the taskbar. the sortable() is a function that comes in jQuery UI that allows a group of DOM elements to be sorted by dragging it’s elements. The axis: ‘x’ parameter makes the items be able to be dragged only horizontally. The revert: 250 parameter makes the dragged element revert to it’s new position with a smooth animation that takes 250 milliseconds to complete. The distance: 5 is the minimum number of pixels that the mouse needs to move before the sort starts
Run the page, it should be looking like this now:

Try dragging the icons on the taskbar now, you’ll see that they are now sortable. The only thing lacking is the smooth animations when sorting like the Windows 7 taskbar. I tried doing it with jQuery UI during a whole day but was unable to make a decent solution, so I will leave this for the future
The last thing we did was append some images to the .preload div so that they start loading now
What we gonna do now is change the background of the taskbar buttons when the user hovers the mouse on them or clicks. For this we are gonna use CSS sprites, notice how the li is using a single image which contains different images as a background

To use CSS sprites we simply change the background-position to -Xpx -Ypx where X and Y are the pixel position where the background starts. The advantage of using one image is that less HTTP requests are required and also that when the background changes it is instantly replaced. If we used 2 background images there would be a delay because the new background would have to load and this is really bad for user experience
Add this CSS
1 2 3 | .apps li.none { background-position: 0 0 } .apps li.hover { background-position: 0 -40px } .apps li.selected { background-position: 0 -80px } |
And create these 2 new functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $.fn.sprites = function() { return $(this).each(function() { $(this).hover(function() { $(this).removeSpritesClasses().addClass('hover'); }, function() { $(this).removeSpritesClasses().addClass('none'); }); $(this).mousedown(function() { $(this).removeSpritesClasses().addClass('selected'); }); $(this).mouseup(function() { $(this).removeSpritesClasses().addClass('hover'); }); }); }; $.fn.removeSpritesClasses = function() { return $(this).removeClass('hover').removeClass('none').removeClass('selected'); }; |
The first function iterates over each matched elements and sets the hover event to remove all of our sprite classes (hover, none, selected) and add the hover class when the mouse enters the element and to set the class to none when the mouse exits the element. The function also sets the mousedown event to set the class to selected and the mouseup to set the class to hover
Add this to our desktop function, below the preloads
1 | $(this).find('.apps li').sprites(); |
We are now gonna add the code to start the application when the user clicks the taskbar icon, but it will lack the actual app window for now. Add this below the code above
1 2 3 4 5 6 7 8 9 | $(this).find('.apps li').click(function(e) { if (e.which == 1 && $(this).css('position') != 'absolute') { if (!$(this).hasClass('active')) { $(this).startApp(); } else { // nothing for now } } }); |
And then define the needed functions
1 2 3 4 5 6 7 8 | $.fn.startApp = function() { html = '<div>' + $(this).html() + '<span>' + $.maxLength($(this).attr('data-app-name')) + '</span></div>'; $(this).removeClass().addClass('active').html(html).find('div').sprites(); }; $.maxLength = function(text) { return text.length > 12 ? text.substring(0, 12) + '...' : text; }; |
The start app function adds the active class to the li, replaces its content with a div that wraps the old content plus a span with the app name (The app name is retrieved by calling the jQuery attr function which returns the value of the attribute passed as parameter). The function then finds our newly inserted div and calls our sprites function on it, so that it changes the background when it is hovered and clicked
Add this new CSS code
1 2 3 4 5 6 7 8 | .apps li.active { background: none; width: 160px; white-space: nowrap } .apps li.active div { background: url('images/sprites.png') no-repeat; background-position: -60px 0; height: 40px } .apps li.active span { cursor: default; display: inline-block; margin: 12px 0 0 -5px } .apps li.active div.none { background-position: -60px 0 } .apps li.active div.hover { background-position: -60px -40px } .apps li.active div.selected { background-position: -60px -80px } .apps li.active div.selected img { margin: 5px 14px 0 15px } .apps li.active div.selected span { margin: 13px 0 0 -4px } |
The li.active now has no background, instead, the children div will have. The white-space: nowrap is inserted so that app name won’t break into new lines if it has spaces on it. The display: inline-block is inserted on the span to allow an inline element (a span) to have the margin set.
Run the page now, clicking on a desktop icon will change it’s background and show the app name

Now let’s add the code to actually show the Window. First declare this variable (the backslashes are there to make multi-line strings):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var windowHTML = '<div class="window">\ <div class="title"><span><span class="start"></span><span class="text"></span><span class="end"></span></span>\</div>\ <div class="buttons">\ <div class="minimize"><div><span></span></div></div>\ <div class="maximize"><div><span></span></div></div>\ <div class="close"><div><span></span></div></div>\ </div>\ <div class="glass">\ <div class="background"></div>\ </div>\ <div class="container">\ <div class="outer_border">\ <div class="content"></div>\ </div>\ </div>\ </div>'; |
Then add this code to the startApp function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var window = $(windowHTML); $(this).parents('.desktop').find('.windows').append(window); var content = $('.contents div[data-app-id=' + $(this).attr('data-app-id') + ']'); var width = $(content).width() +44; var height = $(content).height() + 62; var wallpaper = $(this).parents('.desktop').find('.wallpaper img'); if ($(content).attr('data-glass-only') == 'true') $(window).addClass('glass_only'); $(window).attr('data-app-id', $(this).attr('data-app-id')); $(window).find('.title .text').text($(this).attr('data-app-name')); $(window).find('.content').append($(content).html()); $(window).width(width).height(height).css({ left: ($(wallpaper).width() - width) / 2, top: ($(wallpaper).height() -40 - height) / 2 }); |
First we create a DOM object for the window using our html code defined before, we then append this new window to the .windows div of our deskto
We then need to retrieve the default contents for our window. We do that by selecting the div inside .contents that has the same data-app-id as this (our li). We calculate the size of the window (+44 is because 20 + 20 pixels from the .container border + 1 + 1 from the .outer_border and + 1 + 1 from the .content border. +62 is the same + 18 from .container padding-top) and then search for the image of the desktop. If the li has the attribute data-glass-only we add a class glass_only to the div. We then set the data-app-id attribute on the window to the same data-app-id of the calling li, we will need this information later
Next we fill the span .title .text in the window with the attribute data-app-name from the li. Then we copy the html of our selected content to the .content div of our window. We then set the window left and top positions so that it stays at the center of our wallpaper. The -40 value is to ignore the taskbar part of our wallpaper
Now we need some CSS to style the window. Add this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | .window { z-index: 1; position: absolute } .window .title { margin-right: 140px; position: relative; height: 30px; top: 10px; left: 15px; z-index: 1; font-size: 13px; overflow: hidden /*; text-shadow: 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff */ } .window .title span { cursor: default; vertical-align: middle; white-space: nowrap; display: inline-block; height: 30px } .window .title span.text { background: url('images/glow_background.png'); display: inline-block; line-height: 30px } .window .title span.end { background: url('images/glow_right.png') no-repeat; display: inline-block; width: 10px } .window .title span.start { background: url('images/glow_left.png') no-repeat; display: inline-block; width: 10px } .window .buttons { position: absolute; height: 20px; top: 11px; right: 19px; z-index: 10 } .window .glass { position: absolute; top: 0; left: 0; width: 100%; height: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box; padding: 12px } .window .glass .background { background: url('images/glass.png'); height: 100% } .window .container { -webkit-border-image: url('images/window.png') 20 repeat; -moz-border-image: url('images/window.png') 20 repeat; border-image: url('images/window.png') 20 repeat; padding-top: 18px; border-width: 20px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box; width: 100%; height: 100%; position: absolute; left: 0; top: 0 } .window .container .outer_border { width: 100%; height: 100%; background: #fff; border: 1px solid #bddaed; -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box } .window .container .content { overflow: hidden; width: 100%; height: 100%; border: 1px solid #1d3e58; -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box; } .window .container iframe { border: 0; width: 100%; height: 100% } .glass_only .container .outer_border { border: 1px solid transparent; background: transparent } .glass_only .container .content { border: 1px solid transparent } .glass_only .container .content p { margin: 10px } |
First thing we do is set the .window position to absolute to make each window position relative to the document. The z-index: 1 property is used to each window appear in front of the wallpaper (which has the default z-index: 0)
Originally I had the glow in the window’s title made using multiple text-shadow properties, but this was making the animation run not as smooth as possible, not to mention Internet Explorer doesn’t support text-shadow, so I would need to get rid of these text-shadow if I decided to implement IE support. Because of this I decided to use background images for the glow. You can see there’s a span for the start of the glow, one for the text and one for the end
The window div contains two divs, one for the glass background (named .glass) and another for the content (named .container). Both should be positioned at the same location, so we use position: absolute and left: 0; top: 0 so they start at the beginning of our window. Since .container is declared later it will stay above the glass. You’ll also notice that they are using the box-sizing: border-box CSS property, that’s because they have width: 100% and so any additional border or padding will make it’s width go beyond 100%. To make the padding and border count on the width we use this CSS3 box-sizing property
Another CSS3 property we are using is the border-image: url(‘images/window.png’) 20 repeat. Basically it divides our image into a grid of 9 images each with a size of 20pixels, and then uses these images as the borders of our div. Repeat is there to repeat the background of our top/bottom/left/right borders which can be much longer than the image. Below is a picture of our image. Notice how the interior of the box only starts at 12px inside. That is why the .glass div has a padding of 12px

Run the page now and click on a program, the window will appear:

Let’s make the minimize, maximize and close buttons now. On a first look it seems simple to implement, but is quite complicated because the glows when the user hover a button overlap the other buttons. Look back at our sprites image and see how each button image is bigger than the actual button because of the glow
If you look at our window HTML code you’ll notice each button is formed by a div with the button class, another div inside it (that will have our hover/selected/none sprite classes applied to it) and a span. The sprite div will have a size equal to the whole button image with the glow and each div will overlap the others. The span will have the size of the button without the glow and will thus be the area that responds to the mouse events
Add this line our window creation code (the latest javascript code we created)
1 | $(window).find('.minimize div span, .maximize div span, .close div span').sprites(true); |
Notice we are using our same sprites function, but this time with a boolean parameter. This parameter will indicate that the sprite classes (hover/selected/none) will be applied to the parent element instead of our spans
Change our sprites function to this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $.fn.sprites = function(parent) { return $(this).each(function() { var element = parent ? $(this).parent()[0] : this; $(this).hover(function() { if (!$(element).hasClass('button')) $(element).removeSpritesClasses().addClass('hover'); }, function() { if (!$(element).hasClass('button')) $(element).removeSpritesClasses().addClass('none'); }); $(this).mousedown(function() { if (!$(element).hasClass('button')) $(element).removeSpritesClasses().addClass('selected'); }); $(this).mouseup(function() { if (!$(element).hasClass('button')) $(element).removeSpritesClasses().addClass('hover'); }); }); }; |
It’s the same function as before, except now we check for the boolean parameter and if true we will select the parent of the element (the parent() function actually returns a jQuery object that can wrap many elements, to select the first DOM object we need to use [0])
Now all we need is some CSS code and the buttons are ready!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | .window .minimize div { background: url('images/sprites.png'); background-position: -220px 0; position: absolute; width: 66px; height: 64px; right: 58px; top: -22px } .window .minimize div span { position: absolute; z-index: 2; display: inline-block; width: 30px; height: 20px; left: 18px; top: 22px } .window .minimize div.none { z-index: auto; background-position: -220px 0 } .window .minimize div.hover { z-index: 1; background-position: -220px -64px } .window .minimize div.selected { z-index: 1; background-position: -220px -128px } .window .maximize div { background: url('images/sprites.png'); background-position: -286px 0; position: absolute; width: 66px; height: 64px; right: 30px; top: -22px } .window .maximize div span { position: absolute; z-index: 2; display: inline-block; width: 26px; height: 20px; left: 20px; top: 22px } .window .maximize div.none { z-index: auto; background-position: -286px 0 } .window .maximize div.hover { z-index: 1; background-position: -286px -64px } .window .maximize div.selected { z-index: 1; background-position: -286px -128px } .window .restore div { background: url('images/sprites.png'); background-position: -352px 0; position: absolute; width: 66px; height: 64px; right: 30px; top: -22px } .window .restore div span { position: absolute; z-index: 2; display: inline-block; width: 26px; height: 20px; left: 20px; top: 22px } .window .restore div.none { z-index: auto; background-position: -352px 0 } .window .restore div.hover { z-index: 1; background-position: -352px -64px } .window .restore div.selected { z-index: 1; background-position: -352px -128px } .window .close div { background: url('images/sprites.png'); background-position: -418px 0; position: absolute; width: 74px; height: 64px; right: -11px; top: -22px } .window .close div span { position: absolute; z-index: 2; display: inline-block; width: 49px; height: 20px; left: 13px; top: 22px } .window .close div.none { z-index: auto; background-position: -418px 0 } .window .close div.hover { z-index: 1; background-position: -418px -64px } .window .close div.selected { z-index: 1; background-position: -418px -128px } |
Notice we are using position: absolute to position each div because they overlap each other. Same for the spans. Another thing worthy noting is that the currently hovered/selected div has z-index: 1, this is to make it appear in front of the other divs. The .none div has z-index set to auto to reset a previous z-index that had been set to 1
Run the page now, hovering on the buttons now will change it’s background

Now it is time to actually set the events of the button we created. Add this to our window creation code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $(window).find('.minimize span').click(function() { $(window).minimizeWindow(); }); $(window).find('.maximize span').click(function() { if ($(this).parent().parent().hasClass('maximize')) { $(window).maximizeWindow(); } else if ($(this).parent().parent().hasClass('restore')) { $(window).restoreWindow(); } }); $(window).find('.close span').click(function() { $(window).closeWindow(); }); |
Now let’s define the 4 functions above, and some others
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | $.fn.maximizeWindow = function() { var wallpaper = $(this).parents('.desktop').find('.wallpaper img'); $(this).resizable('disable'); $(this).saveSize('old'); $(this).css('left', '-20px').css('top', '-12px').width(wallpaper.width() + 40).height(wallpaper.height() -6); $(this).find('.maximize').removeClass('maximize').addClass('restore'); return $(this).removeClass('minimized').addClass('maximized'); }; $.fn.minimizeWindow = function() { var wallpaper = $(this).parents('.desktop').find('.wallpaper img'); var button = $(this).parents('.desktop').find('.apps li[data-app-id=' + $(this).attr('data-app-id') + ']'); $(this).saveSize(); $(this).stop().animate({ left: $(button).offset().left -60, width: 280, height: 120, top: $(wallpaper).height() - 150, opacity: 0 }, 500, function() { $(this).hide(); }); return $(this).addClass('minimized'); }; $.fn.restoreWindow = function() { $(this).resizable('enable'); $(this).css('left', $(this).attr('data-old-left') + 'px').css('top', $(this).attr('data-old-top') + 'px'); $(this).width($(this).attr('data-old-width')).height($(this).attr('data-old-height')); $(this).find('.restore').removeClass('restore').addClass('maximize'); return $(this).removeClass('maximized'); }; $.fn.closeWindow = function() { var button = $(this).parents('.desktop').find('.apps li[data-app-id=' + $(this).attr('data-app-id') + ']'); $(button).html($(button).find('img').outerHTML()).removeClass('active'); $(this).remove(); }; $.fn.showWindow = function() { $(this).show().stop().animate({ left: $(this).attr('data-left'), width: $(this).attr('data-width'), height: $(this).attr('data-height'), top: $(this).attr('data-top'), opacity: 1, }, 500); return $(this).removeClass('minimized'); }; $.fn.saveSize = function(prefix) { var prefix = prefix ? prefix + '-' : ''; if (!$(this).is(':animated')) { $(this).attr('data-' + prefix + 'left', $(this).offset().left); $(this).attr('data-' + prefix + 'top', $(this).offset().top); $(this).attr('data-' + prefix + 'width', $(this).width()); $(this).attr('data-' + prefix + 'height', $(this).height()); } return $(this); }; $.fn.outerHTML = function() { return $(this).clone().wrap('<div></div>').parent().html(); }; |
The maximizeWindow function first finds the desktop wallpaper (to use it’s height), then disables the resizable behavior on it, calls the saveSize function with a prefix = ‘old’. The saveSize function saves the size and position of a window inside some custom data attributes. The prefix old here is used to differentiate this size (which represents the size of the window before being maximized) from other sizes that will be saved later
Next we find the .maximize button and change it’s class to .restore to change the button to a restore window button. Last thing we do is remove the .minimized class on the window (if present) and add the .maximized class to indicate the window has been maximized
The minimizeWindow function finds the desktop wallpaper, finds the correspoding li in the taskbar. Saves the current size and position of the window. Then it calls the jQuery animate function. This function receveices a hash of parameters representing the CSS properties this function will animate to. The second parameter is the number of milliseconds this animation will take to complete and the third parameter is a callback the animate function will call when the animation is completeded. What all of this will do is shrink the window and move it to the position of our li while also making it transparent. The last thing we do is remove the maximized class if present and adding the minimized class
The restoreWindow is simple, it reverts the size and position of the window back to the values stored in our custom data attributes with the prefix old. It then changes the restore window to a maximize window and then removes the .maximized class from the window
The closeWindow finds the corresponding li and removes the class active from it. It also changes it’s content to include only the outerHTML of it’s image tag, and finally it removes the window from the document
The showWindow will restore the window back to it’s position and size when it has been minimized and the user clicks on the icon on the taskbar. Remember our (// nothing for now) comment on the desktop function? Replace it with this:
1 2 | var window = $(this).parents('.desktop').find('.window[data-app-id=' + $(this).attr('data-app-id') + ']'); $(window).hasClass('minimized') ? $(window).showWindow() : $(window).minimizeWindow(); |
Run the code now, you should be able to maximize, minimize, restore and close windows
Let’s now make them able to be dragged and resizable, for this we will use jQuery UI. Add this to our window creation code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $(window).draggable({ cancel: '.content., .buttons', iframeFix: true, start: function(event, ui) { $(ui.helper).restoreWindow(event, ui); }, drag: function(event, ui) { $(ui.helper).glassPosition(); } }); $(window).resizable({ handles: 'all', iframeFix: true, stop: function(event, ui) { $(ui.helper).saveSize().saveSize('old'); }, resize: function(event, ui) { $(ui.helper).glassPosition(); } }); $(window).saveSize().saveSize('old').glassPosition(); |
We are calling jQuery UI draggable function passing ‘.content, .buttons’ as the cancel parameter. This prevents the user from dragging the window when he is pressing the mouse on these divs. When the drag starts we restore the window size in case it is maximized. When dragging we call the glassPosition function which will be responsible for making the glass background remain static while the window are dragged. This is the code:
1 2 3 4 5 | $.fn.glassPosition = function() { var top = -1 * $(this).offset().top -13; var left = -1 * $(this).offset().left - 13; return $(this).find('.glass .background').css('background-position', left + 'px ' + top + 'px'); }; |
Basically what the function does is make the glass background always start from the start of the desktop, independently of the window position. The -13 value corrects the following bug which I don’t know why happens when the window is on the top edge of the desktop

We are also using the iframeFix parameter in both draggable and resizable. What this does is insert a div in front of the iframes to prevent the iframe from capturing the mouse while we are dragging and resizing. But there is one problem: resizable doesn’t actually come with this option so we will include it ourselves
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $.ui.plugin.add("resizable", "iframeFix", { resize: function(event, ui) { $("div.ui-resizable-iframeFix").remove(); var o = $(this).data('resizable').options; $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { $('<div class="ui-resizable-iframeFix" style="background: #fff;"></div>') .css({ width: $(this).width() +"px", height: $(this).height() +"px", position: "absolute", opacity: "0.001", zIndex: 99999999 }) .css($(this).offset()) .appendTo("body"); }); }, stop: function(event, ui) { $("div.ui-resizable-iframeFix").each(function() { this.parentNode.removeChild(this); }); } }); |
I got this code from the internet (with some changes made by me). Like I said, what it does is add and div in front of the iframe when the user is resizing and remove the div when the user stop resizing
Run the page now, you should be able to drag and resize the windows now
You’ll notice the resizable handlers are not positioned correctly on our borders, that’s because part of our border are transparent. Add this CSS to fix this
1 2 3 4 5 6 7 8 9 10 | .window .ui-resizable-se { background-image: none } .window .ui-resizable-handle { z-index: 5 } .window .ui-resizable-n { top: 4px; height: 10px } .window .ui-resizable-s { bottom: 4px; height: 10px } .window .ui-resizable-e { right: 4px; width: 10px } .window .ui-resizable-w { left: 4px; width: 10px } .window .ui-resizable-se { right: 4px; bottom: 4px; width: 20px; height: 20px } .window .ui-resizable-sw { left: 4px; bottom: 4px; width: 20px; height: 20px } .window .ui-resizable-ne { right: 4px; top: 4px; width: 15px; height: 20px } .window .ui-resizable-nw { left: 4px; top: 4px; width: 20px; height: 20px } |
If you maximize the window of our About program you’ll see that the glass background is not correctly positioned. We need to call our glassPosition function. Change our maximizeWindow and restoreWindow to this (notice the glassPosition() at the end of it):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $.fn.maximizeWindow = function() { var wallpaper = $(this).parents('.desktop').find('.wallpaper img'); $(this).resizable('disable'); $(this).saveSize('old'); $(this).css('left', '-20px').css('top', '-12px').width(wallpaper.width() + 40).height(wallpaper.height() -6); $(this).find('.maximize').removeClass('maximize').addClass('restore'); return $(this).removeClass('minimized').addClass('maximized').glassPosition(); }; $.fn.restoreWindow = function() { $(this).resizable('enable'); $(this).css('left', $(this).attr('data-old-left') + 'px').css('top', $(this).attr('data-old-top') + 'px'); $(this).width($(this).attr('data-old-width')).height($(this).attr('data-old-height')); $(this).find('.restore').removeClass('restore').addClass('maximize'); return $(this).removeClass('maximized').glassPosition(); }; |
You’ll notice that when dragging, showing and resizing windows they do not appear in front ot the others. To do this add this variable
1 | var zIndex = 1; |
And add this to our minimize, maximize and show window functions
1 | $(this).css('z-index', zIndex++); |
And finally add this to our window creation code
1 2 3 | $(window).mousedown(function() { $(this).css('z-index', zIndex++); }).css('z-index', zIndex++); |
This way, every new window will have the highest z-index and when the user click on the window it will also set the highest z-index to it
If you maximize a window you’ll notice that it’s bottom border appear behind the taskbar. To remove it we will change the border-image of a maximized window to a image without the bottom border. Add this CSS below everything
1 2 | .maximized .glass { padding: 12px 12px 20px } .maximized .container { -webkit-border-image: url('images/window_maximized.png') 20 repeat; -moz-border-image: url('images/window_maximized.png') 20 repeat } |
If you use Windows 7 you’ll see that when the user clicks on a window or on the wallpaper, all the other windows become inactive, with their background and buttons becoming more transparent. To do this create these functions
1 2 3 4 5 6 7 8 | $.fn.makeOthersInactive = function() { $(this).siblings().makeInactive(); return $(this).removeClass('inactive'); }; $.fn.makeInactive = function() { return $(this).addClass('inactive').find('.minimize div, .maximize div, ,.restore div, .close div').removeClass().addClass('none'); }; |
The makeInactive function adds the class inactive to the window, and then search for the divs from the buttons and removes every sprite class from them and ads the class none
The makeOthersInactive make all the windows inactive, except the current window (this). This is done by calling the jQuery siblings function. The function also removes the class inactive from the current window
Now we need some CSS to style the inactive windows, changing the border to one with less shadow, the background to one more transparent, and changing the buttons background position to match the correct inactive position in the sprites image. Add this to the end of our CSS file
1 2 3 4 5 6 | .inactive .container { -webkit-border-image: url('images/window_inactive.png') 20 repeat; -moz-border-image: url('images/window_inactive.png') 20 repeat; border-image: url('images/window_inactive.png') 20 repeat } .inactive .glass .background { background: url('images/glass_inactive.png') } .inactive .minimize div.none { z-index: auto; background-position: -220px -192px } .inactive .maximize div.none { z-index: auto; background-position: -286px -192px } .inactive .restore div.none { z-index: auto; background-position: -352px -192px } .inactive .close div.none { background-position: -418px -192px } |
Now add this to our desktop function to make all the windows inactive when the user clicks on the wallpaper
1 2 3 | $(this).find('.wallpaper').mousedown(function() { $('.window').makeInactive(); }); |
And change or mousedown event on the window creation code to this
1 2 3 | $(window).mousedown(function() { $(this).makeOthersInactive().css('z-index', zIndex++); }).css('z-index', zIndex++); |
And also add this to the end of our showWindow and minimizeWindow functions
1 | .makeOthersInactive(); |
Run the page now. Clicking on the wallpaper or on another window will make the windows transparent

There’s still one problem with our code. To reproduce it first maximize a window then drag it. It will correctly restore the window but the position will be wrong. To correct this change our restoreWindow function to this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | $.fn.restoreWindow = function(event, ui) { if ($(this).hasClass('maximized') && event && ui) { var ratio = event.pageX / $('.windows').width(); var wallpaper = $(this).parents('.desktop').find('.wallpaper'); if (ratio > 0.5) { var offsetWidth = $(wallpaper).width() - event.pageX; if (offsetWidth < $(this).attr('data-old-width') / 2) { $(this).data('draggable').offset.click.left = $(this).attr('data-old-width') - offsetWidth - 20; } else { $(this).data('draggable').offset.click.left = $(this).attr('data-old-width') * ratio; } } else { if (event.pageX < $(this).attr('data-old-width') / 2) { $(this).data('draggable').offset.click.left = event.pageX + 20; } else { $(this).data('draggable').offset.click.left = $(this).attr('data-old-width') * ratio; } } } $(this).resizable('enable'); $(this).css('left', $(this).attr('data-old-left') + 'px').css('top', $(this).attr('data-old-top') + 'px'); $(this).width($(this).attr('data-old-width')).height($(this).attr('data-old-height')); $(this).find('.restore').removeClass('restore').addClass('maximize'); return $(this).removeClass('maximized').glassPosition(); }; |
Notice we added two parameters (event and ui). They are from jQuery UI. Look back at our draggable call in the window creation code and see how the jQuery UI start callback receives these two parameter. The event parameter is the actual browser mouse event and the ui parameter is a helper that contains some objects like the current dragged window
Our restoreWindow now checks for these parameters, and if the window is maximized and these parameters are not null it does some additional logic to correctly position the window. The logic is kind complicated and I did my best to emulate the Windows 7 behavior. Basically it checks how far the mouse X position is relative to the Wallpaper. If the mouse X is past the middle (0.5) it calculates the difference between the wallpaper width and the mouse X position. If this difference is past half of the old window width (before being maximized) it positions the window in a manner that the distance between the mouse and the end of the window is mantained in the restored window, else it mantains the distance ratio. If the mouse X is before the middle it does the same logic but using the start of the window
One thing that our code is lacking is the ability to maximize windows by double clicking. To do that, add this to our window creation code
1 2 3 4 5 6 7 8 | $(window).dblclick(function() { if (!$(this).hasClass('maximized')) { $(window).maximizeWindow(); } else { $(window).restoreWindow(); } }); |
And finally to finish this tutorial we just need to add some animations to show and close the windows. Right now they just instantly appear and disappear. In Windows 7 the windows fades in or out and shrinks or grow by a small ammount. To do that let’s create these functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $.fn.animatedShow = function() { var offsetLeft = $(this).offset().left; var offsetTop = $(this).offset().top; var windowWidth = $(this).width(); var windowHeight = $(this).height(); $(this).width(windowWidth - 60); $(this).height(windowHeight - 60); $(this).css('left', offsetLeft + 30); $(this).css('top', offsetTop + 30); return $(this).css('opacity', 0).animate({ opacity: 1, left: offsetLeft, top: offsetTop, width: windowWidth, height: windowHeight }, 300, 'easeOutCubic'); }; $.fn.animatedHide = function(callback) { var offsetLeft = $(this).offset().left; var offsetTop = $(this).offset().top; var windowWidth = $(this).width(); var windowHeight = $(this).height(); return $(this).animate({ opacity: 0, left: offsetLeft +30, top: offsetTop +30, width: windowWidth -60, height: windowHeight -60 }, 300, 'easeInCubic', callback); }; |
The animatedShow shrinks the window and expand back to it’s original size with an animation. The ‘easeOutCubic’ parameter is a jQuery UI easing function that starts fast and becomes slow when the animation is about to end. To view a list of all jQuery UI easings available click here. The animatedHide shrinks the window by a small ammount using an animation. The ‘easeInCubic’ is an easing that starts slowly and gets faster by the end
Add this to the end of our window creation code
1 | $(window).animatedShow(); |
And in the closeWindow function change $(this).remove() to
1 2 3 | $(this).animatedHide(function() { $(this).remove(); }); |
Thats it! The tutorial is finished!
I hope you were able to learn something new by following this tutorial, if you have any questions post a comment and I will try to answer as soon as possible
And if you want more tutorials check this blog soon because I already have the code ready for the next tutorial, I just need to write the post












