Website Thumbnails with CSS

A thumbnail view of a website is meanwhile a common sight. Google has recently introduced it in its search results page, and numerous ad services offer more or less annoying tool tips with page previews.

What all these instances have in common is the need for rendering the webpage in question on the server and displaying the image only to the client. In this article I’ll show a thumbnail technique that works in the browser alone (given some CSS3 support).

The Technique

A thumbnail is a minified version of the original. For long there was no way to minify rendered HTML. However, with the advent of transformations in CSS3 we now have a new tool to achieve this: transform and scale.

#thumb {
transform: scale(.3);
transform-origin: 0 0;
}

The above will render the element with ID #thumb at ⅓ of its original size. The property transform-origin secures the sizing to keep the upper left corner steady.

The trick is now to apply this CSS to an iframe element. The Iframe loads the website to be previewed, and the CSS minifies the graphical representation:

iframe#thumb {
overflow: hidden;
width: 960px;
height: 480px;

border: #49483E solid 1em;
-moz-border-radius: 1em;
-webkit-border-radius: 1em;
border-radius: 1em;

-moz-transform-origin:0 0; -moz-transform: scale(.3);
-webkit-transform-origin:0 0; -webkit-transform: scale(.3);
-o-transform-origin:0 0; -o-transform: scale(.3);
transform-origin:0 0; transform: scale(.3);
}

Since the corresponding CSS3 specifications are not final yet, we have to set the known browser prefixes for the newer CSS properties. The overflow: hidden removes the scrollbars from the Iframe. Width and height are the ones of the original, untransformed viewport.

Enhancing with Javascript

There is one major drawback of the above simple CSS. You will have quite an annoying flash of unstyled content, perhaps accompanied with a quick shift and re-arranging of the surrounding page elements, when the browser finishes scaling and redraws the screen. The second issue goes away, if the thumbnail is positioned absolutely. This is a very common scenario for thumbnails to pop up as a new layer.

We can remedy the first problem, the flashing of the unscaled Iframe, with Javascript quite simply. I’m using jQuery here, but you can easily port that to plain JS.

To start, we will initially set the width and height of the Iframe to the finally desired size (320px and 160px):

  width: 320px;
height: 160px;

Then, when the element is rendered, we resize it to its desired size:

$(document).ready(function () {
$('#thumb').delay(300).css({
'width': '960px',
'height': '480px'
});
});

Technically this is no “fix”, since it doesn’t remove the flash. But this way, the Iframe does never overlay uninvolved content, and the flash is far less annoying.

Auto-Thumbnails

Finally, we will take advantage of the new data-* attributes in HTML5: We will render thumbnails for all elements with an attribute data-thumbnail:

$(document).ready(function () {
$('*[data-thumbnail]').hover(
/* onmouseenter: */ function () {
// remove a possible current thumbnail:
$('#thumb').remove();

var x = 0, y = 0, $this = $(this);
// we get the URL from the data attribute
var url = $this.attr('data-thumbnail');
y = $this.offset().top;
x = $this.offset().left - 330;
// generate the thumbnail
var $thumb = $('<iframe src="'+url+'" id="thumb" '+
'style="position: absolute; '+
'top:'+y+'px;left:'+x+'px;"></iframe>');
$thumb.appendTo('body').delay(500).css({
'width': '960px',
'height': '480px'
});
},
/* onmouseleave: */ function () {
$('#thumb').remove();
}
);
});

One important issue of this solution is, that the Iframe will always produce at least one HTTP request for each displayed thumbnail, fetching the actual page. Compared to the server-side rendering, where only a single request for many screenshots is needed, this can quickly become a problem.

To minimize the load on the server, the above code should be extended by a caching solution. That is, a once generated Iframe shouldn’t be removed but only be hidden and re-displayed, if it already exists.

It Could be more Powerful

The Gecko rendering engine of Firefox fame has a custom method to the canvas element named drawWindow(). The Mozilla Developer Center has an usage example. This method takes a Window object, e.g., from a loaded Iframe or of the current view and puts the rendering onto the canvas.

Unfortunately, this very useful method is only available, when the Javascript runs in chrome, basically the Firefox UI and addons. If this wouldn’t be the case (and the security considerations would be addressed) we could simply store an image of the current window in an HTML5 LocalStorage and access this, when lateron a thumbnail is requested. The server load would be decreased.

A Final Note

What I haven’t talked about so far, is, that the Iframe stays fully functional despite of the CSS transformation. So, clicking and hovering inside the thumbnail will of course activate whatever the mouse or keyboard is focused to.

This can be seen as bug or feature, depending on the point of view. However, layering an almost transparent layer on top remedies most hovering problems.

A bigger problem is autofocus. It may be, that, when the thumbnail is created, the focus is set inside an element of the thumbnail container. If the user was writing away at that moment on the original page, she may be surprised to confused. And the worst case is, of course, unintended disclosure of rpivate information.

Without addressing these issues, the presented solution is not apt for practical usage. However, it has powerful features, like not being restricted by the same-origin-policy. And if drawWindow() would be available in website scope, the above problems would vanish.