html,body标签上的overflow-x:hidden,height:100%属性对position:absolute的影响

957 阅读6分钟

最近做项目的时候遇到一个问题,项目中的picker全部会随着滚动条滚动,而不是固定在选择框的下方。排查发现,picker显示的时候是被插入body中绝对定位,top值是会随着滚动条的滚动而动态改变的(详见element源码,引入的第三方插件popper.js),但是我们项目中top值并没有发生改变,这是被什么影响的至今没找到。原因没找到,bug还得继续改呀,既然是定位元素,而且位于body内,body,html都没有position:relative,所以picker是相对于初始包含块的定位的。想到这里,查看了一下项目中html,body的样式,如图:

问题就出在这里,具体详细的解释我也解释不好,引用一个网上的回答:

元素仍然是position: absolute,但是由于和框模型之间的一些相当复杂的交互position,它看起来是固定的overflow。令人难以置信的是,这些行为都不是未指定的,也不是任何浏览器中的错误- 实际上,这完全是设计使然,即使有些违反直觉。

基本上可以归结为以下几点:

除非将其绝对祖先定位,否则将绝对定位的元素锚定到初始包含块。(这就是为什么添加position: relative到body可以如另一个答案中所建议的那样。)

您同时拥有width: 100%; height: 100%;html和body;这样可以防止初始包含块扩展到视口(可见区域)之外,因此初始包含块永远不会滚动。

由于初始包含块不会滚动,因此绝对定位的元素也不会滚动。即使页面的其余部分确实滚动,这也会导致它看起来固定。

奇怪的是,这也适用于IE6。

更长的解释:

除非将其绝对祖先定位,否则将绝对定位的元素锚定到初始包含块。 关于属性的规范overflow,其中附带包含您正在观察的同一问题的另一个示例,其中与的元素在overflow: scroll与绝对定位的后代元素进行交互时,声明如下:

此属性指定块容器元素的内容溢出元素框时是否被裁剪。它会影响所有元素内容的剪辑,但其包含块是该元素的视口或祖先的任何后代元素(及其各自的内容和后代)除外。

您绝对定位的元素是其后代,其后继包含块是初始包含块(也是html元素的包含块),因为html和body均未定位。这是根据规范的另一部分。这可以防止html和body上的溢出剪切对绝对定位的元素产生任何影响,因为它已锚定到初始包含块。

您同时拥有width: 100%; height: 100%;html和body;这样可以防止初始包含块扩展到视口(可见区域)之外,因此初始包含块永远不会滚动。 然后,规格说明了以下内容,在同一部分的下方:

UA必须将在根元素上设置的“溢出”属性应用于视口。当根元素是HTML“ HTML”元素或XHTML“ html”元素,并且该元素具有HTML“ BODY”元素或XHTML“ body”元素作为子元素时,用户代理必须改为应用’overflow’属性如果根元素上的值是“ visible”,则从第一个此类子元素到视口。用于视口的“可见”值必须解释为“自动”。从中传播值的元素必须具有“可见”的“溢出”使用的值。

简单地说:

  • 如果不是html overflow: visible,请将其应用于视口,然后将html转到overflow: visibleoverflow赋予body 的值不受影响。

  • 如果html是overflow: visible,但body不是,则将其应用于视口,然后将body转到overflow: visible。 (设定overflow-x或overflow-y比其他任何visible一个元件使所述速记overflow不再等于visible该元素)。

通常,这意味着视口应该与html和body一起自然滚动,因为一次只能存在一个滚动条。

但是… 您还同时给html和body设置了宽度和高度为100%!这意味着其容器的100%。body的容器是html,而html的容器是初始包含块。但是,由于实际上不能使用CSS控制视口的大小(完全由浏览器处理),因此剩下的两个元素被限制为视口高度的100%(也称为折叠)。视口本身不必扩展到折叠之外,因为它的内容都不需要比可见空间更多的空间(请注意,绝对不会考虑绝对定位的元素)。因此,视口不会生成滚动条(html也不会生成)。您看到的滚动条属于主体。

如果未设置width或height属性,则它们将默认设置为auto,从而导致html和body都随其内容扩展,并且大小始终与初始包含块所覆盖的整个区域相同,包括折叠以下的区域。这样可以防止主体生成滚动条,因为它始终会拉伸以适合其内容,因此您只能看到视口滚动条,并且绝对定位的元素将与页面的其余部分一起滚动。

由于初始包含块不会滚动,因此绝对定位的元素也不会滚动。 即使页面的其余部分确实滚动,这也会导致它看起来固定。 滚动时会发生什么,就是您实际上在滚动body元素。由于绝对定位的元素锚定在永远不会滚动的初始包含块上,因此它似乎是固定的而不是滚动。

顺便说一句,这也是为什么当元素根本不滚动时该元素看起来与滚动条重叠的原因。滚动条属于主体,它位于绝对定位的元素下方。如果从html和body中删除overflow-x声明或widthand height声明,则看到的滚动条将改为属于视口。但是,如果放置身体,滚动条仍属于身体,但是元素也成为身体的子元素,因此它不会与滚动条重叠。

下面是一个demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>demo</title>
</head>
<style>
    html, body{
        margin: 0;
        padding: 0;
        width: 100%;
        background: #f7f7f7;
        height: 100%;
        overflow-x: hidden;
    }
</style>
<body>
    <div style="height: 2000px;">
        <div>12222222222222222222222222222222222222s</div>
    </div>
    <div style="position: absolute;top: 500px;left: 200px;width:100%;background-color: red;">123123</div>
</body>
</html>