千萬(wàn)別小瞧九宮格 一道題就能讓候選人原形畢露!
前言
據(jù)不完全統(tǒng)計(jì)(其實(shí)就統(tǒng)計(jì)了自己身邊的朋友和同事),在刨除抖音或快手這一類短視頻 APP 后,每天在手機(jī)上花費(fèi)時(shí)間最長(zhǎng)的就是刷微博和逛朋友圈。
在刷微博和逛朋友圈的時(shí)候經(jīng)常會(huì)看到這種東西:
它有一個(gè)高大上的名字:九宮格。
顧名思義,九宮格通常為如圖這種三行三列的布局。
微信客戶端就用到了這種布局方式:
大家最熟悉的朋友圈也采用了九宮格:
還有微博:
它在移動(dòng)端的運(yùn)用十分的廣泛,而且不僅僅是在移動(dòng)端的運(yùn)用,它甚至還運(yùn)用到了一些面試題中,因?yàn)榫艑m格可以很好的考察面試者的 CSS 功底。
邊距九宮格
九宮格通常分為兩種,一種是邊距九宮格,另一種是邊框九宮格。
邊距九宮格就是朋友圈那種每張圖都帶有一定邊距的那種:
這種其實(shí)反而更簡(jiǎn)單一些,因?yàn)椴簧婕暗竭吙騿?wèn)題,像這種幾行幾列的布局用網(wǎng)格布局(grid)簡(jiǎn)直再合適不過(guò)了。
但考慮到大家普遍對(duì)網(wǎng)格不太熟悉,所以咱們用同樣適合幾行幾列的表格布局來(lái)實(shí)現(xiàn),為什么不用萬(wàn)能的彈性盒子(flex)來(lái)做呢?因?yàn)橄旅婺堑烂嬖囶}就是用 flex 實(shí)現(xiàn)的,不想用兩個(gè)一樣的布局來(lái)實(shí)現(xiàn),來(lái)看代碼:
運(yùn)行結(jié)果:
可以看到在 DOM 結(jié)構(gòu)上我們并沒(méi)有用到 <table\>、<tr\>、<td\> 這類傳統(tǒng)表格元素,因?yàn)樵谶@種情況下只是用到了表格的那種幾行幾列而已。
但實(shí)際上九宮格并不是表格,所以為了符合 W3C 的語(yǔ)義化標(biāo)準(zhǔn),我們采用了其他的 DOM 元素。
在有些適合使用表格布局但又不是表格的情況下,可以利用 display 屬性來(lái)模仿表格的行為:
display: table;相當(dāng)于把元素的行為變成
<table></table>
display: inline-table;相當(dāng)于把元素的行為變成行內(nèi)元素版的
<table></table>
display: table-header-group;相當(dāng)于把元素的行為變成
<thead></thead>
display: table-row-group;相當(dāng)于把元素的行為變成
<tbody></tbody>
display: table-footer-group;相當(dāng)于把元素的行為變成
<tfoot></tfoot>
display: table-row;相當(dāng)于把元素的行為變成
<tr></tr>
display: table-column-group;相當(dāng)于把元素的行為變成
<colgroup></colgroup>
display: table-column;相當(dāng)于把元素的行為變成
<col></col>
display: table-cell;相當(dāng)于把元素的行為變成
<td></td>或<th></th>
display: table-caption;相當(dāng)于把元素的行為變成
<caption></caption>
邊框九宮格
可能大家看了前面的內(nèi)容覺(jué)得:就這?這么簡(jiǎn)單還想讓人原形畢露?
那咱們來(lái)看這么一道題:
要求如下:
邊框九宮格的每個(gè)格子中的數(shù)字都要居中
鼠標(biāo)經(jīng)過(guò)時(shí)邊框和數(shù)字都要變紅
點(diǎn)擊九宮格會(huì)彈出對(duì)應(yīng)的數(shù)字
看起來(lái)還是沒(méi)什么大不了對(duì)不對(duì)?是不是覺(jué)得就是把九宮格加個(gè)邊框就行了?如果你是這么想的話,那么你寫(xiě)出來(lái)的九宮格將會(huì)變成這樣:
是不是跟想象中的好像不太一樣?為什么會(huì)這樣呢?
因?yàn)榻o每個(gè)盒子加入了邊框以后,在有邊距的情況下看起來(lái)都挺正常的,但要將他們合并在一起的話相鄰的兩個(gè)邊框就會(huì)貼合在一起,肉眼看起來(lái)就是一個(gè)兩倍粗的邊框:
那么怎么解決這個(gè)問(wèn)題呢?
解法1
不是相鄰的兩個(gè)邊框合并在一起會(huì)變粗嗎?那么最簡(jiǎn)單粗暴的辦法就是讓兩個(gè)相鄰的盒子的其中一個(gè)的相鄰邊不顯示邊框不就完了!就像這樣:
這么做完全可以實(shí)現(xiàn),絕對(duì)沒(méi)毛病。但這種屬于笨方法,如果給換成四宮格、六宮格、十二宮格,那么又要重新去想一下該怎么實(shí)現(xiàn),而且寫(xiě)出來(lái)的代碼也比較冗余,幾乎每個(gè)盒子都要給它定義一個(gè)不同的樣式。
如果去參加面試的時(shí)候這么實(shí)現(xiàn)出來(lái),面試官也不會(huì)給你滿分,甚至可能連個(gè)及格分都不會(huì)給。但畢竟算是實(shí)現(xiàn)出來(lái)了,總比那些沒(méi)實(shí)現(xiàn)出來(lái)的強(qiáng)點(diǎn),不會(huì)給零分的。
解法2
上面那種實(shí)現(xiàn)方式要給每一個(gè)盒子都寫(xiě)一套不同的樣式,而且還不適合別的像六宮格、十二宮格這類,代碼冗余、可復(fù)用性差。
那么怎么才能每個(gè)盒子只用到一個(gè)樣式,并且同樣還適用于別的宮格呢?來(lái)看看這個(gè)思路:
但是仔細(xì)一看經(jīng)不起推敲?。赫麄€(gè)九宮格最右邊和最下邊的邊框都沒(méi)有了!其實(shí)只要咱們?cè)诟冈厣显偌由嫌覀?cè)和下側(cè)的邊框即可:
而且并不一定非得是這個(gè)方向的,別的方向也可以實(shí)現(xiàn)啊,比如醬嬸兒的:
醬嬸兒的:
還有醬嬸兒的:
這種方式不管你是4、6、9還是12宮格,只需在子元素上加一個(gè)樣式即可,然后再在父元素上加一個(gè)互補(bǔ)的邊框樣式。
解法3
上面那種解法其實(shí)已經(jīng)可以了,但還不是最完美的,那么它都有哪些問(wèn)題呢?
首先,雖然換成別的宮格也可以復(fù)用,但都只適合"滿"的情況。比如像朋友圈,最大就是九宮格對(duì)吧?但用戶可以不是每次都發(fā)滿九張照片,有可能發(fā)7張、有可能發(fā)五張,這樣的話就會(huì)露餡(所以朋友圈采用的是邊距九宮格而不是邊框九宮格)。
其次,它并不適合這道面試題,因?yàn)檫@道面試題的要求是在鼠標(biāo)移入時(shí)邊框變紅,而上面那種解法會(huì)導(dǎo)致每個(gè)盒子的邊框都不完整,所以當(dāng)鼠標(biāo)移入時(shí)效果會(huì)變成這樣:
那么怎么樣才能完美的解出這道題呢?首先每個(gè)盒子的邊框不能再給它缺斤少兩了,但那又會(huì)回到最初的那個(gè)問(wèn)題上去:
有的面試題就是這樣,在你苦思冥想的時(shí)候怎么也想不出來(lái),但是稍微給點(diǎn)思路立馬就能明白!
其實(shí)就是每個(gè)盒子都給它一個(gè)負(fù)邊距,邊距的距離恰巧就是邊框的粗細(xì),這樣后面一個(gè)盒子就會(huì)"疊加"在前面那個(gè)盒子的邊框上,我們來(lái)寫(xiě)一個(gè)粗點(diǎn)的半透明邊框演示一下:
中間那些顏色變深了的就是疊在一起的邊框,由于是半透明,所以疊在一起時(shí)顏色會(huì)變深。
不過(guò)一些比較細(xì)心的朋友可能會(huì)納悶:既然所有盒子都用負(fù)邊距向左上角移動(dòng)了,豈不是九宮格不會(huì)處在原來(lái)的位置上了,沒(méi)錯(cuò)是這樣的!所以我們需要讓最左邊那一排和最上面那一排不要有負(fù)邊距,這時(shí)候就要考察候選人的CSS水平了,看看他/她能不能夠靈活運(yùn)用偽類選擇器:每一行的第一個(gè),應(yīng)該怎么寫(xiě)?
:nth-child(1), :nth-child(4), :nth-child(7) ?
這樣也能實(shí)現(xiàn),不過(guò)更好的方式是寫(xiě)成這樣:
:nth-child(3n+1)
最上面那一排負(fù)邊距可以不用管,因?yàn)槿绻?yè)面上的九宮格往左邊移動(dòng)了,哪怕只有一兩像素,也會(huì)導(dǎo)致和頁(yè)面上的版面無(wú)法對(duì)齊,而往上移動(dòng)個(gè)一兩像素的話誰(shuí)也看不出來(lái)。
每個(gè)宮格內(nèi)的數(shù)字要居中,這里推薦用 grid,因?yàn)榫艑m格可以用 flex 去實(shí)現(xiàn),但里面的內(nèi)容還繼續(xù)用它去實(shí)現(xiàn)的話就體現(xiàn)不出你技術(shù)的全面性了,而且在居中這一方面 grid 可以做到比 flex 代碼更少,即使你對(duì) grid 不感興趣,那么只需記住這一固定用法即可:
里面的內(nèi)容解決了,外面的九宮格咱們來(lái)用萬(wàn)能的 flex 去實(shí)現(xiàn),flex 默認(rèn)是一維布局,但如果僅支持一維的話就不會(huì)稱之為萬(wàn)能的 flex 了,思路是這樣的,假如每一個(gè)宮格寬高為 100 x 100,九宮格加起來(lái)是 300 x 300,每三個(gè)就讓它換行,這樣就可以考察到候選人對(duì) flex 的靈活運(yùn)用的程度了:
看起來(lái)沒(méi)毛病對(duì)不對(duì)?實(shí)際上確是每行只有兩個(gè)宮格就會(huì)換行,因?yàn)榧恿诉吙蛞院笞釉氐膶捀呔妥兂闪薫102 x 102`了,三個(gè)的話就已經(jīng)超過(guò)了`300`,所以還沒(méi)到三個(gè)就開(kāi)始換行了,這時(shí)候就考察到候選人的盒模型了:
這樣即使加了邊框,寬高也還是`100`,剛好能滿3個(gè)就換行,想象一下如果你是面試官,直接問(wèn)盒模型是不是顯得很low,但是就這一個(gè)小小的九宮格立馬就能區(qū)分出這個(gè)候選人的水平如何。
再接下來(lái)就是鼠標(biāo)移入時(shí)邊框和里面的內(nèi)容一起變紅,這有啥難的,不就是:
還是那句話,這樣確實(shí)能實(shí)現(xiàn),但如果在咱們寫(xiě)js的過(guò)程中像red這種多處地方使用的值是不是一般都會(huì)給它設(shè)置成變量???那么這里要寫(xiě)CSS變量?也可以,但有一個(gè)更好的變量叫做 currentColor,這個(gè)屬性可以把它理解成一個(gè)內(nèi)置變量,就像 js 里的 innerWidth(window.innerWidth) 一樣,不用定義自然就是一個(gè)變量。
和CSS變量不同的是它取的是自身或父元素上的color值,而且它的兼容性還更好,可以一直兼容到 IE9。
如果你覺(jué)得納悶:這單詞這么長(zhǎng),還不如直接寫(xiě)個(gè)red多方便啊,那么請(qǐng)別忘了color是可以繼承的!如果在一個(gè)外層元素中定義了一個(gè)顏色,里面的子元素都可以繼承,用JS來(lái)控制的話只需要獲取外層DOM元素然后修改它的color樣式即可。
currentColor作為一個(gè)變量,可以用在 border、box-shadow、background、linear-gradient() 等一大堆的 CSS 屬性上…甚至連svg中的 fill 和 stroke 都可以使用這個(gè)變量,它能做的事情很多,這里為了不跑題就先不展開(kāi)講,有興趣的可以去搜一下。
修改后的代碼如上,為什么沒(méi)有 currentColor?那是因?yàn)槿绻悴粚?xiě)的話,默認(rèn)就是 currentColor,這個(gè)關(guān)鍵字代表的就是你當(dāng)前的color值。
大多數(shù)的候選人可能都不會(huì)寫(xiě)成這樣,如果你作為面試官的話最好是適當(dāng)?shù)奶崾疽幌拢此懿荒苷f(shuō)出 currentColor 這個(gè)變量或者 CSS 變量。
然后就是點(diǎn)擊每個(gè)宮格彈出對(duì)應(yīng)的數(shù)字,這個(gè)考察的是事件冒泡和事件代理:
你可以觀察一下候選人是把事件綁定在父元素上還是一個(gè)個(gè)的綁定在子元素上,這個(gè)問(wèn)題按理說(shuō)基本上都不會(huì)錯(cuò)。但如果發(fā)現(xiàn)候選人一個(gè)個(gè)把事件綁定在子元素上了,那就可以到此為止了,也不用浪費(fèi)時(shí)間再去問(wèn)別的問(wèn)題了,可以十分裝B的來(lái)一句:行,你的情況我已基本了解了,回去等通知吧!
接下來(lái)我們?cè)賮?lái)寫(xiě)一下完整一點(diǎn)的代碼,以便引出下一個(gè)問(wèn)題:
運(yùn)行結(jié)果:
想知道為什么會(huì)這樣嗎?因?yàn)楫?dāng)前這個(gè)邊框被后面的宮格壓住了嘛!那么只需要當(dāng)鼠標(biāo)經(jīng)過(guò)時(shí)不讓后面的壓住就好了(調(diào)高層級(jí))。
說(shuō)到調(diào)高層級(jí),大家首先想到的可能就是 z-index 了,這個(gè)屬性用的最多的地方可能就是絕對(duì)定位和固定定位了。但其實(shí)很少有人知道, z-index 不是只能用在 position: xxx 的,萬(wàn)能的彈性盒子(display:flex)也是支持 z-index 的:
運(yùn)行結(jié)果:
結(jié)語(yǔ)
沒(méi)想到這么一個(gè)看似不起眼的九宮格一下子就能考察這么多內(nèi)容吧!如果面試的時(shí)候直接問(wèn):
你對(duì) flex 了解的怎么樣
當(dāng)元素的外邊距為負(fù)值時(shí)會(huì)有什么樣的行為
請(qǐng)實(shí)現(xiàn)一下水平垂直居中
了解過(guò) grid 嗎
談一下你對(duì)盒模型的理解
說(shuō)一下事件綁定和事件冒泡
CSS3的偽類選擇器用的怎么樣
當(dāng)頁(yè)面元素重疊時(shí)如何控制哪個(gè)在上哪個(gè)在下
在CSS中如何運(yùn)用變量
直接這么問(wèn)的話既浪費(fèi)口舌,又顯得很low,而且還不能篩選出真正能夠靈活運(yùn)用技術(shù)的候選人。
因?yàn)檫@些問(wèn)題都不難,一般來(lái)說(shuō)都能答出來(lái),但具體能不能靈活運(yùn)用就不一定了,而這一道九宮格,就像一面照妖鏡一樣,瞬間讓人原形畢露!
如果你是候選人的話,那么一定要好好練習(xí)一下這道題。
如果是面試官的話,那么也推薦你用這道題來(lái)考察候選者的技術(shù)水平,如果能非常完美的做出來(lái),那么基本上就不用再問(wèn)其他的CSS題目了,日常開(kāi)發(fā)所用到的樣式基本難不倒他/她了,可以直接上JS面試題了。
但如果沒(méi)做出來(lái)也不一定就代表這個(gè)人水平不行,可以試著提示一下候選者,然后再問(wèn)一下其他的CSS題來(lái)確定一下此人的水平。