img与特殊布局下对浏览器渲染的剖析

补白

在内联元素中,分为替换元素和非替换元素(不了解的同窗能够百度一下),非替换元素是不能够设置尺寸的,而替换元素做为特殊的内联元素,因为其自身拥有尺寸属性,因此其的尺寸是能够进行再次设置的。css

此文适合有必定CSS使用基础的同窗html

若是

若是我想实现一个以下图的布局,这是我在作本身博客时遇到的问题:web

其左侧三个字为大小1000*1000像素的图片,其拥有属性display:block;height:30%;,更所固然,这三个字撑开了它的父元素的宽度,且其宽度为图片目前的宽度。这样则能够实现左侧侧边栏的宽度是自适应的。浏览器

---这是布局想法。函数

问题

这样的布局能够完美实现,可是在实际使用过程当中,我发现了一个特殊的问题,当对窗口进行缩放时,出现了很特殊的问题,如今来贴上代码和它的两个缩放动画来演示一下:布局

<style>
div{
    height: 100%;
    float: left;
    background: yellow;
}
img{
    display: block;
    height: 50%;
}
</style>

<div>
    <img src="......">
</div>

窗口缩小时:
动画

看得出来img改变了高度,宽度也随着改变(设置为block才会发生宽度跟随改变),可是它的父div的宽度并无发生改变。this

再来看看当窗口放大时:
编码

与窗口缩小类似,img的尺寸虽然等比放大了,可是它的父div的宽度并无发生改变,以致于超出了父divfirefox

这是为何呢?且一块儿来跟我分析分析

分析

咱们来在Timeline中看一下在浏览器resize的时候,发生了什么事情~

而且图中的Recalculate Style的summary中显示影响的元素数量为1

注意在图中下面的Paint操做,下面将Paint的操做主要分为了三部分:

  1. 绘制body
  2. 绘制黄色div
  3. 绘制图片

注意绘制黄色div的尺寸为:(342 - 8),(339 - 8)

而绘制图片的尺寸为: (174 - 8),(174 - 8)

这其中8为body的初始margin,div和img的坐标原点为(8,8)

在绘制的时候,黄色div的宽度并不和图片同样,并且是保持着resize以前的尺寸,这就让咱们疑惑了,为何是这样呢,让咱们从文章的开头,从替换元素来讲起

Rference from webkit

A replaced element is an element whose rendering is unspecified by CSS. How the contents of the object render is left up to the element itself.

一个替换元素的渲染是和CSS无关的,而是由该元素的自身内容来决定渲染的。

因此这就形成了图片的特殊性,在咱们没有对其进行尺寸的样式设定时,图片的大小由自身的渲染规则来肯定。而咱们对其的高度进行了设置,这则把控制权交给了Layout,那咱们再来看看Layout是如何进行工做的:

Rference from webkit

void layout()
{  
   ASSERT(needsLayout());

   // Determine the width and horizontal margins of this object.
   ...  

   for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
       // Determine if the child needs to get a relayout despite the dirty bit not being set.
       ...

       // Place the child.
       ...

       // Lay out the child
       child->layoutIfNeeded();

      ...
   }
   // Now the intrinsic height of the object is known because the children are placed
   // Determine the final height
   ...

   setNeedsLayout(false);
}

这个函数是一个递归函数,咱们能够看到在对一个元素进行layout时,首先肯定了其宽度及水平margin,而再来肯定子元素的layout,当子元素位置肯定后,再根据其被撑开的高度来做为最后的高度。这样的工做机制保证了页面的竖向排列布局,这种布局也符合咱们人类的阅读习惯,这也是在布局中的行布局(元素都以行为基础展现,例如block元素默认宽度为100%,高度由样式或内部元素决定)。

这下则能够解释清楚为何外部的div没有得到内部img的宽度,即在对内部child进行layout时,已经将div的宽度设置好了,因此这样来讲对于外部div来讲,内部元素的宽度是没法获得的。其实咱们上面看到的在resize后触发的Recalculate Style影响元素个数为1,这个元素其实就是body,更改主body容器的大小后,后面的计算都交给layout去处理。

resize触发的layout是从根容器来进行递归layout的,因此这样咱们只能解决子基于父容器去排列的状况,如:p元素中的文字,其中文字的排列是基于父元素p的宽度的,假设resize后p元素宽度变小,咱们根据上面的layout函数来讲,则先基于p元素的容器元素来设置p的宽度,再根据p元素的宽度进行其中的文字排列,若是文字被挤成了多行,在遍历完成后,再根据子(文字)的高度来决定p元素的高度。

咱们再来一个演示,那就是基于宽度的百分比!

<style>
    div{
        background:yellow;
    }
    img{
        width:30%;
    }
</style>
<div>
    <img src="......">
</div>

下面是演示效果:

完美!能够看出来效果很完美,这则是基于正常layout函数的过程来进行的布局,也是最经常使用的。

继续分析

喜欢动脑筋的同窗能够读到这一块问题就来了,那么为何在height:30%的布局下,初次加载没有resize时div为何能够获得内部的宽度呢???在回答这个问题以前,咱们先来看firefox下的表现:

Oh!My God! div居然没有被撑开,并且宽度为图片的原始宽度,咱们先无论这个,来让咱们来看一看在Chrome初次加载时发生了什么:

咱们能够看到,图片流被一部分一部分的接收,而在接收必定大小的数据后,则会触发Layout,这个Layout则是由img来触发的,它会沿着容器链一路向上进行标记normalChildNeedsLayout或者posChildNeedsLayout位,并接着递归触发layout,而在图像的编码头信息里会包含它的尺寸大小,它会根据这个尺寸并结合img上的style生成计算出img须要占用的尺寸大小,则在后面的图片加载过程当中,不会再触发layout,只是去将图片流paint进已经设置好的区域中。

而firefox的黄色div宽度为图片的默认宽度250px,咱们能够看出来在Gecko引擎中的layout是没有对img来应用style的,而是直接使用了图像里的编码头信息尺寸,看来webkit仍是稍稍地聪明一点,可是他们两个都有一个共同的地方,即对float元素进行从新的宽度计算,这个过程是发生在对其子元素遍历layout结束后来进行的,可是为何在resize的过程当中没有触发对img的宽度从新计算,当黄色div的宽度在初次被初始化后,若是其拥有肯定数值而且基础样式为auto时,layout时再也不对其宽度进行再次的更改。

解药

这一状况在不一样的排版引擎下表现是不同的,由于其并非标准的阅读方式,因此也没有统一的标准去规范它,例如在webkit下,咱们能够使用js来再次触发img的layout(更改overflow或float等不少值),来使引擎进行再次layout,而这时能够再次对黄色div进行宽度设定,能够推测出该过程在对div进行设置needslayout时先洗冲掉了其的尺寸设定,这样则能够像初始的时候同样获取img的宽度,以下代码:

<!-- 基于上面的代码添加 -->
<button id="btn">add overflow</button>
<script>
    var img = document.getElementsByTagName('img')[0];
    
    document.getElementById("btn").onclick = function(){
        if(img.style.cssText){
            img.style.cssText = "";
        }else{
            img.style.cssText = "overflow:hidden;"
        }
    }
</script>

而这一方法在firefox的Gecko中是没法作到的,原本写此文是想在探索这个问题的最优解法,可是到最后才发现这个问题没有最优的解法,都是很麻烦才能去解决。如个人使用方法是,既然你外部div没法探知到内部基于高度百分比的图片变化,那我就监听resize直接用js来给你丫个宽度(能够参考zhiyishou.com)…………虽然很暴力的解决了,可是仍是怎么以为不开心!

这个问题其实主要缘由是img内联元素的特殊性,当它的高度改变时,其宽度也会发生改变,若是咱们在这个例子中把img换成一个100px*100px的div,则不会发生这么多排版引擎没有预料到的事情。

结语

浏览器对整套的排版全是以行排列,本文分析了浏览器的主要排版过程,但愿对你的对浏览器排版有帮助。

对了,若是你有更优的解决方案,记得告诉我!

Finish.

相关文章
相关标签/搜索