寫在最前
我們都是前端工程師,無論你現在是頁面仔,還是Node服務開發者,抑或是全端大神,毋庸置疑的是,我們都是前端工程師,我們生來就對追求頁面的極致擁有敏銳的觸覺,無論是頁面實現方式的高大上、頁面的極致的效能還是頁面完美的展現,都是我們孜孜不倦的追求目標。即使這些在別人眼裡,只是跟其他的頁面一樣沒什麼不同,但我們卻能為其中那只有我們才知道的一抹別緻而竊喜。
而今天我要講的,就是我們最熟悉的老朋友,CSS。不講枯燥的語法,拋開js,讓我們一起來看業務中那別緻的純CSS實現,讓我們一起來追求那更好的頁面實現,希望我帶著你走完這段旅程後,你能收穫一些驚喜甚至靈感。
WHY,為什麼
“我有很多事要做誒,忙都忙不過來,難道我要在這CSS上面浪費很多時間?”
不,不,不,我們要做的事情,當然不會只是滿足技術的追求,而是會有實質的好處的!
我的觀點如下:
- CSS跟UI結合更加緊密;
- 用CSS來實現,能減少JS計算,減少樣式修改,減少重繪,提升渲染效率;
- 用CSS實現的,是一種模組化,更符合Web Components元件化思想,shadow DOM不就是致力於這麼做麼;
- 我們最愛的,逼格更高~
WHEN,何時
“我懂了,看起來是有那麼點意思,可是我什麼時候能用CSS來做大事啊?”
在我看來:
- 實現的物件是非互動性UI;
- 這麼做不會給你帶來過量的DOM。要知道最不能忍受的,就是臃腫的頁面;
- 這麼做能完美實現UI、能覆蓋所有場景,否則設計跟產品不服。
什麼是非互動性UI,就是不會在使用者觸發了某種行為時,嘩啦啦來個閃瞎眼的互動,嚇得使用者直接高潮,而是從頁面渲染後,就一直在那裡,那麼安靜,那麼美的女子,哦不,UI。
HOW,該怎麼做
“可是我還是不懂該如何做才能這麼有逼格”
我個人的見解:
- 佈局之美,理解透盒子模型,熟悉各種佈局,不要忘了這是我們的根本;
- 自適應之美,放心交給瀏覽器去做,我們要做的,是思考規則;
- Magic,新技術及小技巧,總能在某一剎那給你最需要的援助;
- 前人之鑑,坑王之王,你已經身經百戰了,還怕什麼。
這些就是我總結出你要用CSS來實現一個別人想不到的東西時,應該具有素質。最重要的還是思考,因為沒有一個東西是絕對最好的,我們總在前進。
下面就以兩個手機QQ實際業務的例子,帶領大家感受一下CSS的魅力。
一、手Q吃喝玩樂 好友去哪兒九宮格圖
下圖是手Q吃喝玩樂 好友去哪兒九宮格圖的圖示:
從上圖我們可以分析出如下需求:
- 圖片大小自適應;
- 圖片個數不同時,圖片按照指定方式排列;
- 圖片相鄰處有1px空白間隙。
我們以最複雜的6圖佈局為例,一步一步來看如何以純CSS實現。
float佈局
最容易想到的,也是最簡單的方案,就是 float 佈局:
- 圖片大小自適應:寬度百分比,高度使用 padding-top 百分比
- 圖片個數不同時,圖片按照指定方式排列:使用 nth-child 偽類指定不同情況下的元素大小
- 圖片相鄰處有1px空白間隙:使用 border-box + border模擬邊框
這裡父元素的高度未知,height使用百分比行不通,而padding的百分比值是依據父元素的寬度來計算的,我們可以使用padding-top撐開高度。
讓我們一瞅偽碼,猛擊我看demo
1 2 3 4 5 |
<div class="float"> <div class="item">1</div> ... <div class="item">6</div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
.float { overflow: hidden; } .item { float: left; padding-top: 33.3%; width: 33.3%; border-right: 1px solid #fff; border-top: 1px solid #fff; } .item:nth-child(1) { padding-top: 66.6%; width: 66.6%; } .item:nth-child(2), .item:nth-child(3), .item:nth-child(6) { border-right: 0 none; } .item:nth-child(1), .item:nth-child(2) { border-top: 0 none; } |
實際效果並不理想,如下圖:
可以看到 float 佈局的優點是DOM結構十分簡單,缺點是容易出現空白間隙錯位,優缺點都十分明顯,它更適用於js計算的版本。
flex佈局
還有誰?flex!flex佈局有以下重要特性:
- 可以將 flex 佈局下的元素展示在同一水平、垂直方向上;
- 可以支援自動換行、換列(移動端-webkit-box暫不支援,好訊息是從iOS9.2、Android4.4開始都支援新flex了);
- 可以指定 flex 佈局下的元素如何分配空間,可以讓元素自動佔滿父元素剩餘空間;
- 可以指定 flex 佈局下的元素的展示方向,排列方式。
這裡面的子元素同一水平、垂直方向展示對我們很有幫助,它使我們更容易控制子元素的排列,而不會錯位。
使用 flex 佈局與 float 佈局不同的地方在於,移動端目前主要還是-webkit-box,因此圖片個數不同時,我們需要使用不同的html,組合出不同的塊。
flex 佈局上下劃分
來,我們快動手分塊吧!新解決方案出現導致的腎上腺素上升,使我們迫不及待使用了傳統css文件流自上而下的方式來劃分,我稱為上下劃分,如下圖:
上面一塊包含左側1個2/3的大塊,右側2個1/3的小塊,下面一塊則是3個1/3的小塊。
我們指定2/3的大塊寬度是66.6%,1/3的小塊寬度是33.3%(實際可以使用-webkit-box-flex來分配,這裡為了下面的計算方便)。
來看下實際效果,你也可以猛擊demo來檢視原始碼:
demo中我們看到中間那條豎空白間隙錯位了,為什麼?按照預期我們上面塊左側寬度66.6%,下面塊左側寬度33.3% + 33.3%,兩個寬度應該相等才對。
然而我們忽略了flex一個重要特性,子元素會自動佔滿父元素剩餘空間,這時子元素寬度計算受flex控制,下面塊的3個子元素寬度計算並非一定是相等的,會有些許差異,此時66.6% != 33.3% + 33.3%。
怎麼破!別急,我們剛剛只是受到了腎上激素的影響,讓我們冷靜下來重新思考如何劃分。
flex 佈局左右劃分
問題在於豎間隙涉及到的左右側寬度計算不穩定,既然如此,我們可以考慮依據豎間隙左右劃分,排除不穩定因素,如下圖:
這樣就解決了豎間隙錯位問題,但我們依然有所擔心,中間的橫間隙會錯位嗎?我們來算一下。
整體父元素寬度確定,為W;
整體父元素高度由子元素撐開,不確定;
左側大塊高度:左側flex父元素寬度(W * 66.6%) * 100% = W * 66.6%;
左側小塊高度:左側flex父元素寬度( W * 66.6%) * 50% = W * 33.3%;
右側小塊高度:右側flex父元素寬度( W * 33.3%) * 100% = W * 33.3%。
依然是66.6%與33.3% + 33.3%的等式,但這次高度計算會受 flex 影響嗎?
不會,因為此時整體父元素的高度是不確定的,是由子元素內容撐開的,flex的高度也是由子元素來撐開的。
最終 66.6% === 33.3% + 33.3%
我們來看下偽碼,猛擊我看demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="wrap-box wrap-6"> <div class="flex-inner"> <div class="flex-box1 flex-item"></div> <div class="flex-box2"> <div class="flex-item"></div> <div class="flex-item"></div> </div> </div> <div class="flex-inner"> <div class="flex-item"></div> <div class="flex-item"></div> <div class="flex-item"></div> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
.wrap-box { display: -webkit-box; } .flex-inner { -webkit-box-flex: 1; display: -webkit-box; } .flex-item { -webkit-box-flex: 1; position: relative; } .wrap-6 { -webkit-box-orient: horizontal; } .wrap-6 .flex-inner { -webkit-box-flex: 0; -webkit-box-orient: vertical; } .wrap-6 .flex-inner:first-child { width: 66.6%; } .wrap-6 .flex-inner:last-child { width: 33.3%; } .wrap-6 .flex-item { padding-top: 100%; } .wrap-6 .flex-box2 .flex-item { padding-top: 50%; } .wrap-6 .flex-box2 { display: -webkit-box; -webkit-box-orient: horizontal; } .wrap-6 .flex-inner:first-child, .wrap-6 .flex-box2 .flex-item:first-child { margin-right: 1px; } .wrap-6 .flex-box1, .wrap-6 .flex-inner:last-child .flex-item:first-child, .wrap-6 .flex-inner:last-child .flex-item:nth-child(2) { margin-bottom: 1px; } |
實際效果:
二、手Q家校群先鋒教師進度條
下圖是手Q家校群先鋒教師進度條設計稿:
圖中的12345便是主角進度條。分析需求如下:
- 線的長度不固定
- 點平均地分佈在一條線上
- 點的個數不固定,可能會改變
- 啟用的點之間線的顏色是綠色的
讓我們看下如何用純CSS實現。
絕對定位大法
我們看了第一眼,便想起了最受青睞的萬金油 absoulte,方案圖如下:
- 將點、線分離,灰色背景線使用父元素的after實現;
- 點使用絕對定位,left百分比值定位;
- 綠色線條使用父元素before實現,絕對定位,寬度百分比值。
不消一會兒我們就做出來了,但再多看一眼覺得十分不妥,點和線百分比值都要手動指定,不便修改點的數量,且過多的絕對定位不優雅。
這並不是我們想要的CSS實現。
百分比寬度切分
於是我們迴歸本源,遵從CSS世界的規則來,將點線合起來看,每個子元素包含自己的點線,從左至右排列,並使用自適應佈局的方式,子元素寬度為百分比,如下圖的方案:
- 灰色背景線依舊使用父元素的after實現;
- 每個子元素寬度一致,為平均下來的百分比值,如25%;
- 點絕對定位在子元素右側;
- 綠色線條在子元素內實現。
然而我們發現這麼做不靈,在普通盒子模型裡,子元素寬度總和無法溢位父元素(除了flex),在這裡總寬度是4個帶線子元素(百分比)+1個點寬度(固定),實際25%的劃分展示與理想不符。
此外,最左側只有點,沒有線條,點的寬度固定,線條寬度不定,css無法計算(忘掉表示式吧),無法隱藏線條,fail!
百分比寬度切分進化版
攪屎棍就是最左側那固定的點,難道就不能把最左邊那該死的點從我們的百分比團隊裡排除掉嗎?如下圖:
當然可以!我們只需父元素騰出這個子元素寬度出來,不算在其餘子元素寬度百分比計算內。
騰出空間方式:父元素 margin-left 出空間,子元素負 margin-left 移回原位。
此時父元素給子元素的內容計算寬度就是width – margin-left,除首個子元素外,其餘每個子元素寬度一致,為平均下來的百分比值。
實際效果,由於原始碼較長,請猛擊demo看原始碼:
完(美),話還沒說完,產品就找來要改點的數量。
我們一看寬度是百分比設死的,這樣在點的數量修改時,我們還是要改css,完。
百分比寬度劃分究極版
來,心中默唸3遍“要優雅不要汙”,靈光一閃,flex大法好!
flex重要特性之一,可以指定 flex 佈局下的元素如何分配空間,我們將點線元素寬度改為-webkit-box-flex:1,此時子元素就自動平均分了父元素寬度。
實際效果,猛擊demo:
旅程還在繼續
本文講了筆者對前端頁面開發中儘量思考多用CSS實現的一些見解,主觀性強烈,歡迎大家的一起來探討。
通過業務實踐中的兩個例子帶領大家走了一回CSS實現旅程,還望各位觀眾姥爺過了癮,如大家有一些更好的實踐十分歡迎與我分享。
我跟你的旅程就在此結束了,但你的旅程依然在繼續,若本文能給你帶來啟發,我就最開心不過了。
最後,flex大法好!
行文匆忙,如大家發現錯誤歡迎指正。
感謝你的閱讀!