Serving the Right Image for the Right Screen

Images are essential to almost every web page, but they also tend to be the heaviest resource a page loads. In many cases, images account for the majority of total bytes downloaded. Responsive web design has long been about adapting layouts to different devices, but the same logic applies to images: a fixed image that is too large for a small viewport wastes bandwidth, while a low-resolution image looks poor on a high-density display.

High-Density Displays and Flexible Layouts

The most common scenario is an image that scales with its container. At a large desktop viewport, an image sized at 50% of the width may look fine, and its intrinsic resolution may be well matched to the display. On a phone, the same element is scaled down, so the browser still downloads the full file even though the screen is far smaller. Meanwhile, on a 2x display, a standard-resolution image will appear blurry because the device has more physical pixels per CSS pixel.

These two constraints create a tension: you need enough resolution for sharp rendering on high-DPI screens, but you do not want to send huge files to small devices that simply shrink the image. The solution is to serve multiple versions of the same image and let the browser choose the appropriate one based on the device's characteristics.

Coping with Aspect Ratio and Cropping

Sometimes scaling and resolution switching are not enough. An image that looks great in a wide landscape crop may be nearly useless when squeezed into a narrow vertical viewport. In these cases, you may need to adjust the proportions, crop differently, or swap in entirely different artwork. This technique is known as art direction.

Art direction acknowledges that a single image may not be effective across all screen sizes. For example, a group photo with relevant detail at the edges might need a tighter center crop on mobile. Changing the composition or sourcing a completely alternate image for smaller viewports ensures the visual message survives the layout change. More examples of such responsive behavior are available at responsiveimages.org/demos/.

A Deeper Look at Image Workflows

Dealing with these image-related challenges in a structured way is the subject of the free Responsive Images course from Udacity. The course focuses on images and their impact on web performance, noting that they are responsible for a disproportionate share of page weight beyond any other asset type.

The curriculum covers techniques for making sure images render sharply on any device and load efficiently, including how to integrate these practices into an ongoing workflow. You will learn methods for building pages so that images adapt not just to varying viewport sizes but also to the intended usage context. The full course is available at no cost through Udacity.

Building responsive images into markup

The img element remains the workhorse for downloading, decoding, and rendering images, and modern browsers support a broad range of formats. Making images work across devices requires only a handful of adjustments.

  • Use relative units for image widths to prevent overflow.
  • Reach for the picture element when different screens need different image crops or sources (art direction).
  • Use srcset with the x descriptor to let the browser pick the right image for the display's pixel density.
  • Inline images (as SVG or Data URI) when a page has one or two images and cutting down file requests matters.

Keep images inside their containers

Widths expressed in relative units keep images proportional to their container. A width of 50% means half of the containing element's width, not the viewport or the image's intrinsic size. Since CSS content can overflow, add max-width: 100% to be safe.

img, embed, object, video {
    max-width: 100%;
}

Always supply descriptive alt text on img elements — it provides context for screen readers and other assistive technologies.

Use srcset for high-DPI displays

The srcset attribute extends img so you can offer several image files. Broadly parallel to the CSS image-set() function, it lets the browser choose based on device characteristics — a 2x file on a 2x screen, for instance, or potentially a 1x file on a 2x screen over constrained bandwidth.

<img src="photo.png" ...>

Browsers without srcset support fall back to the image referenced by src, so keep a 1x version there. When srcset is parsed, it happens before any requests go out, and only the most suitable image is fetched. Pixel density is the one condition with solid support today; while the spec allows width and height conditions, in practice plain 1x/2x pairs are what work.

Art direction with picture

Art direction example

When different screens call for different framing or sources — art direction — the picture element provides a declarative route. Much like video, it can carry multiple source elements keyed to media queries or image formats.

<picture>
  <source media="(min-width: 800px)">
  <source media="(min-width: 450px)">
  <img src="head-fb.jpg" alt="a head carved out of wood">
</picture>

Given the markup above: at viewport widths of at least 800px, head.jpg or head-2x.jpg loads depending on device resolution. Between 450px and 800px, head-small.jpg or head-small-2x.jpg is served. Under 450px, and in browsers where picture is unsupported, the img element is used as fallback — include it always.

Width descriptors for fluid layouts

Density descriptors are awkward when the image's final rendered size isn't fixed — a problem for images that scale with the viewport. Instead of pairing fixed sizes and densities, add a width descriptor to each file and specify the element's size. The browser then derives the effective pixel density on its own.

<img src="lighthouse-200.jpg"
     alt="a lighthouse">

In this example the image is sized to half the viewport width via sizes="50vw". Combined with the device pixel ratio, the browser can select an appropriate file regardless of the current window width.

Browser width Device pixel ratio Image used Effective resolution
400px 1 200.jpg 1x
400px 2 400.jpg 2x
320px 2 400.jpg 2.5x
600px 2 800.jpg 2.67x
640px 3 1000.jpg 3.125x
1100px 1 800.png 1.45x

Adapting to layout breakpoints

Images often change dimensions across layout breakpoints — full viewport width on small phones, a modest fraction on larger screens. The sizes attribute supports multiple media queries to describe this.

<img src="400.png"
    
     alt="an example image">

In this pattern, the image occupies 25% of the viewport beyond 600px, 50% between 500px and 600px, and full width below 500px.

Enlarging product shots

J. Crews website with expandable product image
J. Crew's website with expandable product image.

Shoppers want to inspect merchandise up close. In usability research, participants grew frustrated when retail sites didn't let them zoom into product imagery. Sites like J. Crew model the pattern well: a subtle overlay cues the tap, and a high-detail zoomed view follows.

More responsive image tactics

Compressive images

The compressive technique serves a heavily compressed 2x image to every device regardless of actual display capabilities. Depending on image type and compression level, quality can hold up remarkably well while file size shrinks.

JavaScript-based swapping

Scripted replacement inspects device capabilities — window.devicePixelRatio, screen dimensions, even navigator.connection — and then picks an image. The tradeoffs are meaningful: the browser delays image downloads until the look-ahead parser finishes, generally after pageload, and a naïve implementation may fetch both 1x and 2x versions, inflating page weight.

Inline images: raster vs. vector

Image creation and storage fall into two fundamental camps, and the choice affects responsive delivery.

Raster formats — PNG, JPEG, WebP — encode images as a grid of colored dots, typically from a camera, scanner, or HTML canvas.

Vector formats — SVG being the relevant one — describe geometry: lines, curves, shapes, fills, and gradients, as produced by tools like Adobe Illustrator or Inkscape.

SVG's scaling advantage

Browsers can render SVG at any dimensions because the format stores geometric instructions rather than sampled pixels. Scaling a raster image forces the browser to guess missing detail; scaling SVG is mathematically exact. Both formats have their place — compare the fuzzy PNG versus the crisp SVG at larger display sizes.

HTML5 logo, PNG format
HTML5 logo, SVG format

SVG also suits inline deployment to reduce HTTP requests. Browser support is broad on mobile and desktop, and optimization tooling can substantially shrink file size — two identical inline logos may differ by a third in bytes.

Base64 via Data URIs

Data URIs place binary content (like images) directly into an img element's src as a Base64-encoded string:

<img src="data:image/svg+xml;base64,[data]">

The HTML5 logo referenced earlier, for instance, begins:

<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiB
BZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW ...">

That full string runs to over 5,000 characters. Drag-and-drop encoder tools such as jpillora.com/base64-encoder convert binary images to Data URIs, which enjoy wide mobile and desktop support alongside SVG.

Inlining within CSS

Both Data URIs and SVG can live inside CSS as background images; support spans mobile and desktop browsers.

Costs of inlining

Inlining exists to cut HTTP requests. An entire document — markup, styles, scripts, images — can arrive as a single request when everything is embedded.

That efficiency has real downsides:

  • Data URIs can render noticeably slower on mobile than external image sources.
  • They bloat the HTML document size.
  • Markup and build tooling grow more complex.
  • Base64 formatting runs about 30% larger than the originating binary, so total transfer is not reduced.
  • Data URIs are uncacheable; each page that embeds one redownloads it.
  • Support gaps exist in longstanding Internet Explorer versions.
  • HTTP/2's multiplexing makes request-count minimization less critical.

Measure before optimizing: use developer tools to compare file size, request count, and round-trip latency. Inlining suits occasional raster images — a home page with a couple of unique photos, say. For vectors, inline SVG is the superior choice.

Conditional images in CSS

The CSS background property combined with media queries gives you a way to load different images based on display characteristics such as viewport width or device resolution. This covers two distinct needs: conditional loading (fetching an image only when it is actually needed) and art direction (serving a different crop or composition depending on the context).

Switching backgrounds with media queries

Media queries let you tie background-image rules to the viewport width. In the example below, small screens download only small.png for the content div. On wider viewports, the body gets body.png and the content div switches to large.png.

.example {
  height: 400px;
  background-image: url(small.png);
  background-repeat: no-repeat;
  background-size: contain;
  background-position-x: center;
}

@media (min-width: 500px) {
  body {
    background-image: url(body.png);
  }
  .example {
    background-image: url(large.png);
  }
}

Try it

Using image-set() for resolution-aware backgrounds

The image-set() function extends background the same way srcset extends <img>: you supply multiple files and let the browser pick. It can use a 2x image on a high-DPI screen, or fall back to a 1x image on a 2x device that is on a constrained network.

background-image: image-set(
    url(icon1x.jpg) 1x,
    url(icon2x.jpg) 2x
);

The browser also scales the chosen image as expected, assuming a 2x asset is twice the pixel dimensions of a 1x asset and rendering it at the same CSS size.

Support is still limited to Chrome and Safari behind the -webkit prefix, so always provide a plain background-image fallback:

.sample {
  width: 128px;
  height: 128px;
  background-image: url(icon1x.png);
  background-image: -webkit-image-set(
    url(icon1x.png) 1x,
    url(icon2x.png) 2x
  );
  background-image: image-set(
    url(icon1x.png) 1x,
    url(icon2x.png) 2x
  );
}

Try it

Resolution queries for high-DPI displays

Media queries can also target the device pixel ratio directly. Chrome, Firefox and Opera support the standard (min-resolution: 2dppx) syntax; Safari and Android browsers need the older vendor-prefixed form without the dppx unit.

@media (min-resolution: 2dppx),
(-webkit-min-device-pixel-ratio: 2)
{
    /* High dpi styles & resources here */
}

Base styles come first so something renders regardless of media query support, then the high-DPI rules override them:

.sample {
  width: 128px;
  height: 128px;
  background-image: url(icon1x.png);
}

@media (min-resolution: 2dppx), /* Standard syntax */
(-webkit-min-device-pixel-ratio: 2)  /* Safari & Android Browser */
{
  .sample {
    background-size: contain;
    background-image: url(icon2x.png);
  }
}

Try it

You can also key off min-width to swap background art. A benefit of this approach is that the image is never fetched unless its query matches — bg.png below only loads when the viewport is at least 500px wide:

@media (min-width: 500px) {
    body {
    background-image: url(bg.png);
    }
}

Icons: prefer SVG and unicode

For page icons, reach for SVG or unicode characters before raster images. A few practical guidelines:

  • Use unicode for simple symbols instead of image files.
  • Use SVG for complex icons — not icon fonts.
  • If you do use icon fonts, weigh the HTTP cost and file size against the small set of glyphs you actually need.

Simple glyphs from unicode

Most fonts cover a wide range of unicode symbols — arrows (←), math operators (√), geometric shapes (★), control pictures (▶), music notes (♬), Greek letters (Ω), chess pieces (♞). Unlike images, these scale cleanly at any size. Reference them by numeric entity, e.g. &#9733; renders ★.

You're a super ★

Complex icons as inline SVG

Where you need more than a single code point, SVG is the better choice. Advantages over raster assets:

  • Vector graphics scale infinitely without quality loss.
  • CSS can control color, shadows, transparency and animation.
  • Inline SVG lives directly in the document, avoiding a network request.
  • The format is semantic and, used with appropriate attributes, more accessible.
With SVG icons, you can either add icons using inline SVG, like
this checkmark:
  <svg version="1.1" xmlns="http://www.w3.org/2000/svg"
       xmlns:xlink="http://www.w3.org/1999/xlink"
       width="32" height="32" viewBox="0 0 32 32">
    <path d="M27 4l-15 15-7-7-5 5 12 12 20-20z" fill="#000000"></path>
  </svg>
or by using an image tag, like this credit card icon:
<img src="credit.svg">.

Try it

The icon font caveat

Example of a page that uses FontAwesome for its font icons.
Example of a page that uses FontAwesome for its font icons.

Icon fonts remain popular, but compared with SVG they have real drawbacks:

  • Anti-aliasing can leave icons less sharp than expected.
  • CSS styling options are limited.
  • Precise positioning is finicky given line-height, letter-spacing and related properties.
  • They carry no semantic meaning and complicate screen reader support.
  • Unless the font is subsetted, you may ship hundreds of unused glyphs as a large file.
With Font Awesome, you can either add icons by using a unicode
entity, like this HTML5 logo (<span class="awesome">&#xf13b;</span>)
or by adding special classes to an &lt;i&gt; element like the CSS3
logo (<i class="fa fa-css3"></i>).

Try it

Free and paid collections exist — Font Awesome, Pictos, Glyphicons — but treat the extra request and payload as a real cost. If a handful of icons is all you need, a single image or a sprite might serve you better.

Cutting image weight for performance

Images are usually the largest share of downloaded bytes and often dominate the visual area of a page. Optimizing them yields outsized wins for load time, since fewer bytes mean less bandwidth contention and faster rendering of every asset.

  • Pick the format based on the image type, not default habit.
  • Fold compression tools into your build process instead of manual post-processing.
  • Group frequently used images into sprites to cut HTTP requests.
  • Defer below-the-fold images until they are near the viewport.

Choosing between raster, vector, and compression formats

A raster image is a grid of pixels, typically from a camera, scanner, or the canvas element. File size grows with pixel dimensions, and upscaling past native resolution makes edges blurry when the browser interpolates the gaps. A vector image is defined by curves, lines, shapes and fills — created in tools like Illustrator or Inkscape and saved to a format such as SVG — so it resizes without distortion or file size growth.

No single format fits all cases. Start with these rules of thumb:

  • JPG for photographic images.
  • SVG for logo and line art; if a vector source is unavailable, try WebP or PNG.
  • PNG over GIF, since it supports more colors with better compression.
  • For longer animations, consider the <video> element — quality is higher and playback is user-controlled.

Lossless post-processing

After saving, further lossless compression can shrink both JPG and PNG files without altering image quality. For JPEG, tools like jpegtran or jpegoptim (Linux; run with the --strip-all flag) work well. For PNG, look at OptiPNG or PNGOUT. Ideally automate these so every output is optimized as part of your normal workflow.

Image sprites

Image sprite sheet used in example

Spriting combines several images into one sheet. Set that sheet as the background and use an offset to display only the part you need:

.sprite-sheet {
  background-image: url(sprite-sheet.png);
  width: 40px;
  height: 25px;
}

.google-logo {
  width: 125px;
  height: 45px;
  background-position: -190px -170px;
}

.gmail {
  background-position: -150px -210px;
}

.maps {
  height: 40px;
  background-position: -120px -165px;
}

Try it

Fewer downloads to get the same set of images, while caching still applies to the single sheet.

Lazy loading and its trade-offs

Long pages with many images below the fold benefit from lazy loading, fetching assets only as needed or once primary content has rendered. Beyond performance, this enables infinite scrolling experiences.

Be aware of two risks with infinitely scrolling pages: search engines may never crawl content that loads only on scroll, and footer information users expect may be pushed out of reach as new content replaces it indefinitely.

When the Best Image Is No Image

Modern browsers can natively generate many visuals that previously demanded an image file. Replacing images with these built-in capabilities avoids extra downloads and prevents the layout distortion caused by awkwardly scaled images.

Keep Text in the Markup

Text should live in the markup rather than inside an image. Putting headlines, phone numbers, or addresses into an image blocks users from copying or pasting the content, hides it from screen readers, and makes it unresponsive. You can still achieve the intended design by styling markup text with webfonts.

Use CSS for Visual Effects

CSS properties can reproduce effects that historically required an image. The background property can build complex gradients, box-shadow creates depth, and border-radius produces rounded corners. These techniques are valuable, but they come with a cost: they require rendering cycles that can be substantial on mobile devices.

<style>
    div#noImage {
    color: white;
    border-radius: 5px;
    box-shadow: 5px 5px 4px 0 rgba(9,130,154,0.2);
    background: linear-gradient(rgba(9, 130, 154, 1), rgba(9, 130, 154, 0.5));
    }
</style>

Being heavy-handed with these CSS effects can erase their performance advantage relative to images. Use them judiciously so the savings from skipping an image download aren't offset by the rendering burden on lower-powered hardware.