在內聯元素中,分爲替換元素和非替換元素(不瞭解的同窗能夠百度一下),非替換元素是不能夠設置尺寸的,而替換元素做爲特殊的內聯元素,因爲其自身擁有尺寸屬性,因此其的尺寸是能夠進行再次設置的。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的操做主要分爲了三部分:
注意繪製黃色div的尺寸爲:(342 - 8),(339 - 8)
而繪製圖片的尺寸爲: (174 - 8),(174 - 8)
這其中8爲body的初始margin,div和img的座標原點爲(8,8)
在繪製的時候,黃色div的寬度並不和圖片同樣,並且是保持着resize以前的尺寸,這就讓咱們疑惑了,爲何是這樣呢,讓咱們從文章的開頭,從替換元素來講起
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是如何進行工做的:
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.