为什么window.width比媒体查询中设置的视口宽度要小

26

我感到困惑,仍然不确定如何用适当的话语来解释这个问题。到目前为止,我使用 Breakpoint 来设置我的媒体查询。一个使用过的 Breakpoint 变量看起来像这样:

$menustatictofixed: min-width 900px;

$breakpoint-to-ems变量被设置为true。我已经根据以下jQuery片段的像素值,使用所有Breakpoint变量布置了页面:

$('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);
});

一切看起来都很正常和干净。但在过去的一两天中,我在设置模式的最后一个断点并使事情可预测时遇到了问题。一些最初看起来干净和良好的事情,现在我逻辑上高度怀疑,实际上是不合适的且有些混乱。因为如果你看一下以下屏幕截图,在Chrome中窗口的宽度与利用window.width的jQuery代码片段的宽度不同。即使我将window.width替换为window.innerWidth以排除滚动条,也没有差异。唯一获得正确结果的方法是在方程中添加15像素:

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

整个方程式中唯一的问题是window.width不是正确的函数选择,更好的选择是使用例如document.documentElement.clientWidth或其他函数吗?那么上面提到的修复问题的15像素代表什么意思?最好的问候Ralf。


可能是因为右侧的滚动条吗? - Hello Universe
7个回答

51

这是对我有用的方法:CSS媒体查询和JavaScript窗口宽度不匹配

不要使用包括滚动条在内的$(window).width();,而应该像这样获取内部宽度:

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

我最近遇到了这个问题,这是对我有效的答案。 - oz.

25
答案是滚动条,解决方案很棘手。
媒体查询很有趣。它们在不同的浏览器上的行为不完全相同,这意味着有时使用它们可能并不容易。例如,在OS X上的-webkit/-blink和Win8上的IE10中,滚动条被叠加到页面上。然而,在-moz中,滚动条不会被叠加到页面上。获取页面的“可视区域”最好的方法是以下原始JavaScript代码(假设您有一个有效的HTML文档):
document.body.parentNode.clientWidth
这将找到body元素,找到它的父元素(在有效文档中将是html元素),然后找到渲染后的宽度。这将为您提供您感兴趣的宽度。不过也是一个没有用的宽度
为什么实际客户端宽度没有用呢?并不是因为它因浏览器而异,因为它确实如此。但这不是width媒体查询所要测试的内容!width媒体查询测试的是window.innerWidth,这就是您现在本质上正在使用的东西。
那么这个问题的解决方案是什么呢?我会说解决方案是使用基于内容的媒体查询而不是基于设备的媒体查询,并且对查询中的一些弹跳空间感到满意(特别是如果该弹跳空间约为15px)。如果您还没有阅读过文章Vexing ViewportsA Pixel Identity Crisis,请先阅读这些文章,以了解为什么查询定义中可能出现的约15px的偏差并非世界上最糟糕的事情(可能会有更大的问题需要解决)。
因此,总之,继续使用$breakpoint-to-ems: true并根据内容在window.innerWidth断开时选择您的媒体查询,因为这是处理跨浏览器问题的唯一明智方法。从对各种问题的粗略了解来看,似乎大多数浏览器和操作系统都正在转向叠加滚动条(截至Firefox 24,每个OS X浏览器都将拥有一个,Ubuntu的Unity UI引入了它们),因此为了友好未来,建议不要担心滚动条偏移,并且可以接受站点在浏览器间略有不同的外观。请记住,正如Ethan Marcotte所说的那样:

The Web is an Inherently Unstable Medium


非常感谢您的详细解释和提供的两篇进一步阅读材料。第一篇我已经知道了,但是关于像素身份危机的第二篇文章我还不知道。这两篇都是非常好的阅读材料。另外,我已经将我的示例中的$(window).width()替换为(window).innerWidth()。而您建议的最后处理过程正是我想要并已经处理事情的方式。第二部分在下一条评论中。 - rkoller
现在我有一个断点,在那里我的导航从切换菜单变成内联显示。在断点中,我设置了900px,但小部件显示断点已经发生在885px。现在我根据innerWidth值将断点更改为885px。然后导航在870px处中断。这个过程可以一直向下进行。这与您建议的情况相反,其中window.innerWidth和断点变量应该相等。我只能绕过它,如果我执行以下操作$(window).innerWidth()+15;。然后window.innerWidth和特定的断点是相同的。 :/ - rkoller
你使用的是哪个浏览器和操作系统?你是否检查了输出的CSS,以确保打印的媒体查询是你所期望的那些? - Snugug
我正在使用OSX 10.8.4,并且已在Safari,Chrome和Firefox中尝试过。所有浏览器表现相同。我已经关闭了$breakpoint-to-ems并查找了CSS中的断点。结果发现,在断点中设置的px值与CSS文件中媒体查询中的值相等。当我使用 $(window).innerWidth()+15; 时,该值对应小部件值。目前,+15是唯一可行的解决方法。 - rkoller
我再次调查了一下,发现问题所在。首先,获取innerWidth值的方法有误,之前使用的jQuery是不必要的。只需使用var viewportWidth = window.innerWidth;即可。现在我已经重新检查了Chrome、Firefox和Safari。我将滚动条可见性设置为仅在滚动时可见。对于给定的断点650px,三者都表现出相同的行为,从1列切换到2列。当我将滚动条设置为始终可见时,Firefox和Chrome仍然在650px处切换到2列,但Safari在665px处切换。 - rkoller
似乎Safari没有遵守W3C对innerWidth的定义:“innerWidth属性必须返回视口宽度,包括呈现滚动条(如果有),或者如果没有视口,则返回零。” - rkoller

5

这是因为滚动条的原因。

我找到的最正确的解决方案是使用媒体查询将实际窗口大小传递给Javascript。 您需要按照以下步骤操作:

  • 向页面添加一个隐藏元素,
  • 使用媒体查询来更改该元素的max-width属性,
  • 通过Javascript读取该元素的max-width属性。

例如,将以下元素添加到您的页面中:

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

然后编写以下CSS规则:
#currentMedia {
    display: none;
}

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

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

然后,从Javascript方面,您可以编写:
if (parseInt(jQuery("#currentMedia").css("max-width"), 10) <= 720) {
    // Code HERE..
}

无论滚动条大小如何,它都将是准确的,因为该值来自触发走马灯消失的同一媒体查询。

我已经在所有主要的现代浏览器上测试了这个解决方案,并且它给出了正确的结果。

您可以在quirksmode.org网站上找到哪些浏览器支持哪些属性的大总结。

您最好抓取页面中的一个元素(使用document.body(如果支持)或document.getElementById或其他方法),沿着其offsetParent链查找最顶层的元素,然后检查该元素的clientWidth和clientHeight。

innerWidth文档

.innerWidth()方法不适用于windowdocument对象;对于这些对象,请改用.width()


1
这是一个很好的答案,可以适用于您想要应用的任何大小规则。不确定为什么它的评分不高。 - Jacob Stamm

1

类似于@Snugugs的答案,但对我的用例效果更好:

CSS(注意我正在使用LESS,因此@tabletMin和@desktopMin会转换为我在其他地方设置的断点变量:

    #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... */
    }

然后在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');
    };

getView()函数将根据媒体查询所看到的宽度返回字符串“mobile”、“tablet”或“desktop”。
这可以扩展以适合更多的视口宽度,只需在CSS中添加其他值的规则即可。

0

跟随此链接

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

0

@Snugug的回答非常好地解释了为什么会发生这种情况,@TusharGupta也提供了一个很好的解决方案,引用了媒体查询检测到的宽度到JavaScript。下面的解决方案则是使用JavaScript检测到的宽度来触发布局更改。

如果您需要将媒体查询与JavaScript像素宽度检测同步,一种方法是基于您使用JavaScript添加的类来触发CSS布局更改。

因此,不要编写媒体查询,而是编写那些在类下嵌套的声明,例如:

html.myMobileBP { ... }

在你的JavaScript/jQuery中,添加这个类:

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

为了进一步推动这一点,您可能需要考虑缺乏JavaScript支持。定义媒体查询,而这些媒体查询进一步包含在html.no-js类中,然后使用JavaScript删除.no-js类并应用上述解决方案,通过JavaScript类添加断点。

-1

使用类在body上定义您是在移动设备、平板电脑还是桌面设备,而不是使用像素。

对我来说,document.body.clientWidthinnerWidth都无法正常工作。即使我的js代码必须执行此语句,我的功能也会在1023-1040px之间崩溃:

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

解决方案是在 body 上添加一个新的类名为“small-res”,并在我的代码中使用它,而不是像素验证,以适应小于1024的屏幕。
if($(body).hasClass('small-res')) 

我也向你推荐它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接