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.

相關文章
相關標籤/搜索