Skip to content

estelle/clowncar

Repository files navigation

Clown Car Technique for Responsive Images

We can use media queries within SVG to serve up the right image. The beauty of the "Clown Car" technique is that all the logic remains in the SVG file. I've called it the "Clown Car" technique since we are including (or stuffing) many images (clowns) into a single image file (car).

When you mark up your HTML, you simply add a single call to an SVG file.

<img src="awesomefile.svg" alt="responsive image">

Now isn't that code simple?

Unfortunately, due to content security policies (not CSP spec, but general security), FF and WebKit to not allow SVG files imported as <img> to call in raster images or scripts. Therefore, we use the almost as simple <object> tag, which as a non-empty element can provide for a child fallback for IE8 and Android 2.3.

<object data="awesomefile.svg" ></object>

The magic is that SVG supports both media queries and rasterized images.

In our SVG file, using the <image> element, will include the all the images that we may need to serve, and include all the media queries.

Here is the code for one of the SVG files:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="329">
  <title>The Clown Car Technique</title>
    <defs>
    <style>
    image {display: none;}
    #small {display: block}
     @media screen and (min-width: 401px) and (max-width: 700px) {
        #medium {display: block}
        #small {display: none}
    }
      @media screen and (min-width: 701px) and (max-width: 1000px) {
        #big {display: block}
        #small {display: none}
    }
     @media screen and (min-width: 1001px)  {
      #huge {display: block}
      #small {display: none;}
    }
    </style>
  </defs>
  <g>
    <image id="small"  height="329" width="300" xlink:href="images/small.png" />
    <image id="medium" height="329" width="300" xlink:href="images/medium.png" />
    <image id="big"    height="329" width="300" xlink:href="images/big.png" />
    <image id="huge"   height="329" width="300" xlink:href="images/huge.png" />
  </g>
  </svg>

Unfortunately, when this file is used, all 4 PNGs are retrieved from the server. To solve this issue, we can use background images instead:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 329" preserveAspectRatio="xMidYMid meet">
 <title>Clown Car Technique</title>
 <style>
  svg {
    background-size: 100% 100%;
    background-repeat: no-repeat;
  }
 @media screen and (max-width: 400px) {
  svg {background-image: url(images/small.png");}
 }
 @media screen and (min-width: 401px) and (max-width: 700px) {
  svg {background-image: url(images/medium.png);}
 }
 @media screen and (min-width: 701px) and (max-width: 1000px) {
  svg {background-image: url(images/big.png);}
 }
 @media screen and (min-width: 1001px) {
  svg {background-image: url(images/huge.png);}
 }
 </style>
</svg>

This version only downloads the images required, thereby solving the multi-HTTP and waste of bandwidth concerns. However, it seems to trigger more Content Security Policy issues than the previous SVG.

The SVG file has it's own declared size, but when included in HTML, the media query size is based on the proportions of the DOM node in the HTML --. it reflects the space provided to it.

Open the SVG in your browser and resize your browser window. As the window grows and shrinks the image the SVG displays changes. The image size takes up the full screen because it has a background size of 100%. It's when we include the SVG in the <object> of a flexible layout that the magic happens. You'll note that the first time you load the SVG there may be flickers of white as the browser requests the next required PNG.

When you include the SVG in your HTML <img> with a flexible width, such as 70% of viewport, as you grow and shrink the container, the image responds to the changes. The "width" media query in the SVG is based on the element width in which the SVG is contained, not the viewport width.

The foreground <img> version works perfectly in Opera. In Chrome and Safari, I need to open the SVG file first, after which the HTML file containing the foreground SVG image works perfectly. In Firefox, the SVG works. Firefox supports SVG and supports SVG as background image, but blocks the importing of external raster images due to their content security policy (CSP).

The content security policy does make sense: you don't want a file to be pulling in untrustworthy content. SVG technology is supported. It is just being prevented from pulling in external raster image. Firefox prevents it altogether. Safari and Chrome work if the SVG is preloaded. We can work with the browser vendors to get this CSP lifted, at least for same-origin files.

The browsers do all support the media queries with the SVG. They all support SVG as foreground or content images. They all support SVG as background images. The support just isn't all the same.

Responsive SVG for foreground images works. It simply works right out of the box. By using background images within the SVG itself the SVG only requests the file it needs. The raster images are background images. Only the required image is downloaded. The only negative is that it requires 2 http requests: one for the image and one for the SVG. I am currently working on getting SVG as data URI to work. I am not there yet.

Another pro for this technique: similar to how we separate content from presentation from behavior: this method enables us to also separate out images -- all the logic is in the SVG image instead of polluting our CSS or HTML.

The main question is: should we attempt this with <object>, or should we get browser vendors to change their content security policy to enable this technique to work as <img> directly?

Accessibility

Object does not support the alt attribute, which make images more accessible. SVG can be made accessible with the help of the TITLE. So, don't forget the title as alt attribute in your SVG