Create fluid width videos

Create fluid width videos

CSS-Tricks' Chris Coyier talks us through different CSS and jQuery solutions to create responsive and fluid width videos that maintain aspect ratio and work with all major video hosts, including YouTube and Vimeo

In a world of responsive and fluid layouts on the web one media type stands in the way of perfect harmony: video. There are lots of ways in which video can be displayed on your site. You might be self hosting the video and presenting it via the HTML5 <video> tag. You might be using YouTube or Vimeo which provides <iframe> code to display videos. You might be using Viddler or Blip.tv which provide nested object/embed tags to display a Flash player. In each of these scenarios, it is very common for a static width and height to be declared.

  1. <video width="400" height="300" ...
  1. <iframe width="400" height="300" ...
  1. <object width="400" height="300" ...
  2.      <embed width="400" height="300" ...

Guess what. Declaring static widths isn't a good idea in fluid width environments. What if the parent container for that video shrinks narrower than the declared 400px? It will bust out and probably look ridiculous and embarrassing.

Simple and contrived, but still ridiculous and embarassing
Simple and contrived, but still ridiculous and embarassing

So can't we just do this?

  1. <video width="100%" ...

Well, yep, you can. If you are using standard HTML5 video, that will make the video fit the width of the container. It's important that you remove the height declaration when you do this, so that the aspect ratio of the video is maintained as it grows and shrinks, lest you get awkward "bars" to fill the empty space (unlike images, the actual video maintains its aspect ratio regardless of the size of the element). You can get there via CSS (and not worry about what's declared in the HTML) like this:

  1. video {
  2.   width: 100%    !important;
  3.   height: auto   !important;
  4. }
Using HTML5 video, fluid is easy. The width is set in percentage so it resizes as its container does, and the height comes along for the ride
Using HTML5 video, fluid is easy. The width is set in percentage so it resizes as its container does, and the height comes along for the ride

<iframe> video (YouTube, Vimeo, etc)

Our little 100% width trick isn't going to help us when dealing with video that is delivered via iframe. Setting a height is required, otherwise browsers will render the iframe at a static height of 150px, which is far too squat for most video and makes for more R&E (Ridiculous and Embarrassing). Literally all browsers will render iframe, canvas, embed, and object tags as 300px x 150px if not otherwise declared. Even if this isn't present in the UA stylesheet.

The iframe above is missing its height attribute. While it's 100% width, its height is a static (and impractical) 150px
The iframe above is missing its height attribute. While it's 100% width, its height is a static (and impractical) 150px

Fortunately there are a couple of possible solutions here. One of them was pioneered by Thierry Koblentz and presented on A List Apart in 2009: Creating Intrinsic Ratios for Video. With this technique, you wrap the video in another element which has an intrinsic aspect ratio, then absolute position the video within that. That gives us fluid width with a reasonable height we can count on.

  1. <div class="videoWrapper">  
  2.     <!-- Copy & Pasted from YouTube -->
  3.     <iframe width="560" height="349" src="http://www.youtube.com/embed/n_dZNLr2cME?rel=0&hd=1" frameborder="0" allowfullscreen></iframe>
  4. </div>
  1. .videoWrapper {
  2.         position: relative;
  3.         padding-bottom: 56.25%; /* 16:9 */
  4.         padding-top: 25px;
  5.         height: 0;
  6. }
  7. .videoWrapper iframe {
  8.         position: absolute;
  9.         top: 0;
  10.         left: 0;
  11.         width: 100%;
  12.         height: 100%;
  13. }
9 box
Whatever YouTube iframe embed code you paste within the .videoWrapper, you'll see it presented in a fluid 16:9 box

But, but... aspect ratios, legacy content, non-tech users, etc

The above technique is awesome, but it has several possible limitations:

  1. It requires wrapper element, so just straight up copy-and-pasting code from YouTube is out. Users will need to be a bit more saavy.
  2. If you have legacy content and are redesigning to be fluid, all old videos need HTML adjustments.
  3. All videos need to be the same aspect ratio. Otherwise they'll be forced into a different aspect ratio and you'll get the "bars". Or, you'll need a toolbox of class names you can apply to adjust it which is an additional complication.

If either of these limitations apply to you, you might consider a JavaScript solution. Imagine this: when the page loads all videos are looked at and their aspect ratio is saved. Once right away, and whenever the window is resized, all the videos are resized to fill the available width and maintain their aspect ratio. Using the jQuery JavaScript Library, that looks like this:

  1. // Find all YouTube videos
  2. var $allVideos = $("iframe[src^='http://www.youtube.com']"),
  3.  
  4.     // The element that is fluid width
  5.     $fluidEl = $("body");
  6.  
  7. // Figure out and save aspect ratio for each video
  8. $allVideos.each(function() {
  9.  
  10.   $(this)
  11.     .data('aspectRatio', this.height / this.width)
  12.  
  13.     // and remove the hard coded width/height
  14.     .removeAttr('height')
  15.     .removeAttr('width');
  16.  
  17. });
  18.  
  19. // When the window is resized
  20. $(window).resize(function() {
  21.  
  22.   var newWidth = $fluidEl.width();
  23.  
  24.   // Resize all videos according to their own aspect ratio
  25.   $allVideos.each(function() {
  26.  
  27.     var $el = $(this);
  28.     $el
  29.       .width(newWidth)
  30.       .height(newWidth * $el.data('aspectRatio'));
  31.  
  32.   });
  33.  
  34. // Kick off one resize to fix all videos on page load
  35. }).resize();

Adding Vimeo

Vimeo uses iframes too, so what works for YouTube will work for Vimeo. The HTML/CSS technique doesn't need any alteration at all, and the jQuery solution could be fixed changing a single line:

  1. var $allVideos = $("iframe[src^='http://player.vimeo.com'], iframe[src^='http://www.youtube.com']"),

<object> / <embed> Video (Viddler, Blip.tv, etc)

Some home-brew video embedding, as well as video sharing services like Viddler and Blip.tv, use old-school nested object and embed tags. YouTube also did it this was until fairly recently. It's non-standard, but this technique was very widely used because Flash was everywhere and it just worked.

Object/embed suffers from the same problem that iframes do: the width and height are required lest R&E results.

For a pure HTML/CSS solution, we can again look to Thierry's technique if we're OK with adding additional HTML and imposing aspect ratio.

  1. .videoWrapper {
  2.         position: relative;
  3.         padding-bottom: 56.25%; /* 16:9 */
  4.         padding-top: 25px;
  5.         height: 0;
  6. }
  7. .videoWrapper object,
  8. .videoWrapper embed,  {
  9.         position: absolute;
  10.         top: 0;
  11.         left: 0;
  12.         width: 100%;
  13.         height: 100%;
  14. }

If we don't want to bother with the extra HTML wrapper and CSS complications, we could again rely on JavaScript. Our script can remain largely the same, except we're going to look for object and embed elements rather than video or iframe elements.

  1. var $allVideos = $("object, embed"),

And important to note: jQuery doesn't allow the use of it's .data() function on those types of elements, so we'll need to use HTML5 data attributes to store and retrieve our aspect ratio data. You could misuse a rel attribute or something if you aren't using HTML5.

  1. $(this)
  2.    .attr('data-aspectRatio', this.height / this.width)
  3.    
  4.    ...
  5.    
  6. $el
  7.    .width(newWidth)
  8.    .height(newWidth * $el.attr('data-aspectRatio'));

Putting it all together

So let's say we are in the position where we have lots of legacy content, which includes videos of all makes and models, and we're redesigning our site to be fluid. The most efficient route is going to be combine everything we've learned in this article and put it together. See the demo page for the complete workings.

View Demo

This article and the demo are all available on GitHub.

9 comments

Comment: 1

Very elegant and nice solution.
Thank you very much!

Comment: 2

This is a nice implementation of fluid width.... but

I feel there is something missing.
Some videos do not look good at 100% width, especially when that width
is wider than the video should be in the first place.

So what I would like to see now, is a modified version of this jQuery script which honor
the initial width height values in releation to the original width of the fluid width element.

It might not even be possible... but I'm not the one to decide that

Here is an example.

Let say your fluid width element (the one that controls the actual width of the video) is 700px wide, and you have entered e.g. 500px width on the video.

With the jQuery solution posted here, the video will get (in this example) a width of 700px, if the fluid width element is displayed at it's full width.

What I would like to see is a solution like this:

Never resize above the initial maximum size for the video, which should be the value I entered when I published the video. In this case it would be: 500px.
If the fluid width element get a width narrower than 500px, then set the width of the video to full width of the fluid element.
The height should be adjusted accordingly, as to honor original aspect ratio.

If the above could be implemented, then the resizing would only kick in
if the width of the fluid element is narrower than initial setting for the video.
... and your posted video may not look like mashed potatoes when the video width gets to wide.

For this to work, jQuery probably needs to know the original width and height of the video, as it was when one published it, also after it has been resized. This might be possible to store in a javascript array, or eventually by creating custom attributes in the iframe, which is then used as a storage container for those values.

What do you think...
Any possibility to implement the features above?

Comment: 3

I figured out how I could implement my previous suggestion:
With code below the video will not be grow larger than way you have set in width, height for the video.
But if the fluid element is narrower than the initial setting then it will schrink to fit.
I used some time to fiddle with some bugs... which turned out not to be bugs... just had to replace my older version of jquery. When I updtated to the one that is used on the demo pages here, then it worked as intended.

Here is my code:
NB! Just remember to replace $jquery with $
Also, in example below I have not included the script tags:

$jquery(function() {

var $allVideos = $jquery("iframe[src^='http://player.vimeo.com'], iframe[src^='http://www.youtube.com'], object, embed"),
$fluidEl = $jquery("div.item-page");

$allVideos.each(function() {

$jquery(this)
// jQuery .data does not work on object/embed elements
.attr('data-oheight', this.height)
.attr('data-owidth', this.width)
.removeAttr('height')
.removeAttr('width');


});

$jquery(window).resize(function() {

$allVideos.each(function() {

var $el = $jquery(this);
var oWidth = $el.attr('data-owidth');
var oHeight = $el.attr('data-oheight');
var aspectRatio = oHeight / oWidth;

var fluidElWidth = $fluidEl.width();

if (fluidElWidth >= oWidth)
var newWidth = oWidth;
else
var newWidth = fluidElWidth;

var newHeight = newWidth * aspectRatio;

$el
.width(newWidth)
.height(newHeight);

});

}).resize();

});

Comment: 4

Just noticed someting weird behaviour.

Two videos, one from YouTube and one from blip.tv, both using iFrame behaves different in relation to the fluid script.

If you look at this page, and try to resize (make it smaller).... then larger... then it works (iframe with video from YouTube).

http://www.monsanto.no/index.php/en/component/content/article/101-englis...

Now if you try the same with this page, then it does not work (iframe with video from blip.tv):

http://www.monsanto.no/index.php/en/component/content/article/101-englis...

Now, why is one iFrame responding to the fluid script, and not the other one?
I see same behaviour in Chrome, Firefox, IE, and Opera.

And yup, I have only tested this with my modified fluid script, which is posted before this post. But that is basically identically with the original script, except that it has an upper limit to the size of the video, so I believe that is not related to this possible 'bug' I see here.

It would be nice to get some feedback on why these differences occur.

Comment: 5

I finally found what caused the bug in my modified script above:

The iframe video that was not working was from

When I replaced this line (in my modified script, above):
var $allVideos = $jquery("iframe[src^='http://player.vimeo.com'], iframe[src^='http://www.youtube.com'], object, embed"),

with this line:
var $allVideos = $jquery("iframe[src^='http://player.vimeo.com'], iframe[src^='http://www.youtube.com'], iframe[src^='http://blip.tv'], object, embed"),

NOTE: I added this part:

iframe[src^='http://blip.tv']

because the iframe video that would not grow or shrink came from: http://blip.tv

So if you experience similar missing functionality, then first thing to check is
if you have added support for the domain that delivers the iframe video.

And thanks to Chris Coyier, who came up with the original script.

Comment: 6

I'm having a problem and I just can't seem to put my finger on it. I am using the code in a wordpress site and when I resize the window on a single post with video the width and height adjust no problem, but when resizing the main blog page which shows 5 video posts, the width adjusts as expected but not the height...well kind of, the video itself adjusts but there are black vertical bars above and below the video that remain the same height as the original spec.

Here is an example of single post:
http://sandboxwp.motomontage.com/high-point-video-redux-the-450/

Here is an example of 5 posts:
http://sandboxwp.motomontage.com/blog/

Comment: 7

nevermind, I figured it out. I was calling the script within my posts so my blog page which was set to display 5 posts was calling the script 5 times. I removed the script from the individual posts and put it in an external file called by my header and problem solved.

Thanks again for this great script!

Comment: 8

you made my day. guys like you make the internet definitely a better place. thanks a lot for this article.

Comment: 9

I love this solution. I am running a simple wordpress blog that needs to be responsive/fluid. For some reason no matter what I do I cannot get this script to function. Can some one spend a few minutes with me to possibly get this worked out...any help is greatly appreciated. Sample site is http://theplanthelper.com

Best Regards...
June issue on sale now!

The Week in Web Design

Sign up to our 'Week in Web Design' newsletter!

Hosting Directory
.net digital edition
Treat yourself to our geeky merchandise!
site stat collection