{"id":28202,"date":"2023-01-09T10:28:52","date_gmt":"2023-01-09T18:28:52","guid":{"rendered":"https:\/\/coderpad.io\/?p=28202"},"modified":"2023-06-07T13:39:38","modified_gmt":"2023-06-07T20:39:38","slug":"the-definitive-guide-to-responsive-images-on-the-web","status":"publish","type":"post","link":"https:\/\/coderpad.io\/blog\/development\/the-definitive-guide-to-responsive-images-on-the-web\/","title":{"rendered":"The Definitive Guide To Responsive Images On The Web"},"content":{"rendered":"\n<p>Using images on the web was made possible in 1995, by implementing the <code>&lt;img&gt;<\/code> tag into the HTML 2.0 specification.<\/p>\n\n\n\n<p>A simple syntax to do a simple job.<\/p>\n\n\n\n<p>But in the times of modern web and the multiplication of devices, HTML authors needed a way to serve different images, based on the screen size. Because images have a great impact on performance, we don\u2019t want to load a large picture on a mobile phone.<\/p>\n\n\n\n<p>We use responsive images to serve different images based on the device it&#8217;s being viewed on. We\u2019ll load a smaller image for smaller screens, reducing the size of the network request.<\/p>\n\n\n\n<p>To achieve responsive images, the <a href=\"https:\/\/www.w3.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">World Wide Web Consortium<\/a> (W3C) proposes two solutions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Using <code>srcset<\/code> on an <code>img<\/code> tag: a straightforward solution, that will cover most of the cases<\/li>\n\n\n\n<li>Using the <code>picture<\/code> element with sources inside: a solution providing more control over your images, allowing us to give more precise instruction to the browser<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Responsive images using <\/strong><strong><em>srcset<\/em><\/strong><\/h2>\n\n\n\n<p>Using an <code>img<\/code> with <code>srcset<\/code> is used for serving the same image in different sizes.<\/p>\n\n\n\n<p>If we want to display a different image (like a zoomed in version for mobile, or a dark-mode version) we\u2019ll want to use <code>picture<\/code> instead.<\/p>\n\n\n\n<p>Indeed, when using <code>srcset<\/code> we\u2019ll set <em>guidelines<\/em> for our browser, but we <em>cannot<\/em> predict which image the browser will actually prefer. The browser might display a larger version because it has it in its cache already.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Basic example: Targeting pixel density<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"goat.jpg\"<\/span> <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"goat.jpg, goat-2x.jpg 2x, goat-4x.jpg 4x\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"A goat climbing a mountain\"<\/span> \/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we gave three different images to our browser, using a density descriptor (e.g. 2x) that will display the image corresponding to the device screen density.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Better: Using width descriptor<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"goat.jpg\"<\/span> <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"goat.jpg 800w, goat-1200.jpg 1200w, goat-1800.jpg 1800w\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"A goat climbing a mountain\"<\/span> \/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, the browser will take the viewport width, multiply it by the pixel density and choose the closest-sized image.<\/p>\n\n\n\n<p>For example, on a device having a viewport width of <code>400px<\/code> and a density of <code>3<\/code>, the browser will look for the image with the closest width to <code>3 * 400px = 1200px<\/code> \u2013 in this case, <code>goat-1200.jpg<\/code>.<\/p>\n\n\n\n<p><em>You\u2019ll use this syntax most of the time, as it handles the device width and its density at the same time.<\/em><\/p>\n\n\n\n<p>While this syntax is helpful for many use cases, it is layout agnostic which means that it may not be optimized for images that don\u2019t take up 100% of the device width.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Even better: Adding the <\/strong><strong><em>sizes<\/em><\/strong><strong> attribute<\/strong><\/h3>\n\n\n\n<p>Usually, an image takes up only a portion of the page width, not its entirety. For this, we can use the <code>sizes<\/code> attribute to define the size of the image relative to the viewport.<\/p>\n\n\n\n<p>An example of how an image width may vary based on the layout:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2023\/01\/img_63bc1abd0c68a.png\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Different layouts and how the image reacts to them.<\/figcaption><\/figure>\n\n\n\n<p>We can add multiple values and use media conditions like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"goat.jpg\"<\/span> <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"goat.jpg 800w, goat-1200.jpg 1200w, goat-1800.jpg 1800w\"<\/span> <span class=\"hljs-attr\">sizes<\/span>=<span class=\"hljs-string\">\"(max-width: 500px) 100vw, (max-width: 700px) 96vw, 60vw\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"A goat climbing a mountain\"<\/span> \/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we have three sizes defined into our sizes attribute:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>(max-width: 500px) 100vw<\/code>: If the viewport is less than 500px, the image will take up 100% of the viewport width<\/li>\n\n\n\n<li><code>(max-width: 700px) 96vw<\/code>: If the viewport is less than 700px, the image will take up 96% of the viewport width<\/li>\n\n\n\n<li><code>60vw<\/code>: Else, the image will use 60% of the viewport width<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Fully control the image using <\/strong><strong><em>picture<\/em><\/strong><\/h2>\n\n\n\n<p>Using <code>srcset<\/code> and <code>sizes<\/code> is enough for most of the cases, but the browser still applies its \u201csecret sauce\u201d regarding which image it\u2019ll load. In some cases, it might show you a bigger version of the picture because it was already in the cache.<\/p>\n\n\n\n<p>That is fine in most cases, but sometimes we\u2019ll have completely different images for different sizes (e.g. a cropped picture for mobile), and using <code>picture<\/code> ensures that the browser will load the expected image \u2013 <em>exactly at the breakpoint we defined<\/em>.<\/p>\n\n\n\n<p>We also use <code>picture<\/code> for specifying a fallback image format, so we can use the brand-new image format without losing compatibility with legacy browsers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Basic example: Different images<\/strong><\/h3>\n\n\n\n<p>If we use not only different sizes of the same images but different images, we refer to this as <strong>art direction<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2023\/01\/img_63bc1abede8c5.png\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Different art direction that show a different level of details.<\/figcaption><\/figure>\n\n\n\n<p>The <code>picture<\/code> element contains at least one <code>img<\/code> and can have multiple sources to choose from, based on the display.<\/p>\n\n\n\n<p>That is how we declare these different images using <code>picture<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">picture<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span>\n    <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"illustration-big.png\"<\/span>\n    <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 1200px)\"<\/span>\n  \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span>\n    <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"illustration-medium.png\"<\/span>\n    <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 800px)\"<\/span>\n  \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n    <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"illustration-small.png\"<\/span>\n    <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"An admin panel\"<\/span>\n  \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">picture<\/span>&gt;<\/span> <\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We can specify media conditions using the <code>media<\/code> attribute, most of the time we\u2019ll check the width.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Supporting modern image format<\/strong><\/h3>\n\n\n\n<p>We can also use the <code>picture<\/code> syntax to elegantly support modern image formats. If the browser does not support it, it will fall back to the <code>img<\/code> element. It even works with old Internet Explorer (IE)!<\/p>\n\n\n\n<p>We use the <code>type<\/code> attribute, inside which we\u2019ll define the media type, using the <a href=\"https:\/\/www.iana.org\/assignments\/media-types\/media-types.xhtml\" target=\"_blank\" rel=\"noreferrer noopener\">Internet Assigned Numbers Authority (IANA) format<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">picture<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"illustration-small.webp\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"image\/webp\"<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"illustration-small.png\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"image\/png\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"An admin panel\"<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">picture<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\"><strong>Using <\/strong><strong><em>srcset<\/em><\/strong><strong> with the <\/strong><strong><em>picture<\/em><\/strong><strong> syntax<\/strong><\/h3>\n\n\n\n<p>We also can define <code>srcset<\/code> inside the <code>picture<\/code> syntax.<\/p>\n\n\n\n<p>But remember that <code>srcset<\/code> are just guidelines, so we are certain of the source but not of the <code>srcset<\/code>. The browser will decide itself what to load.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">picture<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span>\n    <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"\n    landscape-800 800w,\n    landscape-1600 1600w\"<\/span>\n<span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"image\/png\"<\/span>\n    <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 700px)\"<\/span>\n  \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span>\n    <span class=\"hljs-attr\">srcset<\/span>=<span class=\"hljs-string\">\"\n    portrait-200 200w,\n    portrait-400 400w,\n    portrait-600 600w\"<\/span>\n<span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"image\/png\"<\/span>\n  \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"&lt;https:\/\/via.placeholder.com\/400x200.png?text=Fallback&gt;\"<\/span>\/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">picture<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this example, we have multiple sizes for the landscape version and for the portrait version.<\/p>\n\n\n\n<p>We have full control over which version will be displayed, and we let the browser load the right size for it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Automate responsive image generation<\/strong><\/h2>\n\n\n\n<p>Loading different images based on the viewport is great, but we shouldn\u2019t have to generate these variants by hand.<\/p>\n\n\n\n<p>We shouldn\u2019t have to export each image in five different sizes.<\/p>\n\n\n\n<p>Let\u2019s explore how we can generate them automatically.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>On- the-fly responsive images using a Content Delivery Network (CDN)<\/strong><\/h3>\n\n\n\n<p>A lot of CDNs\/hosting services offer a way to resize your images by specifying a size in the URL, here are some that provide that feature:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/docs.netlify.com\/large-media\/transform-images\/#request-transformations\" target=\"_blank\" rel=\"noopener\">Netlify<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/transformation_reference\" target=\"_blank\" rel=\"noopener\">Cloudinary<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/blog.cloudflare.com\/announcing-cloudflare-image-resizing-simplifying-optimal-image-delivery\/\" target=\"_blank\" rel=\"noopener\">Cloudflare<\/a><\/li>\n<\/ul>\n\n\n\n<p>This is super useful, and since we usually externalize our picture to CDNs for performance matters, it might be the solution for you.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Responsive images on wordpress<\/strong><\/h3>\n\n\n\n<p>Since version 4.4, any image you upload to your media library will get generated into five different sizes.<\/p>\n\n\n\n<p>When using an image in a post it will be responsive by default. A good example of a transparent yet powerful way of dealing with responsive images.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Generating them on your server<\/strong><\/h3>\n\n\n\n<p>This is a solution I use on my personal blog, and it works perfectly.<\/p>\n\n\n\n<p>I just upload my images and<a href=\"https:\/\/github.com\/nhoizey\/images-responsiver\" target=\"_blank\" rel=\"noopener\"> image-responsiver<\/a> takes care of the rest.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Responsive images in CSS<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Using media queries<\/strong><\/h3>\n\n\n\n<p>Loading the right image in CSS is simply a matter of utilizing a media query (<code>@media<\/code>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-class\">.hero<\/span> {\n  <span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">url<\/span>(background-<span class=\"hljs-number\">800<\/span>.jpg);\n}\n<span class=\"hljs-keyword\">@media<\/span>\n  (<span class=\"hljs-attribute\">min-width:<\/span> <span class=\"hljs-number\">800px<\/span>){\n  <span class=\"hljs-selector-class\">.hero<\/span>{\n    <span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">url<\/span>(background-<span class=\"hljs-number\">1400<\/span>.jpg);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The browser will now load only the right image. We are using <code>min-width<\/code> in our example, but we could\u2019ve targeted the screen density too:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-class\">.hero<\/span> {\n  <span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">url<\/span>(background-<span class=\"hljs-number\">1<\/span>x.jpg);\n}\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">min-resolution:<\/span> <span class=\"hljs-number\">2dppx<\/span>),\n  (<span class=\"hljs-attribute\">-webkit-min-device-pixel-ratio:<\/span> <span class=\"hljs-number\">2<\/span>){\n  <span class=\"hljs-selector-class\">.hero<\/span>{\n    <span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">url<\/span>(background-<span class=\"hljs-number\">2<\/span>x.jpg);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\"><strong>Using <\/strong><strong><em>image-set<\/em><\/strong><\/h3>\n\n\n\n<p>There is also a new API, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/image\/image-set\" target=\"_blank\" rel=\"noopener\"><code>image-set<\/code><\/a> that aims to handle responsive images in CSS like we do with <code>srcset<\/code>.<\/p>\n\n\n\n<p><em>I would not recommend relying on this yet, since it\u2019s<\/em><a href=\"https:\/\/caniuse.com\/css-image-set\" target=\"_blank\" rel=\"noopener\"><em> <\/em><em>still being implemented into modern browsers<\/em><\/a><em>.<\/em><\/p>\n\n\n\n<p>The syntax looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-class\">.hero<\/span> {\n<span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">url<\/span>(<span class=\"hljs-string\">\"background-1x.jpg\"<\/span>); <span class=\"hljs-comment\">\/* fallback *\/<\/span>\n<span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">image-set<\/span>(\n    url(<span class=\"hljs-string\">\"background-1x.jpg\"<\/span>) <span class=\"hljs-number\">1<\/span>x,\n    <span class=\"hljs-built_in\">url<\/span>(<span class=\"hljs-string\">\"background-2x.jpg\"<\/span>) <span class=\"hljs-number\">2<\/span>x);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It has the same structure as the <code>srcset<\/code> attribute.<\/p>\n\n\n\n<p>We can use this syntax to serve different image formats as well:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-class\">.hero<\/span> {\n<span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">url<\/span>(<span class=\"hljs-string\">\"background.jpg\"<\/span>); <span class=\"hljs-comment\">\/* fallback *\/<\/span>\n<span class=\"hljs-attribute\">background-image<\/span>: <span class=\"hljs-built_in\">image-set<\/span>(\n    url(<span class=\"hljs-string\">\"background.avif\"<\/span>) <span class=\"hljs-built_in\">type<\/span>(<span class=\"hljs-string\">\"image\/avif\"<\/span>),\n    <span class=\"hljs-built_in\">url<\/span>(<span class=\"hljs-string\">\"background.jpg\"<\/span>) <span class=\"hljs-built_in\">type<\/span>(<span class=\"hljs-string\">\"image\/jpeg\"<\/span>));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><strong>Responsive video<\/strong><\/h2>\n\n\n\n<p>When dealing with video that does not intend to be the main content, but part of the design, loading an adapted version of the video is crucial for performances. It would be a huge waste of computing resources to load a 4k video for a mobile phone.<\/p>\n\n\n\n<p>Ideally, we\u2019d have a syntax similar to <code>picture<\/code>, like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"responsive-video\"<\/span> <span class=\"hljs-attr\">preload<\/span> <span class=\"hljs-attr\">autoplay<\/span> <span class=\"hljs-attr\">loop<\/span> <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"none\"<\/span> <span class=\"hljs-attr\">muted<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-360p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 641px)\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-480p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 855px)\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-720p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 1281px)\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-1080p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">\"(min-width: 1921px)\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-4k.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But <em>unfortunately<\/em>, this will likely never be implemented into browsers.<\/p>\n\n\n\n<p>What we can do is use some JavaScript to load the right video, based on the screen size. The main drawback is that this solution works only with <code>min-width<\/code> and cannot, for example, target screen density.<\/p>\n\n\n\n<p>Our syntax will now look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"responsive-video\"<\/span> <span class=\"hljs-attr\">preload<\/span> <span class=\"hljs-attr\">autoplay<\/span> <span class=\"hljs-attr\">loop<\/span> <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"none\"<\/span> <span class=\"hljs-attr\">muted<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-360p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pseudo-source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-360p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">max-size<\/span>=<span class=\"hljs-string\">\"641\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">pseudo-source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pseudo-source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-480p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">max-size<\/span>=<span class=\"hljs-string\">\"855\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">pseudo-source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pseudo-source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-720p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">max-size<\/span>=<span class=\"hljs-string\">\"1281\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">pseudo-source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pseudo-source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-1080p.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span> <span class=\"hljs-attr\">max-size<\/span>=<span class=\"hljs-string\">\"1921\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">pseudo-source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pseudo-source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"header-4k.webm\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/webm\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">pseudo-source<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Notice we\u2019re using <code>pseudo-source<\/code> now, so it doesn\u2019t mess with the browser. Also, we don\u2019t use <code>media<\/code> anymore, but a <code>max-size<\/code> attribute.<\/p>\n\n\n\n<p>The <code>source<\/code> defaults to the smaller version, and the JavaScript will replace its source with the matching one.<\/p>\n\n\n\n<p>Let\u2019s add this JavaScript snippet to our code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> handleSourceChange = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<span class=\"hljs-comment\">\/\/ get the video element<\/span>\n  <span class=\"hljs-keyword\">const<\/span> video = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\".responsive-video\"<\/span>);\n\n  <span class=\"hljs-keyword\">if<\/span> (!video) <span class=\"hljs-keyword\">return<\/span>;\n\n<span class=\"hljs-comment\">\/\/ get all the sources<\/span>\n  <span class=\"hljs-keyword\">const<\/span> sources = video.querySelectorAll(<span class=\"hljs-string\">\"pseudo-source\"<\/span>);\n\n<span class=\"hljs-comment\">\/\/ the source that we'll replace with the matching video<\/span>\n  <span class=\"hljs-keyword\">const<\/span> actualSource = video.querySelector(<span class=\"hljs-string\">\"source\"<\/span>);\n\n<span class=\"hljs-comment\">\/\/ getting the viewport width<\/span>\n  <span class=\"hljs-keyword\">const<\/span> width = video.clientWidth;\n\n<span class=\"hljs-comment\">\/\/ sort the sources<\/span>\n  <span class=\"hljs-keyword\">const<\/span> sortedSources = elementsToArray(sources).sort(\n    <span class=\"hljs-function\">(<span class=\"hljs-params\">a, b<\/span>) =&gt;<\/span> a.maxWidth - (b ? b.maxWidth : <span class=\"hljs-number\">-1<\/span>)\n  );\n\n<span class=\"hljs-comment\">\/\/ we try the max widths and keep the last that matched<\/span>\n  <span class=\"hljs-keyword\">let<\/span> lastSource;\n  sortedSources.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">item, key<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (width &gt; item.maxWidth) {\n      lastSource = sortedSources&#91;key + <span class=\"hljs-number\">1<\/span>];\n    }\n  });\n\n  <span class=\"hljs-comment\">\/\/ if none worked, we'll load the last one<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (!lastSource) lastSource = sortedSources&#91;<span class=\"hljs-number\">0<\/span>];\n\n  <span class=\"hljs-comment\">\/\/ change the source of the actual element<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (lastSource.src != actualSource.getAttribute(<span class=\"hljs-string\">\"src\"<\/span>)) {\n    video.pause();\n    <span class=\"hljs-keyword\">let<\/span> elapsed = video.currentTime;\n    actualSource.setAttribute(<span class=\"hljs-string\">\"src\"<\/span>, lastSource.src);\n    video.load();\n    video.currentTime = elapsed;\n    video.play();\n  }\n};\n\n<span class=\"hljs-comment\">\/\/ utility function<\/span>\n<span class=\"hljs-keyword\">const<\/span> elementsToArray = <span class=\"hljs-function\">(<span class=\"hljs-params\">elements<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> results = &#91;];\n  elements.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">item<\/span>) =&gt;<\/span> {\n    results.push({\n      <span class=\"hljs-attr\">src<\/span>: item.getAttribute(<span class=\"hljs-string\">\"src\"<\/span>),\n      <span class=\"hljs-attr\">maxWidth<\/span>: item.getAttribute(<span class=\"hljs-string\">\"max-size\"<\/span>),\n    });\n  });\n  <span class=\"hljs-keyword\">return<\/span> results;\n};\n\n<span class=\"hljs-comment\">\/\/ handling responsive videos on load<\/span>\n<span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">\"DOMContentLoaded\"<\/span>, handleSourceChange);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And that\u2019s it!<\/p>\n\n\n\n<p>If you want a working example, I&#8217;ve attached a sandbox below. I used different videos for the breakpoints to be obvious.<\/p>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 85%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=238721&#038;use_question_button\" width=\"640\" height=\"544\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p><em>And we are done with this guide! Hope it helps you understand why responsive images are important and which syntax to use for which cases.<\/em><\/p>\n\n\n\n<p><strong><em>I&#8217;m Tom Quinonero, I write about design systems and CSS,<\/em><\/strong><a href=\"https:\/\/twitter.com\/tomquinonero\" target=\"_blank\" rel=\"noopener\"><strong><em> Follow me on Twitter<\/em><\/strong><\/a><strong><em> for more tips and resources \ud83e\udd19<\/em><\/strong>.<\/p>\n\n\n\n<p><strong>Links and sources:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Learn\/HTML\/Multimedia_and_embedding\/Responsive_images\" target=\"_blank\" rel=\"noopener\">Responsive Images on MDN<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/thehistoryoftheweb.com\/the-origin-of-the-img-tag\/\" target=\"_blank\" rel=\"noopener\">The Origin of the IMG Tag<\/a>: The history of images in the browser<\/li>\n\n\n\n<li><a href=\"https:\/\/imagekit.io\/website-analyzer\" target=\"_blank\" rel=\"noopener\">ImageKit Website Analyzer<\/a> : An amazing tool that\u2019ll check if the images of a webpage are sized correctly<\/li>\n\n\n\n<li><a href=\"https:\/\/css-tricks.com\/using-performant-next-gen-images-in-css-with-image-set\/\" target=\"_blank\" rel=\"noopener\">Using Performant Next-Gen Images in CSS with image-set<\/a>: A guide on image-set<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In modern times, HTML authors need to serve different images based on screen sizes as images have a great impact on the performance of a web application. In this guide to responsive images on the web, you&#8217;ll learn how to better serve images based on the device it&#8217;s being viewed on.<\/p>\n","protected":false},"author":1,"featured_media":28249,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"persona":[29],"blog-programming-language":[62],"keyword-cluster":[],"class_list":["post-28202","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/28202","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/comments?post=28202"}],"version-history":[{"count":61,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/28202\/revisions"}],"predecessor-version":[{"id":34557,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/28202\/revisions\/34557"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media\/28249"}],"wp:attachment":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media?parent=28202"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/categories?post=28202"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/tags?post=28202"},{"taxonomy":"persona","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/persona?post=28202"},{"taxonomy":"blog-programming-language","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/blog-programming-language?post=28202"},{"taxonomy":"keyword-cluster","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/keyword-cluster?post=28202"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}