Image Strategies and Options
Scalable Images
- When using the
<img />tag, you usually want the images to scale down when the viewport (browser window) is too small, yet maintain their aspect ratio. The alternative is that the image goes off the viewport and triggers scrollbar(s), so that is not a desirable outcome. - The width and height properties are the key ones in these cases:
img {width: 100%; height: auto;} - Where this can become problematic is when the image's parent element is wider than the graphic, which results in the 100% width scaling the image bigger than its native dimensions, rather than smaller. There are a few solutions to this:
- Only trigger the image scaling when necessary (when there is a need for it to scale); this is handled via media queries, such as triggering the prior CSS when the viewport is less than 768px.
- Reduce the width of the image's parent element.
- Set a
max-widthon the image that matches its native width; thismax-widthwill be in pixels to match the exact width dimension of the graphic. The biggest concern with this is that the values will vary significantly, so one cannot just set a single rule and handle the needs for all images.
- With background images we use
background-size: coverto resolve problems with background images being too small. - For non-background images (and videos) there is a CSS property called
object-fitfor resizing to fit a container. There are various values for the property; if you go down this road it is usuallyobject-fit: cover.
srcset Attribute and device-pixel ratios
- Prior to the arrival of Retina displays (and similar high pixels-per-inch displays) there was a 1-to-1 relationship between the pixels specified in HTML and CSS and the physical pixels on the screen. 100px in CSS was 100 pixels on the screen.
- The formal term for the mapping of pixels in our code to pixels on the screen is the device-pixel ratio. There are two key factors driving this ratio:
- Pixel density of the device (physical pixels per inch): A high resolution device will have a higher pixel density and hence, for the same zoom level, it will have a high device-pixel ratio as compared to a lower resolution device.
- Zoom level of the browser: For the same device, a higher zoom level means more device pixels per CSS pixel, and hence a higher device-pixel ratio. When you zoom in on your browser (hold down Ctrl and the + key on Windows), the number of CSS pixels remains the same, but the number of device pixels it occupies increases. So, you have a higher number of device pixels per CSS pixel.
- Learn more abut device-pixels.
- This has lots of implications when it comes to web design, such as creating high-definition images and calling different images at different device-pixel ratios. You wouldn't want to force a low-end device to download a very high resolution image (which is Apple's advice to double the dimensions of all your images), only to downscale it on the device. You also don't want high-end devices to upscale low resolution images (this just makes for blurry images).
- The
srcsetattribute allows for different images to be downloaded based on device-pixel ratio. This attribute has good browser support. Browser support (or lack thereof) won't make a difference on smartphones, but it does make a difference on desktops and laptops, and thesrcattribute serves as a fallback. - As the attribute name suggests, your are literally indicating which image source to set (thus srcset).
- Here is a basic
srcsetimplementation:<img src="images/building.jpg" srcset="images/building.jpg 1x, images/building-2.jpg 2x, images/building-3.jpg 3x" alt="Office Building" />
The
xdescriptor in thesrcsetattribute is used to define the device-pixel ratio. For the above example we have three image sizes (we'll assume 400px, 800px, and 1200px).- For a device-pixel ratio of 1 (1x), building.jpg will be used (the 400px image).
- For a device-pixel ratio of 2 (2x), building-2.jpg will be used (the 800px image).
- For a device-pixel ratio of 3 (3x), building-3.jpg will be used (the 1200px image).
- Non-supportive devices use the
srcattribute and load building.jpg
- The above works well for varying device-pixels ratios, so regardless of viewport size if the device has the indicated ratio, it shows the indicated image. The order of the
srcsetvalues makes no difference. - What if we want a different size (height, width) image on a larger or smaller viewport? The
wdescriptor insrcsetcomes into play. This descriptor indicates the width of the image being referenced, so that the browser can decide on its own what to download (thexdescriptor is more rigid, as the browser does not get a choice regarding what to do). An example:<img src="images/building.jpg" srcset="images/building.jpg 400w, images/building-2.jpg 800w, images/building-3.jpg 1200w" alt="Office Building" />
In the
wexample we have told the browser the width of the images, so now it can decide which to download. Let's say that the user's viewport is 600 pixels wide. On a 1x device-pixel ratio, the 400 pixel building.jpg image would be downloaded and used, and on a 2x device-pixel ratio the 1200 pixel image would be downloaded and used.We have to tell the browser the widths, because it doesn't know the dimensions otherwise until it has downloaded the image. Without that data it would download all the images and pick among them, which completely negates any performance benefits.
Browsers may also factor in poor Internet connectivity / slow connection speeds and decide to download a smaller or lower-density image based on that.
- Going down this road means that you're creating a lot more images.
- Content Management Systems, such as WordPress, will automate this process for you by creating all sorts of image variations when the images are uploaded to its media library and generating the code for you when you insert them using the WordPress visual editor. This is not always desired, or even a good outcome (I have had to forcibly override these sometimes because the results left a lot to be desired). However, if the alternative is manually creating all the variations and writing out the
srcset, you might be happy to have it automated. - There are also third-party services, such as imgix, that generate these variations for you based on parameters you pass when loading the image from them (they also serve as your Content Delivery Network for the images).
sizes Attribute
- The
sizesattribute is another newer attribute for the<img />element, and is meant to work in tandem withsrcset. srcsetcommunicated the images available and their native widths (because the browser wouldn't know them othewise).sizescommunicates the dimensions to use for the downloaded image at different viewport sizes.- Adding
sizeshelps the browser make a better decision about which image gets downloaded, and what size to display the image at, because you incorporate media queries intosizesvalues. - Like
srcset, you have a comma-separated series of values forsizes. - The first media query in the sequence that matches gets used, so sequencing does make a difference.
- Each value before the last value is a media query, then a space, then the size to use for the image if that media query is true. The final value in
sizesis the default width, for when no media query matches. For example:<img src="images/building.jpg" srcset="images/building.jpg 400w, images/building-2.jpg 800w, images/building-3.jpg 1200w" sizes="(min-width: 1200px) 1200px, (min-width: 800px) 800px, (min-width: 400px) 400px, 100vw" alt="Office Building" />
When the viewport is less than 400px, the entire viewport (100vw) will be used for the image width.
- Identifying the proper sizes here can take a fair amount of trial-and-error. You might even end up using
calc()and viewport widths as part of your sizing approach, such as:(min-width: 800px) calc(100vw - 100px)
<picture> Element
- The
<picture></picture>element was introduced in HTML5 as a way to add flexibility to the<img />element. - Within
<picture></picture>are zero or more<source />elements, and you must put an<img />inside to prove alternative text, image dimensions, as well as be a fallback (in the event of browser support lacking). - The first matching
<source />element is used and the others are ignored. The<img />element must be last, after all the<source />elements. - The
<source />element supportsmedia(for media queries based on the viewport width),type(for MIME-type matching),srcset, andsizes. - A basic structure (you could increase code complexity by adding more values to
srcsetand incorporatingsizesas well into the source tags) is:<picture> <source media="(max-width: 799px)" srcset="building-small.jpg" /> <source media="(min-width: 800px)" srcset="building-large.jpg" /> <img src="images/building-small.jpg" alt="Office Building" /> </picture>
Image Replacement
- The term "image replacement" refers to providing text as a replacement for a meaningful image that does not offer any information for assistive technology, such as screen reading software.
- Typically, this arises with CSS background images. On its own, the background image is invisible to the assistive technology.
- Let's assume you had a div element with a background image and you wanted to communicate some text about that background to assistive technology, while not showing that text to other users (since they can see the image).
- You might be inclined to put in the text and have it
display: noneor bevisibility: hidden. Both are terrible approaches, because assistive technology is very likely to ignore elements and text with those settings. - So what are acceptable approaches for that text?
- Style the text to have a non-visible size. Give the element holding the text
font-size: 0px; width: 0; height: 0; - Position the element holding the text off the viewport; this is typically absolute positioning with a substantial negative top and left.
- Position the background image over the text. In this approach you put an empty element inside the div, instruct the div to
position: relative, then move the background image CSS to the empty element, have it be absolutely positioned and displayed as a block, and set it to have the same width and height as the div:<div style="position: relative; top: 0; left: 0;"> <!-- this is the text alternative for assistive technology --> Front of four story office building viewed from the street <span style="z-index: 10; position: absolute; top: 0; left: 0; display: block; width: 400px; height: 300px; background: url(images/building.jpg) no-repeat top left;"></span> </div>
The image is now positioned over the text, so the text is not visible. However, the text will still be spoken/output by assistive technology. You would also shift the inline CSS to an external file.
- Style the text to have a non-visible size. Give the element holding the text
- If the markup is an anchor or a structural element (header, main, footer, nav, etc.) you should use an
aria-labelattribute instead, which will be spoken/output by assistive technology when the element is encountered. In the case of an anchor, this would be spoken/output rather than the text inside the anchor, which is great for "Read More..." links, because you can provide a rich, detailed description. This attribute cannot be used for divs unless some additional markup is added, soaria-labelis not a good option in that case.
Responsive Design and Background Images
- If you know that you do not want a background image to show at a given device size (typically smartphones), or that you want different images at different sizes (one background for phones and tablets, another for large displays), then it's just a matter of using media queries carefully.
- You want to ensure that your media queries are set up to only download one of those images, otherwise you're increasing page weight and potentially consuming data allowances for smartphone users.
Summary of Image Strategies and Options
| Scenario | Recommended Solution |
|---|---|
| Images are triggering brower scrollbars at smaller viewport sizes | {width: 100%; height: auto;} or {object-fit: cover;} |
| Background image is too small to fill the space | {background-size: cover;} |
| An image looks fuzzy on Retina displays and needs to look good on all displays. | Either use a larger version of the image (rendering it at smaller dimensions) or make versions at different sizes, using srcset with x descriptor to show the appropriate image. |
| Same as prior scenario, but there is a need to customize the height and width of the image based on the viewport. | Make versions at different sizes, using srcset with w descriptor and sizes to show the appropriate image and set its size based on viewport size.
|
| Entirely different images need to be loaded based on various device characteristics. | <picture></picture> with <source /> using media queries. |
| Usage of the AVIF, JPEG XL, or other newer image formats with spotty browser support. | <picture></picture> with <source /> using type attribute. |
| CSS backgrounds need to be accessible. | One of the image replacement techniques or, if the markup supports it (is an anchor or structural element) use aria-label. |
| A given background image should only be shown at particular device sizes. | Use media queries to isolate the device sizes. |
Favicons and App Icons
- Favicons are the small (generally 16 x 16) "favorite icons" that show in the browser tabs next to the page titles, as well as in browser favorites lists.
- As the years have passed and mobile devices have proliferated, there are also a multitude of other image versions that serve similar functions on mobile devices (as "app icons").
- Browsers look for these in your website's root directory, although you can also put them in a subdirectory. Ultimately, you may be adding something like this to the head region of your page:
<meta content="/ms-icon-144x144.png" name="msapplication-TileImage" /> <link href="/favicon-32x32.png" rel="icon" type="image/png" sizes="32x32" /> <link href="/favicon-96x96.png" rel="icon" type="image/png" sizes="96x96" /> <link href="/favicon-16x16.png" rel="icon" type="image/png" sizes="16x16" /> <link href="/apple-touch-icon.png" rel="apple-touch-icon" /> <link href="/apple-touch-icon-57x57.png" rel="apple-touch-icon" sizes="57x57" /> <link href="/apple-touch-icon-60x60.png" rel="apple-touch-icon" sizes="60x60" /> <link href="/apple-touch-icon-72x72.png" rel="apple-touch-icon" sizes="72x72" /> <link href="/apple-touch-icon-76x76.png" rel="apple-touch-icon" sizes="76x76" /> <link href="/apple-touch-icon-114x114.png" rel="apple-touch-icon" sizes="114x114" /> <link href="/apple-touch-icon-120x120.png" rel="apple-touch-icon" sizes="120x120" /> <link href="/apple-touch-icon-144x144.png" rel="apple-touch-icon" sizes="144x144" /> <link href="/apple-touch-icon-152x152.png" rel="apple-touch-icon" sizes="152x152" /> <link href="/apple-touch-icon-180x180.png" rel="apple-touch-icon" sizes="180x180" /> <link href="/android-chrome-192x192.png" rel="apple-touch-icon" sizes="192x192" />
- Based on the file names, there are icons there for Apple (iOS), Android, and Microsoft.
- For Android devices there is also a manifest.json file that gives further details about the Android images, as well as a browserconfig.xml file with details about the Microsoft images.
- I have found that even if I specify a subdirectory for the
hrefpaths that server logs will have errors involving failed lookups for these in the web root directory, so you might as well have the images there. Clearly, not all visitors / bots are reading the source code - they are just going straight to where they expect the files to be, since these follow a standard naming scheme. - And even though there is not a favicon.ico file listed in the above code example, you should still have that file in your web root directory, as that is where it is expected to be found.
- There are a variety of favicon / app icon generators out there; I generally just search the web for "favicon app icon generator" and there are many options. You want one that will also generate the other versions, beyond the favicon.ico file.
- Once you have these in place it may take a while for your browser to pick them up and start showing them. You may need to reload the page a few times, especially in Chrome, Edge, and Safari. Those browsers have previously stored that there is no favicon, so getting them to recognize that one exists can take a bit.