simonmadine | 03 May, 2012 09:00
This is the second part of the post on the City Pages header images. If you want to read more about background-size:cover, start there.
Now you have your large, dynamically scaling image, it's time to make it better. You might have noticed that when you scale an image up, it can appear low quality. When images are 'upscaled', it can have a bad effect on your users' perception of your page. Even if the effect is subtle, it can detract from any benefits gained in using background-size: cover. Fortunately, there are a couple of techniques we employ to lessen this problem.
It is possible to specify the algorithm the browser uses when it scales your image. The image-rendering attribute lets you choose, amongst other options, between 'faster, lower quality' and 'slower, higher quality'. In this use-case, we have only a single header image so there won't be a performance hit if we specified the higher quality option. Even though this isn't available in IE, we can use the IE-specific property -ms-interpolation-mode to achieve something similar.
div {
width: 180px;
height: 180px;
background-size: cover;
image-rendering: optimizeQuality;
-ms-interpolation-mode: bicubic;
}
Another possible way to minimise problems with upscaling is to add a subtle checkerboard effect on top of the image so it breaks up the artifacts and makes the image look better. When we first implemented this, we built it using a checkerboard pattern background-gradient (similar to this one but much, much smaller). This worked well in WebKit-based browsers but slowed down terribly in Firefox. On top of that, the CSS required to specify the gradient pattern was actually larger than the image itself would be. We decided in the end to use a transparent PNG included in our main sprite. It turns out that the clever answer is not always the best answer.
We add the pattern to the page by setting it as the background of the :after pseudo element
div:after {
display: block;
background: transparent url(check.png) 0 0 repeat;
width: 100%;
height: 100%;
margin: 0;
position: absolute;
content: "";
Adding a pattern like this can be a good solution when the image is upscaled but we don't want it to happen all the time. When the image isn't upscaled or when it is only minimally larger than the original size, it can be distracting. To avoid this, we specify these styles in a media-query block. We'll go into them in more detail in a future article.
w.weglinska | 19 April, 2012 14:25
Nokia Maps allows “deep links” to be created externally from the application which link to specific places, routes, addresses, etc. These deep links can be created by any other service and link directly into our application.
This URL format will open maps.nokia.com and show a single address result directly on the map with a nice little bubble and everything.
The address is formed by concatenating the various parameters of the address.
http://maps.nokia.com/whereis/address
http://maps.nokia.com/whereis/unter den linden, 21, berlin, 10117, germany
This URL format will open maps.nokia.com and show a route and maneuvers on the map, and a detailed maneuver list on the left-hand panel.
The address is formed by concatenating the various parameters of the address. We support up to 6 waypoints.
http://maps.nokia.com/drive/address(start)/ address(waypoint1)/.../address(waypoint2)/address(destination)
We strongly believe that this new API will make our users very happy. You only need one link to create route or to show the location. We hope you will have a lot of fun with it!
simonmadine | 12 April, 2012 13:55
One of the most fulfilling aspects of web development is being able to add nice little touches that most people wouldn't even notice. These are the kind of things that web devs put in for professional pride and knowledge of a job well done.
One of my favourite minor additions that we've sprinkled liberally around City Pages and Nokia Maps is the 'Left Click Handler'.
When you're attaching events to elements, whether using standard JS or jQuery, the standard technique is to attach to 'click'.
jQuery:
$(element).on('click', function() {
doSomething();
}
);
By default, this attaches to every variation of the click event – left-click, right-click, middle-click, click while holding shift, while holding control, while holding command – everything. From the developer point-of-view, this might be the easiest solution but from the user point-of-view, this isn't necessarily what you'd expect. The standard browser behaviour on right-click is to show the context menu while the standard shift-click is to open a new window or tab. There are several different kinds of clicks that all fire that same event and you need to figure out which one the user actually did. Middle-click? They want the current content or application state to stay in whatever form it is in the current tab and they want whatever your handler does to happen in a new tab in the background. Are they holding down control? Alt? Command? They could be intending to do any of the above things or a number of other things. Can you guarantee you want your event handler to run for all of them?
jQuery has handy normalisation for events. You can use this to make a simple little check for the probable intention of the user and be a bit smart about how you handle your handlers.
As good, conscientious developers (as I know you all are), the 'right thing to do' is to allow the user to maintain control over their actions. Whenever a click event is detected, the first thing the code should do is check whether it was a simple left-click or something more complicated. If it was a left-click, handle it they way you always intended to, if it was anything else, handle it the way the user expects – if they are trying to open the link in a new background window, let the link open in a new background window. If they want to see the right-click context menu, show them the right-click context menu. The jQuery snippet below will only call the attached event if it is a standard left-click event. Anything else will proceed according to the standard browser behaviour.
/** Attach event to element */
$('a').on('click', function(evt) {
handleLeftClick(evt,function(evt){ // our new call
alert('left-click')
}); // closing our call
});
/** Wrap call to click handler to filter out non-left clicks */
handleLeftClick = function (evt, func, context) {
// Normalize Event - ensure the event is the same in all browsers
evt = $.event.fix(evt);
// Only show overlay on left click so middle click/shift-click/etc can open new tab
if (evt.which !== 2 && !evt.metaKey && !evt.shiftKey) {
evt.preventDefault();
func.call(context);
}
};
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.
mmarcon | 29 March, 2012 09:00
Nowadays, whatever effect or component a developer may need for a website or web application is available in the form of a jQuery plugin. Every other day I see a blog post with a title like "The best 25 jQuery carousels" or "The best 50 photo gallery plugins for jQuery". Alright, I get it, one should not reinvent the wheel: if somebody builds a plugin that suits your needs it makes sense to use it rather than spending time reimplementing the whole thing from scratch by yourself. Unfortunately too many times developers are willing to adapt their markup or even their layout to accommodate the requirements of the plugin they use. Often plugins expose several features and support endless options for customization: while this may result very convenient, the price is a quite large amount of code that in most cases is unnecessary. This translates to more bytes (or kilobytes) delivered to the user's browser and possibly an increased perceived loading time for the web page containing the widget.
In situations where keeping the codebase small and efficient is crucial, it is probably worth to invest a little time and write a completely customized component from scratch, strictly based on the needs. Eventually with a few bytes more the widget can be converted to a simple plugin in case it has to be used in other pages or application components. In the last few weeks at City Pages we have been working on a new feature that required a carousel to nicely display some information to the user. After a quick analysis of the problem we chose to implement our own little carousel instead of relying on any of the thousands of plugins out there: this way we could quickly come up with a completely customized carousel component with only a few lines of JavaScript and some small changes to our CSS file. In this post I am going to explain how to build a carousel using jQuery and some CSS tricks, following the steps we took when developing the one for City Pages.
Here is what the final carousel will look like.
The carousel will slide horizontally with a nice sliding animation and will be a circular carousel in the sense that after the last item the first one is shown. In order to obtain this circular effect the carousel items will be arranged by the script as follows:
The last item is cloned and placed before all the other items and the first item is cloned and appended at the end. When the viewport is showing number 1, sliding backwards animates to the cloned version of number 3 and then seamlessly changes the viewport to the actual number 3. The next sections describe in details the markup and CSS for the carousel and the JavaScript code that creates the interaction.
First of all we need some markup. It can be either generated on the server side - good for SEO purpose - or dynamically created front-end side, but here is what it should look like:
<!-- A container for the carousel -->
<div class="carousel-wrapper"> <!-- The actual carousel -->
<ul class="carousel">
<li class="item">
<img src="some/image/here.png" alt=""/>
<p class="text"><!-- Item text here --></p>
</li>
<li class="item">
<img src="some/image/here.png
<p class="text"><!-- Item text here --></p>
</li>
<li class="item">
<img src="some/image/here.png
<p class="text"><!-- Item text here --></p>
</li>
<li class="item">
<img src="some/image/here.png
<p class="text"><!-- Item text here --></p>
</li>
</ul>
<!-- The prev/next controls -->
<div class="carousel-nav">
<a href="" class="prev"><</a>
<a href="" class="next">></a>
</div>
</div>
There is not much to say here: we have a wrapper block that contains the carousel itself and the navigation controls. The carousel is a ul element and the carousel items as expected are li elements in a perfectly semantic and SEO friendly fashion.
As shown in the diagram above, the idea is to place the items horizontally, and set the viewport size so only one of them at the time is visible. In our case the viewport is the wrapper element, and what we want to obtain is a wide ul that slides left and right within the wrapper to show the active item and hide all the others. The images will always represent the background of the carousel items and the text will be displayed on the right. We also want the carousel controls to be always shown on the left and on the right of the wrapper horizontally centered. Additionally we will add some rules to make it beautiful.
In order to accomplish that we'll add the following CSS rules:
.carousel-wrapper { position: relative; /*Reference for absolutely positioned descendants*/
width: 800px; /*Actual width each item should have*/
height: 240px; /*Actual height each item should have*/
overflow: hidden; /*So only one item is visible*/
border-top: 1px dotted #ccc;
border-bottom: 1px dotted #ccc;
margin: 20px 0;
}
.carousel {
position: absolute; /*This way we can dynamically "move" the ul from the JavaScript by changing the left*/
}
.carousel li {
display: block;
float: left; /*Place items horizontally*/
width: 800px;
height: 240px;
position: relative; /*Reset the reference to absolutely position children*/
}
.carousel img {
position: absolute; /*Goes top-left (note that we work under the assumption that the image is the right size)*/
z-index: 0; /*Goes below everything*/
}
.carousel p.text {
position: absolute;
z-index: 1;
font-size: 24px;
width: 340px;
right: 80px;
top: 10px;
color: #ff6347;
background-color: #111111; /*IE tolerant*/
background-color: rgba(0,0,0,0.7);
padding: 5px;
}
.carousel-nav a {
display: block;
height: 40px;
width: 30px;
font-size: 24px;
line-height: 1.6;
font-weight: 800;
color: #222222;
background-color: rgb(255,99,71); /*IE tolerant*/
background-color: rgba(255,99,71,0.7);
text-decoration: none;
padding: 0 4px;
position: absolute;
top: 50%;
margin-top: -20px;
text-align: center;
z-index: 10; /*Always on top*/
}
.carousel-nav a.prev {
left: 0; /*Prev goes on the left*/
-webkit-border-top-right-radius: 4px; /*Some rounded corners fanciness*/
-webkit-border-bottom-right-radius: 4px;
-moz-border-radius-topright: 4px;
-moz-border-radius-bottomright: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.carousel-nav a.next {
right: 0; /*Next goes on the right*/
-webkit-border-top-left-radius: 4px; /*Some rounded corners fanciness*/
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-topleft: 4px;
-moz-border-radius-bottomleft: 4px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
We will use jQuery for simplicity. The following code registers event handlers for the carousel controls, handles sliding animation and repositions the ul element when necessary.
//We don't want to poison the global scope
//therefore we wrap everything into an anonymous self-executing function
(function() {
var first = $('.item').first(),
last = $('.item').last(),
itemWidth = first.width(),
carousel = $('.carousel');
//Adds the clones of the first and last items, as shown in reddish in the picture above
carousel.prepend(last.clone()).append(first.clone());
//Determine the width the carousel ul should have
carousel.width(itemWidth * $('.item').length);
//Scroll to the right position to show the first element
carousel.css({left: -itemWidth});
//Setup handlers...
$('.prev').on('click', function(e){
e.preventDefault();
//Some nice sliding animation
carousel.animate({left: '+=' + itemWidth}, 300, function(){
if(Math.abs(carousel.position().left) < 2) { //See Note below
//Handles circular carousel when we get to the left as much as possible
carousel.css({left: -itemWidth * (carousel.children().length - 2)});
}
});
return false;
});
$('.next').on('click', function(e){
e.preventDefault();
carousel.animate({left: '-=' + itemWidth}, 300, function(){
if(Math.abs(carousel.position().left + itemWidth * (carousel.children().length - 1)) < 2) { //See Note below
//Handles circular carousel when we get to the right as much as possible
carousel.css({left: -itemWidth});
}
});
return false;
});
})();
Note: instead of comparing the current position with 0 in the previous case and to itemWidth * (carousel.children().length - 1) in the next case we compare the absolute value and the absolute value of the difference with 2 (2px) respectively. The reason for this is that we noticed older versions of IE do some sort of approximation and the value was never 0 as one would expect. This way the code is more tolerant to this sort of browser weirdnesses.
And finally the working demo and all the code. The page was tested with Chrome, Firefox 9+, Safari, IE8 and IE9.
Update: we just released an updated version of City Pages, where you can see the carousel in action for displaying City Facts.