Why is the window.width smaller than the viewport

2019-01-08 20:40发布

问题:

I am quite puzzled and still unsure how to explain this in proper words. So far i've used and set up my media queries with Breakpoint. An used Breakpoint-variable looks like e.g.:

$menustatictofixed: min-width 900px;

$breakpoint-to-ems is set to true. I've laid out the page with all its Breakpoint variables based on the pixel values of the following jQuery snippet:

$('body').append('<div style="position: fixed; bottom: 0; right: 0; display: inline-block; font-weight: bold; padding: 5px 7px; border-radius: 5px 0 0 0; background: green; color: white; z-index: 9999;">Viewport width <span id="width"></span> px</div>');
var viewportWidth = $(window).width()
$('#width').text(viewportWidth);
$(window).resize(function() {
  var viewportWidth = $(window).width();
    $('#width').text(viewportWidth);
});

Everything looked proper and clean. But over the last one or two days i had issues setting up the last breakpoints for a pattern and to get things behave predictable. Somehow the things that appeared to add up clean and fine in the first place, which i logically highly doubt now, are in fact improper and somehow a mess. Cuz if you take a look at the following screenshot somehow the width of the window (in Chrome) differs to the width from the jQuery snippet utilising window.width. There isn't also a difference if i would replace window.width by window.innerWidth to rule out scrollbars eventually. The only way to receive proper results is by adding 15 pixels to the equation:

var viewportWidth = $(window).width() + 15;

Is the only issue in the whole equation that window.width is the wrong choice of function and it would be better to go with e.g. document.documentElement.clientWidth or something else or ... ? And for what the 15 pixels are standing for which fixed the problem above in a bit hacky way? Best regards Ralf

回答1:

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



回答2:

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


回答3:

IT's because of the scrollbar's

The most correct solution I found is to use media queries to pass the actual window size to Javascript. You have to follow these steps:

  • Add a hidden element to your page,
  • Use media queries to alter the max-width property of that element,
  • Read back the max-width property of that element through Javascript.

For instance, add the following element to your page:

<div id="currentMedia"></div>

Then write the following CSS rules:

#currentMedia {
    display: none;
}

@media (max-width: 720px) {
    // Make arrows in the carousel disappear...

    #currentMedia {
        max-width: 720px;
    }
}

Then, from the Javascript side, you can write:

if (parseInt(jQuery("#currentMedia").css("max-width"), 10) <= 720) {
    // Code HERE..
}

And it will be accurate regardless of the scrollbar size, since the value comes from the same media query that triggers the carousel's disappearance.

I tested this solution on all major recent browsers, and it gives correct results.

You will find the big summary of what properties are supported on what browsers on this page on quirksmode.org.

Your best bet is probably to grab an element in the page (using document.body where supported, or document.getElementById or whatever), walk its offsetParent chain to find the topmost element, then examine that element's clientWidth and clientHeight.

innerWidth documentation

.innerWidth() method is not applicable to window and document objects; for these, use .width() instead.



回答4:

Similar to @Snugugs's answer but worked better for my use case:

CSS (Note I am using LESS so @tabletMin and @desktopMin translate to breakpoint variables I have set elsewhere:

    #cssWidth {
        display:none;
        content:'mobile';
    }

    /* Responsive styles (Tablet) */
    @media (min-width: @tabletMin) {
        #cssWidth {
            content:'tablet';
        }
        /* Other tablet CSS... */
    }
    /* Responsive styles (Desktop) */
    @media (min-width: @desktopMin) {
        #cssWidth {
            content:'desktop';
        }
        /* Other Desktop CSS... */
    }

And then in JS:

    getView = function() {
        // If #cssWidth element does not exist, create it and prepend it do body
        if ($('#cssWidth').length === 0) {
            $('body').prepend($(document.createElement('div')).attr('id', 'cssWidth'));
        }
        // Return the value of the elements 'content' property
        return $('#cssWidth').css('content');
    };

The getView() function will then return the string 'mobile', 'tablet' or 'desktop' dependant on the width the media queries see.

This could be extended to fit more viewport widths, just add more rules in the CSS with other values.



回答5:

@Snugug 's answer gives a really good explanation as to why this is happening, and @TusharGupta has a good solution as well that references the mediaquery detected width to javascript. Below solution goes the other way around by using javascript detected width to trigger layout changes.

In case you need to sync mediaqueries with your javascript pixel width detection, one way of approaching the problem is to trigger css layout changes based on classes you add with javascript.

So, instead of writing mediaqueries, write those declarations nested under a class, say:

html.myMobileBP { ... }

And in your javascript/jQuery, add this class like:

if ($(window).width() < myMobileBPPixelSize) {
    $("html").addClass("myMobileBP");
}

To drive this even further, you might want to consider lack of javascript support. Define mediaqueries that are furthermore wrapped in a html.no-js class, then with javascript remove the .no-js class and apply the above solution of adding breakpoints via javascript classes.



回答6:

follow this link

function getWindowWidth() {
    var windowWidth = 0;
    if (typeof(window.innerWidth) == 'number') {
        windowWidth = window.innerWidth;
    }
    else {
        if (document.documentElement && document.documentElement.clientWidth) {
            windowWidth = document.documentElement.clientWidth;
        }
        else {
            if (document.body && document.body.clientWidth) {
                windowWidth = document.body.clientWidth;
            }
        }
    }
    return windowWidth;
}


回答7:

Use classes on body to define if you are on mobile, tablet or desktop instead using pixels.

For me it did not work document.body.clientWidth or innerWidth. My functionality crashes between 1023-1040px even if my js code must do this statement:

if($(window).width()<1024)

The solution was to put on body a new class for less than 1024 and name it "small-res" and use it than in my code instead of pixels verification:

if($(body).hasClass('small-res')) 

I recommend it to you too.