Mobile-First Images — Complete Guide
In this tutorial, you will learn about Mobile. We cover key concepts, practical examples, and best practices to help you master this topic.
Mobile-first images use srcset, sizes, WebP, Lazy Loading, responsive art direction, and proper aspect ratios for performance and visual quality.
What You'll Learn
- The srcset and sizes attributes
- Responsive art direction with picture element
- Modern image formats (WebP, AVIF)
- Lazy loading for below-fold images
- Aspect ratio containers to prevent CLS
- Background images for mobile
- Image optimization workflow
Why It Matters
- Images are the largest assets on most pages
- Mobile networks are slower and more expensive
- Wrong image size wastes bandwidth
- Images without dimensions cause layout shifts
Real-World Use
- An e-commerce site serves 400px images on mobile, 1200px on desktop
- A news site uses WebP with JPEG fallback
- A travel site crops photos differently for mobile
- A blog lazy loads images, improving initial load
flowchart LR A[Mobile-First Images] --> B[srcset] A --> C[Picture Element] A --> D[Lazy Loading] A --> E[Aspect Ratio] B --> F[Resolution switching] C --> G[Art direction] D --> H[loading=lazy] E --> I[aspect-ratio CSS]
Responsive Images
Code Example: srcset and sizes
<!-- Mobile-first: smallest image is default -->
<img
src="photo-400.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w,
photo-1600.jpg 1600w
"
sizes="
(max-width: 480px) 100vw,
(max-width: 768px) 90vw,
(max-width: 1024px) 70vw,
800px
"
alt="Descriptive text describing the image content"
width="800"
height="600"
loading="lazy"
decoding="async"
>
<!-- Combined pixel density and width -->
<img
src="photo-400.jpg"
srcset="
photo-400.jpg 400w,
photo-400-2x.jpg 400w 2x,
photo-800.jpg 800w,
photo-800-2x.jpg 800w 2x,
photo-1200.jpg 1200w
"
sizes="
(max-width: 600px) 100vw,
800px
"
alt="Descriptive text"
width="800"
height="600"
loading="lazy"
>
Expected output: The browser selects the appropriate image based on viewport width, pixel density, and network conditions. On a 375px phone with 2x display, it may load photo-800.jpg (400w * 2x = 800w equivalent).
Code Example: Art Direction with Picture
<!-- Art direction: different crops for different screens -->
<picture>
<!-- Mobile: tight crop, portrait -->
<source
media="(max-width: 480px)"
srcset="
hero-mobile.webp 480w,
hero-mobile-2x.webp 960w
"
sizes="100vw"
type="image/webp"
>
<source
media="(max-width: 480px)"
srcset="
hero-mobile.jpg 480w,
hero-mobile-2x.jpg 960w
"
sizes="100vw"
type="image/jpeg"
>
<!-- Tablet: medium crop -->
<source
media="(max-width: 768px)"
srcset="
hero-tablet.webp 768w,
hero-tablet-2x.webp 1536w
"
sizes="100vw"
type="image/webp"
>
<source
media="(max-width: 768px)"
srcset="
hero-tablet.jpg 768w,
hero-tablet-2x.jpg 1536w
"
sizes="100vw"
type="image/jpeg"
>
<!-- Desktop: wide crop, landscape -->
<source
srcset="
hero-desktop.webp 1200w,
hero-desktop-2x.webp 2400w
"
sizes="100vw"
type="image/webp"
>
<source
srcset="
hero-desktop.jpg 1200w,
hero-desktop-2x.jpg 2400w
"
sizes="100vw"
type="image/jpeg"
>
<!-- Fallback for older browsers -->
<img
src="hero-desktop.jpg"
alt="Hero banner showing the main value proposition"
width="1200"
height="600"
loading="eager"
fetchpriority="high"
decoding="async"
>
</picture>
Expected output: Mobile users see a tightly-cropped portrait version of the hero image. Desktop users see a wide landscape crop. The browser chooses the best format (WebP if supported, JPEG otherwise).
Code Example: Aspect Ratio Containers
<!-- Fixed aspect ratio: prevents CLS -->
<div class="image-container" style="aspect-ratio: 16 / 9;">
<img
src="photo.jpg"
alt="Descriptive text"
width="800"
height="450"
loading="lazy"
style="width: 100%; height: 100%; object-fit: cover;"
>
</div>
<!-- Legacy padding-bottom hack (fallback) -->
<div class="image-container-legacy">
<img
src="photo.jpg"
alt="Descriptive text"
loading="lazy"
>
</div>
<style>
/* Modern: aspect-ratio property */
.image-container {
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Legacy: padding-bottom hack */
.image-container-legacy {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%; /* 16:9 */
overflow: hidden;
}
.image-container-legacy img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* Common aspect ratios */
.aspect-video { aspect-ratio: 16 / 9; }
.aspect-square { aspect-ratio: 1 / 1; }
.aspect-portrait { aspect-ratio: 3 / 4; }
.aspect-wide { aspect-ratio: 21 / 9; }
.aspect-instagram { aspect-ratio: 4 / 5; }
/* Background images with aspect ratio */
.hero-bg {
aspect-ratio: 16 / 9;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
@media (max-width: 480px) {
.hero-bg {
aspect-ratio: 4 / 5; /* Taller on mobile */
}
}
</style>
Expected output: Images reserve their space before loading. The aspect-ratio CSS property prevents layout shift (CLS) as images load. Object-fit: cover ensures images fill the container without distortion.
Code Example: Background Images for Mobile
/* Mobile-first background images */
.hero {
/* Mobile: smaller image loads first */
background-image: url('hero-mobile.webp');
background-size: cover;
background-position: center;
min-height: 60vh;
}
/* Tablet: larger image */
@media (min-width: 480px) {
.hero {
background-image: url('hero-tablet.webp');
min-height: 50vh;
}
}
/* Desktop: full-resolution image */
@media (min-width: 1024px) {
.hero {
background-image: url('hero-desktop.webp');
min-height: 70vh;
}
}
/* image-set() for pixel density */
.hero {
background-image: image-set(
url('hero.webp') 1x,
url('hero-2x.webp') 2x,
url('hero-3x.webp') 3x
);
}
/* Lazy loading for non-critical background images */
.lazy-bg {
background-image: none;
transition: background-image 0.3s ease;
}
.lazy-bg.loaded {
background-image: url('large-image.webp');
}
// Lazy loading background images
const lazyBgs = document.querySelectorAll('.lazy-bg');
const bgObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('loaded');
bgObserver.unobserve(entry.target);
}
});
}, {
rootMargin: '200px' // Start loading before visible
});
lazyBgs.forEach(bg => bgObserver.observe(bg));
Expected output: The hero background uses a small image on mobile and progressively larger images on wider screens. Non-critical background images are lazy loaded with Intersection Observer.
Common Mistakes
- No srcset attribute — The same large image is served to all devices, wasting bandwidth on mobile.
- No dimensions on images — Without width and height (or aspect-ratio), images cause Cumulative Layout Shift (CLS).
- Using max-width: 100% without height: auto — Images with only max-width: 100% can have incorrect intrinsic height.
- Not providing WebP fallback — Serve WebP with JPEG fallback via the picture element for broad browser support.
- Eager loading of all images — Only above-fold (LCP) images should load eagerly. Below-fold images use loading="lazy".
- Wrong image resolution for mobile — A 2400px image on a 375px screen wastes bandwidth. Serve appropriately sized images.
- No art direction — The same image crop on mobile and desktop often looks wrong. Use picture element for different crops.
Practice Questions
- What HTML attribute tells the browser which image to use at different viewport widths? The sizes attribute combined with srcset width descriptors (400w, 800w).
- How does the picture element support art direction? Multiple source elements with media queries allow different image crops for different viewports.
- What CSS property prevents layout shift from images? aspect-ratio (e.g., aspect-ratio: 16/9) or explicit width and height attributes.
- What loading attribute enables lazy loading for images? loading="lazy" on the img element.
FAQ
Mini Project
Build a responsive image gallery that implements all mobile-first image techniques. The gallery should: (1) use srcset with 4 width variants and sizes attribute, (2) use picture element for WebP/JPEG format switching, (3) use art direction (different crops for mobile portrait vs desktop landscape), (4) set explicit width/height on all img elements, (5) use aspect-ratio CSS to prevent CLS, (6) lazy load below-fold images, (7) use background-image with image-set() for a hero section, (8) lazy load non-critical background images. Test on a throttled 3G connection. Verify no CLS occurs and the correct image sizes load.
What's Next
Continue with Lesson 6: Mobile-First Forms to design forms for mobile users.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro