mmarcon | 05 April, 2012 00:13
Web developers hardly have to deal with network-related issues. Most of the time the resources they have to deal with are completely under their control: images are served from a directory in the web server where the web site is hosted and perhaps cached by a content delivery network; CSS files and sprites are where they expect them to be, as well as all the other resources that contribute to the smooth functioning and good looks of their application.
Unfortunately, this is not necessarily the case when scaling up to big and complex systems. At Nokia maps we have a number of non-homogeneus systems and API that we use to retrieve the data to generate maps, routes and city and place pages. Moreover, we often deal with user-generated and 3rd-party-generated content such as reviews and photos. This normally leads to two types of issues: data consistency across different domain and missing resources. In this post I will be focusing on this last problem. More specifically I will describe how we deal with missing recommendation images in City Pages.
When a City Page for a city is generated, one important piece of information that we show to our users is a number of recommended places to visit in such city. Recommendations are then presented in a grid with name, category and a picture of the place, as shown in the picture below.
One problem that we have seen every now and then is that some of these pictures are missing. The complication is that we don't have any control on those resources: when the API we rely on tells us there is a picture for a place, we cannot do anything else but trust it. Unfortunately, sometimes the picture is simply no longer there: either the user that posted it decided to remove it, or the 3rd party provider deleted it or moved it somewhere else. Checking picture by picture in the back-end would slow down the page load quite a bit so for this reason we decided to tackle the problem on the front-end side.
Our template delivered by the server normally contains a list of places, each of them with the related picture or a placeholder for the place category in case there are no picture for that place. The URLs to photo and placeholders are normally inserted into the DOM as data-* attributes and displayed with a simple CSS rule for browsers that support background-size: cover and as an img element for non-modern browsers. This means that if the photo does not exist the user sees an empty grayish box in the best case and the little "missing image icon" in the worst case. This conveys a bad user experience.
The solution is very simple, but at the same time very effective. We basically rely on the browser to determine whether or not an image can be loaded. If the browser can't load it, then it probably does not exist, therefore we show the placeholder. In terms of code, we have the following.
<ul>
<li data-image="http://lorempixel.com/120/100/sports"
data-placeholder="http://placehold.it/120x100"><h3>Sport</h3></li>
<li data-image="http://lorempixel.com/120/100/food"
data-placeholder="http://placehold.it/120x100"><h3>Food</h3></li>
<li data-image="http://lorempixel.com/120/100/people"
data-placeholder="http://placehold.it/120x100"><h3>People</h3></li>
</ul>
ul {
overflow: hidden;
width: 260px;
margin: 20px auto;
}
li {
width: 120px;
height: 100px;
float: left;
background-color: #ccc;
position: relative;
border: 1px solid #999;
margin: 2px;
}
li h3 {
font-family: 'Oleo Script', cursive;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 32px;
font-size: 24px;
line-height: 32px;
background-color: rgba(0,0,0,0.7);
color: #fff;
text-align: center;
}
Again I used jQuery to simplify, but it easy very easy to do this without relying on frameworks.
$(function() {
//Go through all the elements that should have a background
$('li').each(function() {
var $this = $(this),
img = $this.data('image'),
ph = $this.data('placeholder'),
imgObject;
//First of all, by default assign the placeholder image to all of them
//This way if an image fails to load the UI is not affected
$this.css('background-image', 'url(' + ph + ')');
//Now create an empty Image object
imgObject = new Image();
//Should an image fail to load (e.g. 404), replace the image data with the placeholder
//in case we need to use it later
imgObject.onerror = function() {
$this.data('image', ph);
};
//When the image finally loads, replace the background with the final one
imgObject.onload = function() {
$this.css('background-image', 'url(' + img + ')');
};
//Assign the src property: this forces the browser to preload and
//possibly cache the image and eventually either the onload or the onerror
//event will be triggered.
imgObject.src = img;
});
});
The JavaScript code as I said is very simple and it is pretty well commented, so I believe it does not need any further explanation. If you have any questions though, feel free to ask in the comments section.
In the full demo I included an additional list element in the HTML that points to an image with invalid URL. The bottom-right element will therefore show the placeholder. All the code is available as a JSFiddle.
Comments
Re: Reliable Image Replacement
shoesunglasses | 20/04/2012, 00:18
http://www.shoesunglasses.com/