Responsive images
One <img>, many sources.
Let the browser pick the right file for the screen with srcset and sizes, switch art direction or formats with <picture>, and defer offscreen loads with loading=lazy.
srcset with density descriptors
The simplest srcset offers the same image at different pixel densities using x descriptors. srcset="a.png 1x, b.png 2x" tells the browser to use b.png on a 2x (Retina) display and a.png on a standard one. The src stays as the fallback for browsers that ignore srcset. Use this when the rendered size is fixed and only the device pixel ratio varies β avatars, logos, icons.
srcset + sizes with width descriptors
For images whose layout width changes, describe each candidate by its intrinsic pixel width with a w descriptor (small.jpg 480w), then tell the browser how wide the image will render with sizes. sizes is a list of (media-condition) length pairs with a bare fallback length last β sizes="(min-width: 40em) 50vw, 100vw" means "half the viewport at 40em and up, otherwise full width". The browser combines sizes with the device pixel ratio to pick the smallest candidate that still looks sharp.
<picture> for art direction and formats
srcset only swaps resolutions of the same image. When you need a different crop β a wide hero on desktop, a tight square on mobile β reach for <picture>. Each <source> carries a media query; the browser uses the first one that matches, falling back to the inner <img>. The same element handles modern formats: give a <source> a type="image/webp" and the browser skips it if it can't decode WebP, landing on the next source or the <img>.
loading="lazy"
Add loading="lazy" to any <img> (or the <img> inside a <picture>) and the browser defers fetching it until it nears the viewport β no observer code required. Leave it off (or use loading="eager") for above-the-fold images so they load immediately. Pairing lazy with width/height (or aspect-ratio) prevents layout shift as deferred images arrive.
Try it 4 examples
Density descriptors (1x / 2x)
HTML+CSS+JSintroThe image always paints at the same 120px box, but the browser silently swaps in the 240Γ240 2x candidate on Retina/HiDPI displays and the smaller 1x one elsewhere β so a low-DPI preview shows the 1x tile and a HiDPI screen shows the crisper 2x tile at identical layout size.