50

Note: Ok while I was typing this question I came across this question which suggests to use @media query but was asked back in 2011...

As you know CSS3 introduces new Viewport-percentage length units, vh and vw, which I feel are really useful for a solid responsive layout, so my question is, is there any JavaScript/jQuery alternative for this? More over apart from using it for font sizes, is it safe to use for sizing elements? Like example

div {
   height: 6vh;
   width: 20vh;  /* Note am using vh for both, do I need to use vw for width here? */
}
Community
  • 1
  • 1
Mr. Alien
  • 153,751
  • 34
  • 298
  • 278
  • The closest I could find was a polyfill for the `rem` units -- https://github.com/chuckcarpenter/REM-unit-polyfill. I couldn't find one for `vh`/`vw`, but given that the `rem` one exists, I would imagine it is possible. – SDC Dec 19 '12 at 10:57
  • And this one might be helpful to look up too -- https://github.com/heygrady/Units -- I see it mentions `vh`/`vw` units in the docs. I don't think it's the direct polyfill you want though. – SDC Dec 19 '12 at 11:02
  • 3
    Here's a 10.9k question edited by a 19.2k, answered by a 15.4k.. aaaaah – Ben Dec 19 '12 at 11:17
  • 3
    @Ben Battle of the giants (: – chridam Dec 19 '12 at 11:21
  • You can use either unit interchangeably, but it is a percentage of the viewport height and the viewport width respectively, so you have to be careful. – BoltClock Dec 19 '12 at 11:24
  • 1
    It took me a while, I got hooked to this question, great challenge. – elclanrs Dec 19 '12 at 11:29
  • 2
    @Ben: Retagged by a 140k. Sorry, I was extremely bothered by the tags :) – BoltClock Dec 19 '12 at 12:12
  • I made a few updates to my answer, it works seamlessly now as it extends the native `css` method and handles the resize inside of the plugin. – elclanrs Dec 20 '12 at 05:54
  • 1
    If anyone is still into this, I made a grid plugin that works very well already, if you guys want to try it out it's here https://github.com/elclanrs/jquery.columns – elclanrs Dec 22 '12 at 22:30

8 Answers8

16

Update 5: .css(property) fix Plugins like fancyBox use .css('margin-right') to fetch the right margin of an element and .css('margin-right', '12px') to set the right margin of an element. This was broken, because there was no check if props is a string and if there are multiple arguments given. Fixed it by checking if props is a string. If so and there is multiple arguments, arguments is rewritten into an object, otherwise parseProps( $.extend( {}, props ) ) is not used.

Update 4: Plugin for responsive layouts https://github.com/elclanrs/jquery.columns (in the works)

I gave this a (long) try. First here's the CSS example: http://jsbin.com/orajac/1/edit#css. (resize the output panel). Notice that the font-size doesn't work with viewport units, at least on latest Chrome.

And here's my attempt at doing this with jQuery. The jQuery demo which works with the font as well is at http://jsbin.com/izosuy/1/edit#javascript. Haven't tested it extensively but it seems to work with most properties since it's just converting the values to pixel and then by calling the plugin on window.resize it keeps updating.

Update: Updated code to work with many browsers. Test locally if you're using anything other than Chrome because jsBin acts a bit weird with window.resize.

Update 2: Extend native css method.

Update 3: Handle window.resize event inside of the plugin so the integration is now seamless.

The gist (to test locally): https://gist.github.com/4341016

/*
 * CSS viewport units with jQuery
 * http://www.w3.org/TR/css3-values/#viewport-relative-lengths
 */
;(function( $, window ){

  var $win = $(window)
    , _css = $.fn.css;

  function viewportToPixel( val ) {
    var percent = val.match(/[\d.]+/)[0] / 100
      , unit = val.match(/[vwh]+/)[0];
    return (unit == 'vh' ? $win.height() : $win.width()) * percent +'px';
  }

  function parseProps( props ) {
    var p, prop;
    for ( p in props ) {
      prop = props[ p ];
      if ( /[vwh]$/.test( prop ) ) {
        props[ p ] = viewportToPixel( prop );
      }
    }
    return props;
  }

  $.fn.css = function( props ) {
    var self = this
      , originalArguments = arguments
      , update = function() {
          if ( typeof props === 'string' || props instanceof String ) {
            if (originalArguments.length > 1) {
              var argumentsObject = {};
              argumentsObject[originalArguments[0]] = originalArguments[1];
              return _css.call(self, parseProps($.extend({}, argumentsObject)));
            } else {
              return _css.call( self, props );
            }
          } else {
            return _css.call( self, parseProps( $.extend( {}, props ) ) );
          }
        };
    $win.resize( update ).resize();
    return update();
  };

}( jQuery, window ));

// Usage:
$('div').css({
  height: '50vh',
  width: '50vw',
  marginTop: '25vh',
  marginLeft: '25vw',
  fontSize: '10vw'
});
gidomanders
  • 465
  • 5
  • 16
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • That works well on chrome 22, but fails on 17.0.1 :) but nice try +1...wanted a cross-browser, ya I can leave IE7,8 for sure..but atleast ff, chrome, opera(wont work, fine), ie9/10 – Mr. Alien Dec 19 '12 at 19:34
  • @Mr.Alien: It was line `prop = viewportToPixel( prop )` the problem. I changed to `prop[p] = viewportToPixel( prop )` and it works fine now in all browsers. Test it out! http://jsbin.com/ugefub/1/edit#javascript – elclanrs Dec 19 '12 at 22:06
  • @Mr.Alien: Also jsBin does wierd things with `window.resize` in all browsers except Chrome. Try it locally, it works fine in FF, Opera and Chrome AFAIK. Here's a gist https://gist.github.com/4341016 – elclanrs Dec 19 '12 at 22:10
  • Now that's awesome...thank you, this will really help many many people, btw your your website is elegant... – Mr. Alien Dec 20 '12 at 08:09
  • 1
    @Mr.Alien: I've been doing some grid tests here http://jsbin.com/evupug/1 (locally works better). It works quite well actually, combined with media queries this seems very powerful although probably only useful for elements at body level and dynamic font-resizing. For children elements it would be better to use actual percentages I guess... – elclanrs Dec 20 '12 at 08:56
  • This plugin with data attributes could be the start of a responsive grid system, so many ideas come to mind... – elclanrs Dec 20 '12 at 08:58
  • ya I know, that was the reason behind asking this question, this will be taking responsive design to another level, perhaps you should start a project on `git`...let other programmer's drop in there ideas ;) – Mr. Alien Dec 20 '12 at 17:48
  • May I add my pick as well? It's not a polyfill, it's a tiny lib: https://github.com/joaocunha/v-unit – João Cunha Feb 15 '14 at 16:42
  • That is a awsome library. But I have an file of css which all sizing are using viewport, can it convert the viewport of css file to px? – Jacky Shek Feb 17 '16 at 06:23
14

I am facing this issue with the Android 4.3 stock browser (doesn't support vw,vh, etc). The way I solved this is using 'rem' as a font-size unit and dynamically changing the < html >'s font-size with javascript

function viewport() {
    var e = window, a = 'inner';
    if (!('innerWidth' in window )) {
        a = 'client';
        e = document.documentElement || document.body;
    }
    return { width : e[ a+'Width' ] , height : e[ a+'Height' ] };
}
jQuery(window).resize(function(){
    var vw = (viewport().width/100);
    jQuery('html').css({
        'font-size' : vw + 'px'
    });
});

and in your css you can use 'rem' instead of px,ems,etc

.element {
    font-size: 2.5rem; /* this is equivalent to 2.5vw */
}

Here's a demo of the code : http://jsfiddle.net/4ut3e/

  • 4
    About `rem`s, there are some versions of Android (don't remember which) that have a minimum font-size (in my case it was 8px). So if your root font-size ends up being < 8px, it will be rounded to 8px automatically and potentially break your layout. – rachel Dec 22 '14 at 20:26
7

I wrote small helper to deal with this problem. It's supported on all main browsers and uses jQuery.
Here it is:

SupportVhVw.js

function SupportVhVw() {

    this.setVh = function(name, vh) {

        jQuery(window).resize( function(event) {
            scaleVh(name, vh);
        });

        scaleVh(name, vh);
    }

    this.setVw = function(name, vw) {

        jQuery(window).resize( function(event) {
            scaleVw(name, vw);
        });

        scaleVw(name, vw);
    }

    var scaleVw = function(name, vw) {

        var scrWidth = jQuery(document).width();
        var px = (scrWidth * vw) / 100;
        var fontSize = jQuery(name).css('font-size', px + "px");
    }


    var scaleVh = function(name, vh) {

        var scrHeight = jQuery(document).height();

        var px = (scrHeight * vh) / 100;
        var fontSize = jQuery(name).css('font-size', px + "px");
    }
};

Simple example how to use it in HTML:

<head>

    <title>Example</title>

    <!-- Import all libraries -->
    <script type="text/javascript" src="js/libs/jquery-1.10.2.min.js"></script>
    <script type="text/javascript" src="js/libs/SupportVhVw.js"></script>

</head>
<body>

                    <div id="textOne">Example text one (vh5)</div>
                    <div id="textTwo">Example text two (vw3)</div>
                    <div id="textThree" class="textMain">Example text three (vh4)</div>
                    <div id="textFour" class="textMain">Example text four (vh4)</div>

            <script type="text/javascript">

                    // Init object
                    var supportVhVw = new SupportVhVw();

                    // Scale all texts
                    supportVhVw.setVh("#textOne", 5);
                    supportVhVw.setVw("#textTwo", 3);
                    supportVhVw.setVh(".textMain", 4);

            </script>

</body>

It's available on GitHub:
https://github.com/kgadzinowski/Support-Css-Vh-Vw

Example on JSFiddle: http://jsfiddle.net/5MMWJ/2/

Konrad G
  • 419
  • 7
  • 14
  • 3
    Why are you handling attachEvent and addEventListener AND importing jQuery? – rlemon Feb 14 '14 at 13:41
  • It's a small mistake and have nothing to do with correct code behavior. I don't see reason for down voting if code works well and is easy to use :/ I've fixed my answer. – Konrad G Feb 14 '14 at 15:41
  • 1
    I didn't downvote, I just was asking. it looks silly to handle those cases on their own and then use jQuery in other places. – rlemon Feb 14 '14 at 17:57
  • @Konrad thanks for the answer and I upvoted for you efforts :) appreciated... It would be better if you provide a demonstration over here as well, using jsfiddle :) – Mr. Alien Feb 15 '14 at 04:35
  • I've added JSFiddle demonstration as you asked :) – Konrad G Feb 15 '14 at 08:02
4

Vminpoly is the only polyfill I know of — it's under develpment but works as of this post. There are static polyfills as part of the Jquery Columns and -prefix-free projects as well.

RobW
  • 10,184
  • 4
  • 41
  • 40
2

I've just made a very ugly, but perfectly working workaround for this WITH PURE CSS (no JS needed). I came across a CSS stylesheet full of 'vw' declarations (also for heights and top/bottom properties) that needed to be rewritten for native Android Browser (which in versions 4.3 and below does NOT support 'vw' units).

Instead of rewriting everything to percentages, that are relative to parent's width (so the deeper in DOM, the most complicated calculations), which would give me a headache even before I would reach the first { height: Xvw } declaration, I generated myself the following stylesheet: http://splendige.pl/vw2rem.css

I guess you're all familiar with 'em' units (if not: http://www.w3schools.com/cssref/css_units.asp). After some testing I discovered that old Android Browsers (as well as all the others) perfectly support the 'rem' units, which work the same as 'em', but are relative to the ROOT element font-size declaration (in most cases, the html tag).

So the easiest way to make this work was to declare the font-size for html tag that is equal to 1% of the viewport's width, e.g. 19.2px for 1920px viewport and use a lot of media queries for the whole 1920-320px range. This way I made the 'rem' unit equal to 'vw' in all resolutions (step is 10px, but you can even try declaring html{font-size} for every 1px). Then I just batch-replaced 'vw' with 'rem' and admired the working layout on Android 4.3 native browser.

Whatsmore, you can then redeclare the font-size even for the whole body tag (in whatever units you want: px, em, pt, etc.) and it does NOT affect the 'rem' equality to 'vw' in the whole stylesheet.

Hope this helps. I know it looks kinda silly, but works like a charm. Remember: if something looks stupid, but works, it is not stupid :)

k0m0r
  • 451
  • 4
  • 3
1

"jquery.columns" is so far the best solutions.

https://github.com/elclanrs/jquery.columns

You only need 2 lines of codes to turn "vh" into a "all-browser scale".

Declare a class in html:

<img src="example.jpg" class="width50vh" />

Then in javascript:

$('.width50vh').css({width: '50vw'});
stonyau
  • 2,132
  • 1
  • 20
  • 18
  • How does this work if `vw` and `vh` are not supported? Does the Javascript css internally set something else than `50vw`? If not, you simply set a value in a unit not supported and I don't see this working. – Zelphir Kaltstahl May 16 '16 at 16:57
  • Yes it works in older IE as well. jquery.columns override jquery's css() function and turn 'vw' into pixel during its declaration. – stonyau May 19 '16 at 10:11
1

The simplest and most elegant solution I have found is to simply make a div that has height: 1vh; and width: 1vw; and when the page loads grab those values as pixels with getBoundingClientRect(). then add a listener to the document to listen for screen resizing and update the size when screen is resized.

I save the value in pixels of the vh and vw and then whenever I need to use vh i would just say 100 * vh which would give me 100vh.

Here is a code snippet on it's implementation, my answer is in vanilla js, no need for jquery.

function getViewportUnits(){
  const placeholder = document.getElementById("placeholder");
  const vh = placeholder.getBoundingClientRect().height;
  const vw = placeholder.getBoundingClientRect().width;
  console.log({vh: vh, vw: vw});
}
#placeholder{
  background: red;
  height: 1vh;
  width: 1vw;
}
<body onload="getViewportUnits()">
<div id="placeholder"></div>
Chance
  • 347
  • 2
  • 7
0

I've published a tiny lib that eases viewport-relative dimensions usage. Keep in mind it's not a polyfill, so it requires that you apply classes on the elements you want to resize. For instance, <div class="vh10 vw30">hello</div> will fill 10% of the height and 30% of the width.

Check it out: https://github.com/joaocunha/v-unit

João Cunha
  • 3,776
  • 3
  • 22
  • 33