Fluid embedded movies

I've just finished up Ethan Marcotte's excellent little book on Responsive Web Design. Well worth picking up, if only because it compiles and expands on his excellent articles on fluid grids, fluid images, and media query-based responsive design.

Responsive Web Design dedicates an entire chapter to fluid images (see also Ethan’s blog post on the matter). Basically, the trick is this in your CSS:

img {
   max-width: 100%;
}

Like magic, an image will shrink along with the width of its container, and it crucially will also not expand beyond its intrinsic pixel dimensions.

There are some other details to making it work nicely in older versions of Internet Explorer, but that's the gist of it.

But what about movies?

Movin’ on to movies

Luckily, as Ethan points out, most browsers treat other multimedia just like images when it comes to resizing, so you can roll with this:

img, 
embed,
object, 
iframe, 
video {
   max-width: 100%;
}

iframe will cover you for the current YouTube and Vimeo embed markup.

In which I get controversial

You may disagree with this, but you might want to consider adding the !important flag to your max-width declaration, like so:

img, 
embed, 
object, 
iframe, 
video {
   max-width: 100% !important;
}

This is a bit of a CSS sledgehammer, but let me explain why you might want to do it.

One of the key tricks for using max-width to contain elements is that they cannot have width or height attributes in the markup. So you can't use <img height="500" width="500" src="whatever.jpg" alt="whatever" />. It must be <img src="whatever.jpg" alt="whatever" />. Same goes for movies.

So, you're making the assumption that everyone always will remember to skip putting the dimensions in the markup. If you write all your own code, fine. I know you're good.

But this is not how the world works right now. If you're authoring HTML in most text editors and drag-and-drop an image, you'll get the dimensions on the <img> tag. Most WYSIWYG editors will add the dimensions if you insert an image. The embed code for every streaming video site includes dimensions. It's almost entirely unavoidable.

The !important declaration will override any of those dimensions in the markup (either style="width: 900px;" or width="900") that you or your clients forget to remove. You may not love it, but it’s easy, it’s idiot-proof, and it works. Plus, how often do you use max-width for anything else? It's unlikely that you'll have to override it.

A tweak for desktop Safari

So far so good in most browsers, (IE7+, Chrome, Firefox, Mobile Safari, Opera). But I've noticed in Safari on OS X (but, strangely, not Chrome or Mobile Safari) that embedded videos do not always respect the max-width declaration. I believe this is because those elements are set to display: inline-block by default.

So, an easy fix that doesn't really break anything else is to switch them all to display: block.

img, 
embed, 
object, 
iframe, 
video {
   max-width: 100% !important;
   display: block;
}

Avoiding bonsai kittens

Now at least your movies won't break your layout. That's getting somewhere. Unfortunately, most browsers do not seem to scale embedded movies proportionately. The widths will change to fit, but the heights won't always change proportionally. Worst case, your video might look something like a bonsai kitten, with a smashed video crammed into a very tall container. Check out this example on JSFiddle.

Won't someone please think of the kittens?

One option to avoid this is to use Javascript to resize all the dimensions of the element proportionately.

Chris Coyier at CSS Tricks has a jQuery-powered solution that's specifically for YouTube videos, but it sets the dimensions with pixels and must recalculate the new dimensions every time the browser is resized.

A different solution that requires additional markup around your videos is Thierry Koblentz’s intrinsic ratios idea (via TJKDesign and Web Designer Wall). Here, you create a box, add padding-top or padding-bottom with a percentage to force the box to stay in a certain aspect ratio, and then force the video to stay within that box using absolute positioning.

Koblentz’s article suggests setting up some classes that define common aspect ratios, like so:

.vid-container {
   position: relative;
   padding-top: 35px;
}

.sixteenByNine {
   padding-bottom: 56.25%;
}

.fourByThree {
   padding-bottom: 75%;
}

.vid-container iframe, 
.vid-container embed, 
.vid-container object {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

Wrap your the embed code for your movie with a <div class="vid-container sixteenByNine"> element and it works delightfully well. Because you're forcing the video to fit in a box with a set ratio of width to height, the videos resize proportionately.

The catch is that you have to remember to put additional markup around the embed video (Koblentz’s article offers some JavaScript automation if you're using Adobe's SWFObject script, so this wasn't written for your typical embedded video code). And you'll have to do some math (or guesstimation) to figure out the proper class to use for the aspect ratio. And, like, math is haaaaard, ya know?

So why don't we automate that with some JavaScript? I've been testing a solution that works pretty well that uses the Koblentz intrinsic approach along with some JavaScript to dynamically wrap the videos in the .vid-container div and uses the height and width of the video to dynamically figure out the aspect ratio. So you can lean on the JavaScript to add the extra markup, you don't need to maintain multiple classes for the aspect ratios, and you don't need to do any math. That makes my English major brain happy.

Start with the same basic foundation in the CSS:

.vid-container {
   position: relative;
   padding-top: 35px; /* for the player's chrome (esp YouTube) */
}

.vid-container iframe, 
.vid-container embed, 
.vid-container object {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

Same starting point, but no particular classes for certain aspect ratios.

Now, the JavaScript. I'm using jQuery here because it comes with some nice helper functions to get the dimensions and wrap the video in another element, but this could easily be adopted to other frameworks:

$(function(){

   $('object, embed, iframe').each(function(){
      //cache the video
      var vid = $(this),
      //get its width
      vid_width = vid.width(),
      //get the width of its container
      vid_parent_width = vid.parent().width();

      //only if the video is breaking out of its parent...
      if (vid_width > vid_parent_width) {
         
         //get its height
         var vid_height = vid.height(),
         //calculate the aspect ratio as a %
         vid_aspect = vid_height/vid_width * 100 + "%"; 

         //wrap the video in our div.vid-container
         vid.wrap('<div class="vid-container">');

         //set the padding to the aspect ratio as a percentage
         //vid.parent() is now the div.vid-container
         vid.parent().css('padding-bottom', vid_aspect); 

      }

   });
	
});

So there you go. We check the width of both the video and its parent. Then, if the video is larger than its parent that's a problem, so it gets wrapped in our <div class="vid-container"> and gets an appropriate aspect ratio with padding-bottom. Neat!

So that ends up nicer-looking than just using max-width: 100% in most browsers. But this isn't perfect.

One drawback is that I've coded this to only check the videos once on the page load. If a video fit when the page loaded, but then you resize the browser and the video is now too wide, it's not going to be wrapped in the extra markup and accordingly won't resize. I'm assuming right now that normal people aren't constantly resizing their browser windows, but if you’re worried you might want to add a resize event to check the videos again. Chris Coyier’s and Mathias Bynens’ method that I mentioned earlier uses a resize event that you can take a look at for inspiration.

Another option would be to remove the check if the video is wider than its parent element and always wrap every video in a <div class="vid-container"> element. The drawback here is that the video will then always expand to be as wide as its parent. So you might end up with some videos that stretch out beyond their static dimensions. This is pretty unlikely, though. And given the generally crap quality of streaming video, it probably wouldn't be noticeable.

Another drawback is that we're relying on JavaScript. However, we can still use the uglier-but-at-least-not-breaking-things CSS max-width method as a fallback for people that disable scripts. To do that, I would tack on a no-js class to your <html> tag and use some script to change that to js (the following assumes jQuery again):

$('html').removeClass('no-js').addClass('js');

(The Modernizr library does exactly this for you if you're using it.)

With that, now'll you have a .no-js hook for your CSS when JavaScript is disabled. Here's the whole CSS:

/* 
   Fluid images
   and fallback for embedded media when there's no JavaScript 
   Adapted from:
   http://unstoppablerobotninja.com/entry/fluid-images/
*/
img,
.no-js embed,
.no-js object,
.no-js iframe,
video {
   max-width: 100% !important;
   display: block;
}

/* 
   Intrinsic ratios for videos
   Adapted from: 
   http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ 
*/
.vid-container {
   position: relative;
   padding-top: 35px; /* little extra for the player's chrome (esp YouTube) */
}

.vid-container iframe, 
.vid-container embed, 
.vid-container object {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

And all the JS (again, after you've loaded jQuery):

$(function(){

   $('html').removeClass('no-js').addClass('js');

   $('object, embed, iframe').each(function(){
      //cache the video
      var vid = $(this),
      //get its width
      vid_width = vid.width(),
      //get the width of its container
      vid_parent_width = vid.parent().width();

      //only if the video is breaking out of its parent...
      if (vid_width > vid_parent_width) {
         
         //get its height
         var vid_height = vid.height(),
         //calculate the aspect ratio as a %
         vid_aspect = vid_height/vid_width * 100 + "%"; 

         //wrap the video in our div.vid-container
         vid.wrap('<div class="vid-container">');

         //set the padding to the aspect ratio as a percentage
         //vid.parent() is now the div.vid-container
         vid.parent().css('padding-bottom', vid_aspect); 

      }

   });
	
});

See it in action at this updated JSFiddle.

And let me know if it helps!

Am I doing it wrong?

Comments? I don’t do open comments. Life is too short.

If you have something to say, get in touch via .(JavaScript must be enabled to view this email address) or on Twitter. If it's interesting, I'll post it.