- Whats the problem?
- Selectively serving images to mobile
- Responsive image strategies
- Responsive image options
- Background images
- High-resolution displays
- Other fixed-width assets
- Wrapping it up
Selectively serving images to mobile
Let’s start by removing the images in the “More in Football” section from the core experience. It might be tempting to just use display:none and call it a day, but that doesn’t fix the problem, it only hides it.
An image set to display:none will still be requested and downloaded by the browser. So while the image won’t show up on the screen, the issue of the extra request and weight is still there. Instead, as usual, the correct approach is to start with mobile first and then progressively enhance the experience.
Begin by removing the images from the HTML entirely:
1.
<ul class="slats">2.
<li class="group">3.
<a href="#">4.
<h3>Kicker connects on record 13 field goals</h3>5.
</a>6.
</li>7.
<li class="group">8.
<a href="#">9.
<h3>Your favorite team loses to that team no one likes</h3>10.
</a>11.
</li>12.
<li class="group">13.
<a href="#">14.
<h3>The Scarecrows Win 42-0</h3>15.
</a>16.
</li>17.
</ul>
Obviously, the images will not load with this HTML. On the small-screen display, that’s the way it’ll stay. For the larger sizes, a little JavaScript will bring the images back. Using the HTML5 data-* attributes as hooks, it’s easy to tell the JavaScript which images to load:
1.
<ul class="slats">2.
<lidata-src="images/ball.jpg"
class="group">3.
<a href="#">4.
<h3>Kicker connects on record 13 field goals</h3>5.
</a>6.
</li>7.
<lidata-src="images/goal_post.jpg"
class="group">8.
<a href="#">9.
<h3>Your favorite team loses to that team no one likes</h3>10.
</a>11.
</li>12.
<lidata-src="images/ball_field.jpg"
class="group">13.
<a href="#">14.
<h3>The Scarecrows Win 42-0</h3>15.
</a>16.
</li>17.
</ul>
JavaScript
The first thing to add is a quick utility function to help select elements. It’s not necessary, but it’s definitely useful to have around:
1.
q : function(query) {2.
if (document.querySelectorAll) {3.
var res = document.querySelectorAll(query);4.
} else {5.
var d = document,6.
a = d.styleSheets[0] || d.createStyleSheet();7.
a.addRule(query,'f:b');8.
for(var l=d.all,b=0,c=[],f=l.length;b<f;b++) {9.
l[b].currentStyle.f && c.push(l[b]);10.
a.removeRule(0);11.
var res = c;12.
}13.
return res;14.
}15.
}
If you’re unfamiliar with native JavaScript, that might look a bit messy. That’s OK. All the function does is take a selector, and return the elements that match it. If you can grasp the code, that’s great. If not, as long as you understand what it accomplishes, that’s enough for our purposes.
Armed with that function, the part that actually loads the images is pretty straightforward:
1.
//load in the images2.
var lazy = Utils.q('[data-src]');3.
for (var i = 0; i < lazy.length; i++) {4.
var source = lazy[i].getAttribute('data-src');5.
//create the image6.
var img = new Image();7.
img.src = source;8.
//insert it inside of the link9.
lazy[i].insertBefore(img, lazy[i].firstChild);10.
};
Line 2 grabs any elements with a data-src attribute applied. Then, in line 3 the script loops through those elements. In lines 4–7, the script creates a new image for each element using the value of the data-src attribute. The script then inserts the new image (line 9) as the first element within the link.
With this JavaScript applied, the images aren’t requested right away. Instead, they’re loaded after the page has finished loading, which is what we want. Now, we just have to tell the script not to load for small screens.
Introducing matchMedia
In Chapter 3, “Media Queries,” the script we built to toggle the display of the navigation on small screens checked to see if the list items in the navigation were floated. If they were, the collapse feature was created. This time, let’s use the handy matchMedia() method.
The matchMedia() method is a native JavaScript method that lets you pass in a CSS media query and receive information about whether or not the media query is a match.
To be specific, the function returns a MediaQueryList object. That object has two properties: matches and media. The matches property returns either true (if the media query matches) or false (if it doesn’t). The media property returns the media query you just passed in. For example, the media property for window.matchMedia("(min-width: 200px)") would return "(min-width: 200px)".
matchMedia() is supported natively by Chrome, Safari 5.1+, Firefox 9, Android 3+, and iOS5+. Paul Irish has created a handy polyfill for browsers that don’t support the method.
With the matchMedia polyfill in place, telling the browser to insert only the images above the first breakpoint simply requires wrapping the code inside a matchMedia check:
1.
if (window.matchMedia("(min-width: 37.5em)").matches) {2.
//load in the images3.
var lazy = Utils.q('[data-src]');4.
for (var i = 0; i < lazy.length; i++) {5.
var source = lazy[i].getAttribute('data-src');6.
//create the image7.
var img = new Image();8.
img.src = source;9.
//insert it inside of the link10.
lazy[i].insertBefore(img, lazy[i].firstChild);11.
};12.
}
Now when the page is loaded on a phone, or the screen is sized down, the images are no longer requested (Figure 4.3). This is a big win for performance on small screens. There are now three fewer HTTP requests, and the size of the page has been reduced by about 60KB (the size of those three images combined). Best of all, the headlines are still there and the links are completely functional. The experience doesn’t suffer at all.
Figure 4.3. On small screens, the images in the “More in Football” section won’t be requested, greatly improving the performance of the page.
With those images out of the way, we can focus on the lead-in image and the logo. We want those images, the logo in particular, to show up no matter the resolution. So, instead of conditionally loading them, we’ll load them every time, but sized appropriately. This is where things get hairy.