前幾天某個羣友在羣裏問了一道面試題,就是關於一個自適應的正方形佈局的困惑,先貼上代碼。我其實很長一段時間沒有寫 CSS 了,對於裏面的一些細節也比較模糊了,所以決定重拾 CSS,來從新捋一捋這題目中的一些知識點。(本文大多采用的講解方式爲 w3 的 CSS 標準 + MDN,若是對標準比較熟悉的大神請跳過這篇文章)javascript
經過標準分析有什麼好處?最權威的解答,可以少走彎路,不會出錯。
代碼:css
<style> .square { width: 30%; overflow: hidden; background: yellow; } .square::after { content: ""; display: block; margin-top: 100%; } </style> <div class="square"></div>
效果(https://codepen.io/hua1995116...:html
上面的實現看似簡單,可是咱們去深究,大約會冒出如下三個問題。java
::after
僞元素有什麼特殊的魔法嗎?margin-top:100%
爲何可以自適應寬度?overflow:hidden
在這裏是什麼做用?所以咱們會按照上述疑問來逐一講解。node
本文全部 demo 都存放於 https://github.com/hua1995116...
::after
僞元素有什麼特殊的魔法嗎?說到 ::after
那就須要說到僞元素,咱們先來看看僞元素的定義吧。git
僞元素(Pseudo elements)表示文檔的抽象元素,超出了文檔語言明確建立的那些元素。 —— https://www.w3.org/TR/css-pse...github
再來看看 ::after
的特性:面試
When their computed content value is not none, these pseudo-elements generate boxes as if they were immediate children of their originating element, and can be styled exactly like any normal document-sourced element in the document tree.
根據描述來看,當僞元素 content
不爲 none
的時候,咱們能夠把他們當作正常的元素同樣來看待。算法
所以咱們的示例轉化爲更加通俗易懂的樣子。瀏覽器
<style> .square1 { width: 30%; background: red; overflow: hidden; } .square1-after { margin-top:100%; } </style> <div class="square1"> <div class="square1-after"></div> </div>
既然說到了僞元素,咱們也順便回顧一下僞類(Pseudo classes),他們的語法很是相似,倒是水和水銀的關係。
是添加到選擇器的關鍵字,指定要選擇的元素的特殊狀態。
:
單冒號開頭的爲僞類,表明形態爲 :hover
。
/* 全部用戶指針懸停的按鈕 */ button:hover { color: blue; }
表示文檔的抽象元素,超出了文檔語言明確建立的那些元素。(由於它們並不侷限於適應文檔樹,因此可使用它們來選擇和樣式化文檔中不必定映射到文檔樹結構的部分。)
::
雙冒號開頭的爲僞元素,表明形態爲 ::after
。
僞元素是應用於元素
/* 每個 <p> 元素的第一行。 */ p::first-line { color: blue; text-transform: uppercase; }
經過上述,咱們可能大體理解了可是對於一些看上去用途很類似的僞類和僞元素仍是有點迷糊。
關鍵區別: 是否建立了一個超出文檔樹之外的元素。
咱們乍一看 ::first-line
的效果不是和 :first-child
同樣嘛?來舉一個例子。
// pseudo-elements.html <style> p::first-line { color: blue; } </style> <div> <p> This is a somewhat long HTML paragraph that will be broken into several lines. </p> </div>
// pseudo-classes.html <style> p:first-child { color: blue; } </style> <div> <p> This is a somewhat long HTML paragraph that will be broken into several lines. </p> </div>
像我剛纔所說乍一看,兩種效果是同樣的。可是他們真的同樣麼?固然不是!咱們給他們加上寬度。
p { width: 200px; }
::first-line
的形態
在真實的渲染中咱們能夠理解爲
<p><p::first-line>This is a somewhat long</p::first-line> HTML paragraph that will be broken into several lines.</p>
可是咱們在真實的 DOM Tree 是看不到的。這一點規範中也說明了,由於它們並不僅僅適用於文檔樹,因此使用它們來選擇和樣式化文檔不必定映射到文檔樹。
Since they are not restricted to fitting into the document tree, they can be used to select and style portions of the document that do not necessarily map to the document’s tree structure.
:first-child
的形態
至此咱們搞清楚了咱們的第一個問題, ::after
沒有魔法,在本題能夠將它當成正常的元素,而且咱們搞清楚了僞元素和僞類的區別。
margin-top:100%
爲何可以自適應寬度?如今咱們已經將這個示例轉化成一個比較簡單的形態,沒有過多的知識。
<style> .square1 { width: 30%; background: red; overflow: hidden; } .square1-after { margin-top:100%; } </style> <div class="square1"> <div class="square1-after"></div> </div>
而後咱們來看看這個 margin-top: 100%
,看上去他相對於了父元素的 width
值來進行計算的。那麼咱們來看看 margin-top
究竟是怎麼計算的。
https://www.w3.org/TR/CSS22/b...
能夠看到 margin-top
主要有三種形態。第一種是固定值,第二種爲百分比,第三種爲 auto,這裏咱們主要來看下 百分比的計算。
經過上述的描述,能夠知道margin-top
margin-bottom
margin-left
margin-right
百分比的長度是由當前元素的包含塊的寬度來決定的。
那麼什麼是包含塊(Containing blocks)
呢?
The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element.
元素盒子的位置和大小有時是相對於某個矩形計算的,稱爲元素的包含塊。
上述的描述有點拗口,咱們大體只須要知道它就是一個矩形的塊。下面重要的來了,包含塊是怎麼肯定的?(https://developer.mozilla.org...)
肯定一個元素的包含塊的過程徹底依賴於這個元素的 position
屬性:
static
、 relative
或 sticky
,包含塊可能由它的最近的祖先塊元素(好比說inline-block, block 或 list-item元素)的內容區的邊緣組成,也可能會創建格式化上下文(好比說 a table container, flex container, grid container, 或者是 the block container 自身)。absolute
,包含塊就是由它的最近的 position 的值不是 static
(也就是值爲fixed
, absolute
, relative
或 sticky
)的祖先元素的內邊距區的邊緣組成。fixed
,在連續媒體的狀況下(continuous media)包含塊是 viewport ,在分頁媒體(paged media)下的狀況下包含塊是分頁區域(page area)。若是 position 屬性是absolute 或fixed,包含塊也多是由知足如下條件的最近父級元素的內邊距區的邊緣組成的:
transform
or perspective
value other than none
will-change
value of transform
or perspective
filter
value other than none
or a will-change
value of filter
(only works on Firefox).contain
value of paint
(例如: contain: paint;
)注意,如下全部例子的視口寬度都爲 594px
第一種狀況,就是咱們的例子的狀況,當前元素的 position 沒有填寫,默認爲 static 。所以知足第一種狀況,取它最近的祖先元素,也就是包含塊爲 container.
<style> .container { width: 30%; } .inner { margin-top:100%; } </style> <div class="container"> <div class="inner"></div> </div>
所以inner
的 margin-top = 父元素container = 窗口寬度(594px) * 30% = 178.188px。
當前元素爲 position:absolute
, 所以獲取的最近的一個 position
非 static
的元素
<style> .outer { width: 500px; position: relative; } .container { width: 30%; } .inner { position: absolute; margin-top: 100%; } </style> <div class="outer"> <div class="container"> <div class="inner"></div> </div> </div>
這個時候 inner
的 margin-top = outer 的寬度(500px)* 100% = 500px。
當前元素爲 position:fixed
,此時的包含塊爲視口。
<style> .outer { width: 500px; position: relative; } .container { width: 30%; } .inner { position: absolute; margin-top: 100%; } </style> <div class="outer"> <div class="container"> <div class="inner"></div> </div> </div>
所以這個時候 margin-top = viewport 的寬度(594px)* 100% = 594px。此時是無關父元素,以及無關外層position 的設置的。
在 case2 和 case 3 的基礎上,會有一些特例影響包含塊的尋找。主要就如下4種狀況
transform
or perspective
value other than none
will-change
value of transform
or perspective
filter
value other than none
or a will-change
value of filter
(only works on Firefox).contain
value of paint
(例如: contain: paint;
)我舉一個 transform
例子來說解。
<style> .outer { width: 500px; position: relative; } .container { width: 30%; transform: translate(0, 0); } .inner { position: fixed; margin-top: 100%; } </style> <div class="outer"> <div class="container"> <div class="inner"></div> </div> </div>
這個時候咱們的計算又發生了變化,此時包含塊又變成了 container
.
margin-top = 父元素container = 窗口寬度(594px) * 30% = 178.188px。
因此對於咱們一開始的問題,就是咱們的 Case1,採起的就是最近的父元素。因此 margin-top 就是 父元素 square1
的寬度,所以實現了一個自適應的正方形。
對於 position 的不一樣形態,對於佈局狀態的影響,通常在咱們入門 css 的時候就學了,可是可能沒有那麼仔細去了解每種狀況,也可能不知道他的名詞,叫作包含塊
,此次咱們對它進行了梳理,這一節就這樣結束,繼續看!
overflow:hidden
在這裏是什麼做用?假如咱們把 overflow:hidden
去了。
<style> .square1 { width: 30%; background: red; } .square1-after { margin-top:100%; } </style> <div class="square1"> <div class="square1-after"></div> </div>
咱們能夠看到以上執行完顯示出現的畫面爲一篇空白。此時咱們就要引出了咱們的最後一個概念就是,邊距坍塌(Collapsing margins) .
在CSS中,兩個或多個框(多是也可能不是兄弟)的相鄰邊距能夠合併造成一個邊距,稱爲邊距塌陷。
Horizontal margins
)不會崩潰min-height
且有auto
的height
,父子元素都含有 margin-bottom
,此時 margin-bottom
不會發生邊距坍塌。對於以上,可能對於狀況3和狀況4會比較疑惑,因此舉例子以下。
<style> .case { width: 200px; background-color: yellow; } .container { background-color: lightblue; margin-bottom: 70px; padding-top: 0.01px; } .preface { float: left; height: 58px; width: 100px; border: 1px solid red; } .one .intro { clear: left; margin-top: 60px; } .two .intro { clear: left; margin-top: 59px; margin-bottom: 20px; } </style> <div class="case one"> <div class="container"> <div class="preface"> lorem ipsum </div> <div class="intro"></div> </div> after </div> <hr> <div class="case two"> <div class="container"> <div class="preface"> lorem ipsum </div> <div class="intro"></div> </div> after </div>
在 Firefox 和 IE 下的效果(谷歌失效,緣由可能和谷歌瀏覽器實現有關,暫未深追。)
能夠看到若是在在沒有 clearance
的狀況下,父元素底部是會隨着子元素一塊兒坍塌的,可是若是中間有 clearance
的狀況下,父元素的底部則不會坍塌。
<style> .case2 { min-height: 200px; height: auto; background: red; margin-bottom: 20px; } .case2-inner { margin-bottom: 50px; } </style> <div class="case2"> <div class="case2-inner"></div> </div> <div>爲了看間距效果</div>
效果:
能夠看到這種狀況下,父子元素下邊距並不會發生邊距坍塌。
發生邊距坍塌須要知足2個前提
1.是 block 盒子模型,在同一個 BFC。
2.兩個元素之間沒有行內元素,沒有 clearance ,沒有 padding,沒有border。
而後如下幾種狀況會發生邊距坍塌。
補充: 若是'min-height'屬性爲零,而且框沒有頂部或底部border,也沒有頂部或底部padding,而且元素的'height'爲0或'auto',而且沒有行內元素,則元素自身的全部邊距坍塌,包括其全部流入子元素的邊距(若是有的話)都會坍塌。
這裏有幾個問題要解釋一下 1.什麼是流入子元素,2. 是什麼 clearance
流入元素須要用的反向來進行介紹,有流入元素,就有流出元素,如下狀況爲流出元素。
position: absolute
(including position: fixed
which acts in the same way)。經過設置position屬性爲absolute或者fixed的元素html
)根元素除了以上狀況的元素,叫作流入元素。
<style> body { border: 1px solid #000; } .case2 { width: 200px; height: 50px; background: red; } .case2-inner { margin-top: 50px; height: 0; } .float { float: left; } </style> <div class="case2"> <div class="float"></div> <div class="case2-inner">看出了啥</div> </div>
當某個元素有clear 非 none 值 而且盒子實際向下移動時,它叫作 clearance。
<style> .case1 { height: 50px; background: red; margin-top: 100px; } .case1-inner { margin-top: 50px; } </style> <div class="case1"> <div class="case1-inner">我直接從頂部開始了</div> </div>
<style> .case2 { height: 150px; background: red; } .case2-inner1 { margin-bottom: 50px; } .case2-inner2 { margin-top: 20px; } </style> <div class="case2"> <div class="case2-inner1">我和底下之間距離爲50px</div> <div class="case2-inner2">我和頂上之間距離爲50px</div> </div>
<style> .case3 { height: auto; background: red; margin-bottom: 10px; } .case3-inner { margin-bottom: 50px; } </style> <div class="case3"> <div class="case3-inner">底部和父元素被合併了</div> </div> <div>距離頂上50px</div>
<style> .case4 { height: 200px; background: red; } .case4-inner { margin-top: 20px; margin-bottom: 30px; } </style> <div class="case4"> <p>它把本身給合併了,距離底下30px</p> <div class="case4-inner"><span style="clear: both;"></span></div> <p>它把本身給合併了, 距離頂上30px</p> </div>
1.改變盒子模型(非 block 模型)
2.建立新的 BFC
查看剛纔不會發生高度坍塌的狀況
1.當兩個或更多邊距坍塌時,當邊距全爲正數的時候,結果頁邊距寬度是邊距塌陷寬度的最大值。
2.當邊距全爲負數的時候,取最小值。
3.在存在負邊距的狀況下,從正邊距的最大值中減去負邊距的絕對值的最大值。 (-13px 8px 100px疊在一塊兒,則邊距塌陷的值爲 100px - 13px = 87px)
若是轉爲算法就是如下代碼
// AllList 全部坍塌邊距 function computed(AllList) { const PositiveList = AllList.filter((item) => item >= 0); const NegativeList = AllList.filter((item) => item <= 0); const AllPositive = AllList.every((item) => item >= 0); const AllNegative = AllList.every((item) => item <= 0); if (AllNegative) { return Math.min(...AllList); } else if (AllPositive) { return Math.max(...AllList); } else { const maxPositive = Math.max(...PositiveList); const minNegative = Math.min(...NegativeList); return maxPositive + minNegative; } }
經過上面對邊距坍塌的理解,咱們能夠很快得出,咱們的自適應正方形中的例子,子元素的 margin-top
和 父元素的 margin-top
發生了坍塌,所以能夠新建一個 BFC 來消除這個問題。而 overflow:hidden
就是會造成一個 新的 BFC 。BFC詳見 https://developer.mozilla.org...
經過上面的解析,咱們終於把這一道小小的面試題,進行了全方位的剖析。每個問題都對應着一個知識塊。
::after
僞元素有什麼特殊的魔法嗎? -> 僞元素(Pseudo elements)margin-top:100%
爲何可以自適應寬度? -> 包含塊 (Containing blocks)overflow:hidden
在這裏是什麼做用? -> 邊距塌陷(Collapsing margins)想不到小小的面試題,竟然能夠牽扯出這麼多的知識,因此咱們在面對一些面試題的時候,例如實現一個自適應的正方形佈局,別單單看有幾種方式可以實現,解決方法永遠會隨着時間的推動,變得愈來愈多,那咱們能作的就是以不變應萬變(固然規範也是相對的,也可能會變,只是機率低)去理解剖析這些方法背後的用到的知識。
相信若是你把以上搞懂了,面試官對你深層次的靈魂追問,你也能對答如流了。注意本文的一些專有名詞,我都用英文屢次標註,這也許將來會對你有所幫助。
穩住,咱們能贏!嘻嘻嘻,最後,若是你對題目的理解一時間比較迷茫,歡迎加羣提問,本文也是基於羣友的問題,展開了一系列的講解。
https://stackoverflow.com/que...
https://www.w3.org/TR/css-dis...
https://stackoverflow.com/que...
歡迎關注公衆號 「秋風的筆記」,主要記錄平常中以爲有意思的工具以及分享開發實踐,保持深度和專一度。
也能夠掃碼加我微信好友,進交流羣。