Fixing Cross-Browser Issues With CSS3 Viewport Units in IE9+ and Safari for iOS

September 24th, 2014 by zoltan · 1 Comment
Note: This blog post has been updated since the script has been simplified. Please see the Changelog notes on github for more information.

Resize the iframe on the left using the dragger next to the text. (or, better yet, see the demo on the left without the iframe and resize your browser). Open the demo on the left in your mobile browser and rotate the screen. You will see that font-size of the word “STACEY” resizes according to the size of the containing circle. The diameter of this circle is always 33% of the width or height, depending on which is bigger. All these calculations are done in CSS using viewport units, which are very handy when coding responsive websites. With them, you can describe width, height, font-size and other styles as percentages of the viewport width and height.

This article will cover off what viewport units are and how to use them. If you are already familiar with them, you will already know that there a few nasty gotchas when it comes to using viewport units, not the least of which are some commonly known bugs in iOS Safari and IE9 and 10. These are easily fixed, however, with an excellent JavaScript fix (or “buggyfill”) by Rodney Rehm (I have used these fixes in a production environment and they work like a charm and have a minimal effect on performance). His original release fixed issues with Safari for iOS, but he also graciously merged code that I added that plugs some holes in IE9 and allows developers to use viewport units inside of CSS3 calc() expressions in iOS Safari and IE9+. If you want to use viewport units today, hopefully this article and “buggyfill” will help you avoid the issues that I had when I first started using them.

stacey-portrait-small stacey-landscape-small

Here are screenshots of the full demo page on an iPhone.Hover over it to see the layout in portrait mode.

<

Download the latest version of viewport-units-buggyfill from github.

What are Viewport Units

Unlike the px unit, viewport units describe how big an object is relative to the width and height the viewport (i.e. the visible part of the web page). If you always want a block of text to be 90% the viewport height, you can use the following CSS to do this:

.myLargeText {
    /* 90% of the browser height = 90vh */
    font-size: 90vh;
}

1vh is equal to 1% of the viewport height, so have the font-size of 90vh means the font is never taller than the height of the viewport. If you want to have a block that is 80% of the viewport width, that can be done like this:

.myLargeBlock {
    /* 80% of the browser height = 80vw */
    width: 80vw;

    /* let's center this block */
    margin: 0 auto;
}

There are also vmin and vmax. 1vmin is equal to 1vw or 1vh (whatever is smallest) …

Resize me or open demo in a new window

… while 1vmax 1vw or 1vh (whatever is largest).

Cool … So What Are The Gotchas You Were Talking About?

There are, of course, some issues with viewport units as they are implemented natively in browsers today, most of which are outlined on the html5please website:

  • Safari for iOS browsers has a well-known bug that affects calculating viewport units when a iPhone/iPad is rotated.
  • IE9 does not calculate viewport units correctly when changing media-query breakpoints.
  • IE9 and 10 does not support vmax
  • IE9 does not support vmin (it does, hoever, support vm which is equivalent to vmin).
  • IE9 and Safari for iOS calculates viewport units differently inside of a frame. IE9 will assume the 100vw and 100vh to be the width and height of the parent document’s viewport, while Safari for iOS will choose 1px (!!!!) for both.
  • Safari in iOS cannot do viewport units in a lot of the more complicated CSS properties (e.g. text-shadow).

All of these issues, however, are handled using viewport-units-buggyfill.. It was created by Rodney Rehm to fix iOS Safari’s well known bugs with viewport units, and I added some code to it to handle additional issues in iOS as well as IE9+. Just download the code from github and insert this into the bottom of your web page — you’ll then be able use viewport units in your code in all modern browsers, including iOS Safari and IE9+:

<!-- 
      This is the base buggyfill. 
-->
<script src="path/to/viewport-units-buggyfill.js"></script>

<!--
      This is the hacks plugin needed for contentHacks to work correctly 
      (see below).
-->
<script src="path/to/viewport-units-buggyfill.hacks.js"></script>
<script>window.viewportUnitsBuggyfill.init({

    // milliseconds to delay between updates of viewport-units
    // caused by orientationchange, pageshow, resize events
    refreshDebounceWait: 250,

    // provide hacks plugin to make the contentHack property work correctly.
    hacks: window.viewportUnitsBuggyfillHacks

});</script>

The paramater object for .init() is optional. It can have the following properties:

  1. If the refreshDebounceWait property is set to a number, then IE9+ will use a debounce routine to throttle how many times the buggyfill is exeucuted on a browser resize.
  2. If hacks is set to window.viewportUnitsBuggyfillHacks, then we can use it to use vmin and vmax units in IE9+ using “content hacks”. (Note that in order for “content hacks” to work properly, the hacks property has to be set to window.viewportUnitsBuggyfillHacks). Content hacks are coded like this:
    .myLargeBlock {
        
      /* Non-IE browsers */
      width: 50vmin;
      font-size: 80vmax;
    
      /* IE9 and 10 */
      content: 'viewport-units-buggyfill; width: 50vmin; font-size: 80vmax;';
    
    }
    
  3. Note that Safari for older iOS (6.0 specifically) does not support the vmax keywords and it’s JavaScript engine ignores it. As of September 2014, David Smith’s iOS Version Stats Page states this only makes up 8.3% of all iOS devices in the wild, so I don’t think it’s not a huge deal. However, if this is of concern to you, you can use a “content hack” to fix this as well:

    .myLargeBlock {
        
      /* Modern browsers */
      font-size: 80vmax;
    
      /* Safari for iOS < 8 and IE <= 10 */
      content: 'viewport-units-buggyfill; font-size: 80vmax';
    
    }
    

    Note that I implemented this hack by using the content property since it is not used in CSS selectors that don’t use ::before or ::after pseudo-elements. This means, of course, that you cannot use virtual units in ::before and ::after rules for Safari for iOS <= 6. If you have to support this browser, it is better than nothing, but hopefully I can come up with a better way of doing this to prevent this restriction (if you have an idea, please make a comment below).

Download the latest version of viewport-units-buggyfill from github.

Using Viewport Units inside of calc() Expressions

Using viewport units inside of CSS3 calc() statements can be very handy! Let’s say, for example, you want to center a DOM element smack in the middle of the viewport. The CSS recipe is rather simple:

.circle {

  /* Let's make this a 200 x 200 circle */
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: red;
  position: absolute;

  /* 
   * We set the top property to be half of the 
   * viewport height minus half of the height 
   * of the element height to center it vertically.
   *   
   * Similarly set the left property to be 
   * half of the viewport width minus half 
   * the element width to center it horizontally
   */
  top: calc(50vh -  100px );
  left: calc(50vw -  100px );
  
}

Perfect and simple! However, Safari for iOS and IE9-10 also doesn’t allow viewport units inside the CSS3 calc() expressions (it ignores these expressions). In order to fix this, I implemented the following hack so that viewport-units-buggyfill would do it instead:

.circle {
  /* Let's make this a 200 x 200 circle */
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: red;
  position: absolute;
  /* 
   * We set the top property to be half of the 
   * viewport height minus half of the height 
   * of the element height to center it vertically.
   *   
   * Similarly set the left property to be 
   * half of the viewport width minus half 
   * the element width to center it horizontally
   */
  top: calc(50vh -  100px );
  left: calc(50vw -  100px );
  /*
   * Here is the code for WebKit browsers that will allow 
   * viewport-units-buggyfill.js to perform calc on viewport
   * units.
   */
  content: 'viewport-units-buggyfill; top: calc(50vh -  100px ); left: calc(50vw -  100px );';
}
Note: you must .init() the buggyfill with the hacks property set to window.viewportUnitsBuggyfillHacks (as described above) in order for this to work.

How about support for legacy IE properties?

While adding features to this library, I did some tests with some of the fancier CSS3 properties, such as text-shadow. They, of course, didn’t show up in IE9, since it doesn’t support CSS text-shadows. To fix this, I applied the DropShadow Visual Filter trick I wrote about in a previous blog post to my test page. It looked fine, except it didn’t have the responsive sizing for the shadows like in more modern browsers, so I decided to add Visual Filter support for viewport units. Take a look at the demo below in both IE9 and a modern browser and you’ll see that text-shadows grow with the height of the viewport:

As you can see from the CSS, viewport units now work in IE’s DropShadow Visual filter:

.copy {

  font-family: "AmericanCaptain", "Impact", sans-serif;
  text-align: center;
  color: white;
  /*
   * The first part of this text-shadow (1vh 1vh 0 #000)
   * is the huge drop shadow on the bottom right of the
   * text.  The other 1px shadows create the outline 
   * around the text.
   */
  text-shadow: 1vh 1vh 0 #000, 
               1px 0 0 #000, 
               -1px 0 0 #000, 
               0 -1px 0 #000, 
               0 1px 0 #000;

}

body.ie9down .copy {

  zoom: 1;
  background: #000001;
  /*
   * This CSS is the IE9- equivalent of the text-shadow code above.  
   * For more details, see my article "CSS3 Text-Shadow – Can It Be 
   * Done in IE Without JavaScript?" 
   *
   */
  filter: progid:DXImageTransform.Microsoft.Chroma(color=#000001)
          progid:DXImageTransform.Microsoft.DropShadow(OffX=1, OffY=1, Color=#000000) 
          progid:DXImageTransform.Microsoft.DropShadow(OffX=-1, OffY=-1, Color=#000000) 
          progid:DXImageTransform.Microsoft.DropShadow(OffX=1vh, OffY=1vh, Color=#000000);

}
Note: you must .init() the hacks property set to window.viewportUnitsBuggyfillHacks in order for this to work. Remember to include the viewport-units-buggyfill.hacks.js script to ensure this works correctly!

Download the latest version of viewport-units-buggyfill from github.

Acknowledgements

  • The iframe resizing routines on this web page were lifted from jQuery UI Resizable (note, however, that neither jQuery nor jQuery UI are needed for this buggyfill to work).
  • As mentioned before, the buggyfill was originally written by Rodney Rehm. I just added some extra fixes to it
  • Thanks to talented STACEY for the kind use of her photo and logo for the demo at the top of this page. In return, I promise to work on making her site responsive using this technique. :-)
  • In the Rob/Doug Ford example, I used the freeware version of American Captain by The Fontry’s Michael G. Adkins.

Tags: CSS · CSS3 · IE Visual Filters · math · viewport units · , , , , , , , , , , , , , , , , , , , , , ,

1 response so far ↓
Give Feedback

Don't be shy! Give feedback and join the discussion.

Please Note: If you are asking for help using the information on this page or if you are reporting a bug with the code featured here, please include a URL that shows the problem you are experiencing along with the browser/version number/operating system combination where the issue manifests itself. Without this information, I may not be able to respond.

An orange star denotes a required field.