Why is the window.width smaller than the viewport width set in media queries

Solution 1:

This is what worked for me: CSS media queries and JavaScript window width do not match.

Instead of using $(window).width(); which includes scrollbars get the inner width like this:

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' ] };
}

var vpWidth = viewport().width; // This should match your media query

Solution 2:

The answer is scrollbars, and the solution is tricky.

Media queries are interesting. They don't behave precisely the same across browser, meaning using them can sometimes not be so easy. In -webkit/-blink on OS X and IE10 on Win8 for example, scrollbars are overlaid onto the page. In -moz, however, scrollbars are not overlaid onto the page. The best way to get the "viewable area" of the page is the following line of vanilla JavaScript (assuming you have a valid HTML document):

document.body.parentNode.clientWidth

What this will do is find the body element, find it's parent (which in a valid document will be the html element), and then find it's width after rendering. This will give you the width you're interested in seeing. It's also a useless width to have.

Why, you may ask, is having the actual client width useless? It's not because it varies from browser to browser, because it does. It's because that's not what the width media queries are actually testing! What width media queries test is window.innerWidth, which is what you're in essence using now.

So what is the solution to this problem? Well I'd say the solution is to use content based media queries instead of device based media queries and be OK with some wiggle room in your queries (especially if that wiggle room is approx. 15px). If you haven't already, read the articles Vexing Viewports and A Pixel Identity Crisis to get an idea as to why a potential 15px shimmer in your MQ definitions isn't the worst thing in the world (there are probably bigger fish to fry).

So, in conclusion continue using $breakpoint-to-ems: true and choose your media queries based on when content breaks to window.innerWidth as it's the only sane way of handling cross-browser issues. From a cursory glance of various issues, it appears as if most browsers and OSes are moving to an overlay scrollbar (as of Firefox 24 every OS X browser will have one, Ubuntu's Unity UI introduced them), so in an effort to be future friendly, I'd suggest not worrying about the scroll bar offset and be OK with sites looking slightly different across browser. Remember, as Ethan Marcotte so eloquently put:

The Web is an Inherently Unstable Medium