Folks, we’ve got an image bloat problem. But, it’s OK. We can fix it.

When someone on an iPhone with a 320px wide screen on a 3G network arrives at your fancy-schmancy WordPress blog, they don’t need a 1020px wide image. They need an image that’s 320px wide. Each image you load that’s more than 320px wide compounds their slow load time.

Sure, we could use techniques like lazy loading images to make the time to “first-load” of content a little more bearable, but how about we go to the root of the problem? Not sending people images that are larger than they need.

Enter srcset and sizes!

New specs for responsive images (not just images that squish and stretch), but providing images for your user’s screen size and resolution. If you don’t know anything about that and why letting the browser choose which image is best to serve up to your user, read Eric Portis’ amazing & fun post about srcset & sizes.

Awesome, right?! Now let’s sit back and enjoy us some cute cat videos.

…but, if you’re using WordPress, adding images with WordPress’s default “Add Media” button doesn’t generate future-proof code using the srcset and sizes attributes.

*Sad trumpet* Waaaah, waaaaaaaaah.

Polyfill Srcset & Sizes

First, we need a polyfill to support srcset & sizes on old browsers. Fortunately, for us, Scott Jehl at Filament Group helped make Picturefill so we could start using responsible web design today.

Install that on your site and come back to finish this up. This article will still be here.

Don’t Write that href, Shortcode It!

I love pure code. If I have to use a shortcode, I grumble a little, but know that it’s saving me a ton of time. But when I have to write HTML into the WordPress post, I get annoyed, because if I have to make changes to it in the future, I know it’ll be difficult. When WordPress generates images, it looks like something like this:


<img class="alignleft wp-image-382" 
src="skateboarding-dog-1020x612.jpg" 
alt="Dog wearing sunglasses skateboarding a huge vert ramp" 
width="1020" height="612" />

So, even if you don’t want to use srscet and sizes on your images, what happens when you want to move over to use them in the future? That’s a lot o’ MySQL queries, my friend. Lots of search and replace and hoping you caught everything. I’d rather write a shortcode that’ll pass on the variables I need to generate responsive image code. Then, if (or when) I need to change it, I just have to update the shortcode function, not the database entries.

So, how’s this for a shortcode?


[rsp_img id="332"]

Look good? Alright! Give it the Attachment (image) ID, and it generates this code on your site:


<img class="post-img align-none size-large"
sizes="100vw"
srcset="/img/radical-skateboarding-dog-1020x612.jpg 1020w,
        /img/radical-skateboarding-dog-640x384.jpg 640w,
        /img/radical-skateboarding-dog-480x288.jpg 480w,
        /img/radical-skateboarding-dog-320x192.jpg 320w,
        /img/radical-skateboarding-dog-150x90.jpg 150w"
alt="Dog wearing sunglasses skateboarding a huge vert ramp"/>

Not the prettiest to read… but, it is to spec. That list of srcset images lets the browser pick the one that’ll fit best for your user’s screen.

There’s a few options we can pass to it too:

  • id– the attachment ID, as mentioned earlier.
  • alignment– left, right, or center.
  • size– thumbnail, medium, or large.

So, when you enter a shortcode into your editor with all the options, it might look something like this:


[rsp_img id="332" alignment="center" size="large"]

Here Comes the PHP – Adding Image Sizes

First off, we need to register our image sizes so when a new image gets uploaded, all the right sized images get created. These are the sizes I use on this blog.


add_image_size('rsp_img_xl', 1020);
add_image_size('rsp_img_large', 640);
add_image_size('rsp_img_medium', 480);
add_image_size('rsp_img_small', 320);
add_image_size('rsp_img_tiny', 150);

I suggest setting “large” to be the full width of your content area on a desktop / laptop. Then, have one size that’s twice as large for retina versions of those desktops.

Hijack the Add Media Output

When a user adds an image to their post with the “Add Media” button, we want it to insert our rsp_img shortcode, not the img HTML. So, we need to add a filter to grab the submission and send our shortcode instead of the default HTML. Here’s how we’ll do that:


function insert_rsp_img_shortcode($html, $id, $caption, $title, $align, $url, $size) {
    // build the align attribute
    $img_align = (!empty($align) ? ' align="'.$align.'"' : '');
    // build the size attribute
    $img_size = (!empty($size) ? ' size="'.$size.'"' : '');
    // generate the shortcode with id attribute
    $rsp_img_shortcode = '[rsp_img id="'.$id.'"'.$img_align.$img_size.']';
    // send the shortcode to the editor
    return $rsp_img_shortcode;
}
add_filter( 'image_send_to_editor', 'insert_rsp_img_shortcode', 10, 9 );

Now the shortcode gets output to the editor when you add media to the post. Awesome! But… we still need to build the functionality…

Run the rsp_img Shortcode

Here’s the full functionality of the rsp_img shortcode. It’s commented like crazy to make it as easy as possible for you to follow along with what is happening. Hack away at it, or just copy / paste it into your functions.php file along with the insert_rsp_img_shortcode function and add_image_sizes code.


// what should the shortcode do?
function rsp_img_shortcode( $atts ) {
    // get our attributes from the shortcode
    extract(shortcode_atts(array(
        'align' => '',
        'id' =>'',
        'size' => '',
    ), $atts));


    // get all of our image sizes that we set above with add_image_size() earlier
    $img_retina = wp_get_attachment_image_src( $id, 'rsp_img_xl' );
    $img_lrg = wp_get_attachment_image_src( $id, 'rsp_img_large' );
    $img_med = wp_get_attachment_image_src( $id, 'rsp_img_medium' );
    $img_small = wp_get_attachment_image_src( $id, 'rsp_img_small' );
    $img_tiny = wp_get_attachment_image_src( $id, 'rsp_img_tiny' );

    // get the alt text
    $alt_text = get_post_meta($id , '_wp_attachment_image_alt', true);

    // get the image caption
    $attachment = get_post( $id );
    $caption = $attachment->post_excerpt;

    // if they want medium or small, we'll add some media queries to serve up the right images by the viewport
    // we don't actually need to change the srcset, just the sizes attribute because we're letting the browser pick the image size
    if($size === 'thumbnail') :
        // I'm making some generalizations here to deliver the right size image
        // Thumbnail doesn't mean 150x150 here, it means 'smaller than medium but appropriately sized to the screen'
        // So, thumbnail to me means:
        // 1/4 of viewport for desktop
        // 1/3 of viewport for tablets
        // 1/2 of viewport for mobile devices
        // full width if less than that
        $img_sizes = '(min-width: 48em) 25vw
                      (min-width: 34em) 33vw,
                      (min-width: 24em) 50vw,
                      100vw';
    elseif($size === 'medium') :
        // 1/2 of viewport for desktops and tablets, full width otherwise
        $img_sizes = '(min-width: 34em) 50vw,
                      100vw';
    else :
        // if you don't specify a size, or it's something other than 'thumbnail' or 'medium',
        // we're passing you the full viewport size img, dawg
        $img_sizes = '100vw';
    endif;


    // generate the html of the image. we're adding our classes, and, if there's no caption,
    // we're adding a post-img-no-caption class for styling
    $rsp_img_html = '<img class="post-img'.(!empty($align) ? ' align-'.$align : '').(!empty($size) ? ' size-'.$size : '').(empty($caption) ? ' post-img-no-caption' : '').'"
                        sizes="'.$img_sizes.'"
                        srcset="'.$img_retina[0].' '.$img_retina[1].'w,'.
                                $img_lrg[0].' '.$img_lrg[1].'w,'.
                                $img_med[0].' '.$img_med[1].'w,'.
                                $img_small[0].' '.$img_small[1].'w,'.
                                $img_tiny[0].' '.$img_tiny[1].'w"
                        alt="'.$alt_text.'"/>';
    if(!empty($caption)) :
        // ooo! We have a caption! We should wrap that image in a figure element so we can use
        // appropriate HTML5 syntax and put the caption in a figcaption element
        $rsp_fig = '<figure class="post-figure'.(!empty($align) ? ' align-'.$align : '').(!empty($size) ? ' size-'.$size : '').'">'
                        .$rsp_img_html
                        .'<figcaption class="post-figcaption">'
                            .$caption
                        .'</figcaption>
                    </figure>';
    else :
        // no caption. let's just output the straightup img html we made earlier
        $rsp_fig = $rsp_img_html;
    endif;

    // send on the html we generated to the page
    return $rsp_fig;
}
add_shortcode('rsp_img', 'rsp_img_shortcode');

Style the Image & Caption

We have our code running great, but it’s unstyled. Here’s the basic CSS to get it all wrapped up nice. Adjust to fit your style, and you’ll be good to go! I’m using Sass, but you can extract it to CSS too if you don’t have a Sass workflow. It’s marked up with comments to show you what’s happening and why.


.post-figure {
    background: #f5f2f0;
    border-bottom: 4px solid #ddd;
    margin-bottom: 1.6em;
    max-width: 100%;

    .post-img {
        border: 1px solid #f5f2f0;
        display: block;
        margin: auto 0;
        width: 100%;
    }

    .post-figcaption {
        font-size: .85em;
        padding: 1.3em 1.3em 1em;
        color: #787A7E;
        margin-bottom: 0;
    }
}


.post-figure,
.post-img-no-caption {
    margin-bottom: 2em;
    // we only need to apply this to the image if there's no figure
    // wrapped around it (like when there's no caption)
    width: 100%; // we're assuming a 100% width for a fallback if no size is specified, then overwriting if needed

    &.align-none,
    &.align-center {
        // we're treating these as the same. center it.
        display: block;
        margin-right: auto;
        margin-left: auto;
    }

    // floats
    &.align-right {
        float: right;
        margin-left: 2em;
    }

    &.align-left {
        float: left;
        margin-right: 2em;
    }

    // sizes
    // these all match our "sizes" attribute in our  shortcode
    &.size-thumbnail {
        width: 100%;

        @media screen and (min-width: 24em) {
            width: 50%;
        }

        @media screen and (min-width: 34em) {
            width: 33%;
        }

        @media screen and (min-width: 48em) {
            width: 25%;
        }
    }

    &.size-medium {
        width: 100%;

        @media screen and (min-width: 34em) {
            width: 50%;
        }

        @media screen and (min-width: 48em) {
            width: 30%;
        }
    }

}

What’s Next for Responsive Images?

I’m not sure! That’s why I wanted to use a shortcode, to make sure I could adjust as specs change.

Have any ideas on how to improve this? See any oversights in my set-up? Let me know on twitter or in the comments below.