項目穩(wěn)定性治理思考:防御性CSS技能
一、概念解釋
防御性CSS,防的是誰?我把他總結為:一切使表現(xiàn)和行為偏離預期效果的情景。出現(xiàn)這些場景的原因是因為終端環(huán)境的多樣化,開發(fā)及測試用例只能覆蓋大多數(shù)使用場景,在其他環(huán)境下,解析機制差異、內容動態(tài)變化等,都是導致非預期效果的原因。
二、防御的必要性
防御性CSS不僅僅是為了兼容其他少數(shù)場景,避免邊界情況,更大的價值在于提升團隊協(xié)作的可能性。防御性CSS的意義類似JS中的try...catch, 他可能無法縮短需求開發(fā)的時間,但卻是你程序正確運行和穩(wěn)定運行的最后一道防線,更何況JS的錯誤只有在用戶交互后才有感知,而CSS一旦出錯,直接赤裸裸的展現(xiàn)在用戶面前,直接影響用戶的使用率和留存率。
都說編程風格分為三種:能跑就行風格、中規(guī)中矩風格、錦上添花風格。能跑就行風格代表的是:每一個設定和判斷都和當次需求貼合的嚴絲合縫,如同山羊走鋼絲,搖搖欲墜,但就是不倒,不得不令人稱奇,但這種風格,不僅對編程人員要求極高,而且十分不利于團隊協(xié)作,一旦意料意外的情景發(fā)生或者需求變更,帶來的是雪崩式的改動;中規(guī)中矩風格概述為,該寫注釋的地方寫注釋,該寫思路的地方寫自己這么做的理由,該兜底處理的地方做攔截處理,程序的魯棒性和可維護性直接拉滿;萬無一失風格更多的像是處女座,追求極致和完美,在中規(guī)中矩風格上再增一抹亮色,年輕時候的“雷布斯”就是典型代表。防御性CSS的目的就是從技術上盡可能的改變編程者能跑就行的僥幸心理,提升項目的可用性和可維護性。其目的也可以歸納為讓你的項目做到:跑起來不出錯,改起來不罵人。
防御性CSS的作用是對常規(guī)CSS的兜底,是實現(xiàn)項目穩(wěn)定性建設重要但極其容易被忽視的一環(huán)。
三、防御技能
技能一:flex-wrap
屬性背景:flex-wrap是flex布局中的屬性,其作用是控制flex容器內元素所占空間超出flex容器空間時是否折行。
防御原因:flex-wrap屬性默認是不折行,容易忽略多元素溢出兜底;為兜底,請設置flex-wrap: wrap;
意外后果:內部元素被裁剪,或flex容器出現(xiàn)滾動條;
應用場景:
1)開發(fā)中flex容器空間夠用,但小尺寸屏幕會溢出;
2)內容由服務端下發(fā),元素個數(shù)無法提前預支,超于預期時導致flex容器出現(xiàn)滾動條或內部元素被裁剪;
代碼:
.options-list {
display: flex;
flex-wrap: wrap;
}
示例場景
技能二:margin間距
屬性背景:margin作用是調整元素的外邊距。用于指定元素與周圍空間的距離關系。
防御原因:防止元素與元素之間擠壓空間,造成重疊等情況;
意外后果:元素重疊或被擠壓;
應用場景:
1)內容所占空間無法保證與其他元素不存在擠壓的場景;
代碼:
.section__title {
margin-right: 1rem;
}
示例場景
技能三:長文本處理
背景:當文本長度超出容器時,該如何顯示。
意外后果:文本折行,樣式不統(tǒng)一;
應用場景:
1)要求列表表現(xiàn)一致但文本長度不可控;
此處假設與設計最終商定,超出部分以省略顯示,那么,意外兜底的樣式代碼為:
.username {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
場景示例:
技能四:防止圖像被拉伸或壓縮
背景:通常,服務器下發(fā)的圖片尺寸以及用戶自定義上傳的圖片,顯示在頁面時,不可能百分百與容器尺寸貼合,不可避免的會遇到圖片的放縮處理。
意外后果:圖像被拉伸或壓縮;
應用場景:
1)服務端下發(fā)多種不確定尺寸的圖片;
2)用戶自定義上傳圖片且需要預覽和編輯;
.card__thumb {
object-fit: cover;
}
demo鏈接:https://monageju.github.io/Blog/object_fit.html
場景示例:
技能五:鎖定滾動鏈接
背景:overscroll-behavior是overscroll-behavior-x和overscroll-behavior-y的簡寫屬性,它控制的是元素滾動到邊界時的表現(xiàn)。換個能聽得懂的說法:在JS世界里,有事件冒泡機制,你可以通過event的stopPropagation方法去阻止冒泡的發(fā)生,同樣,在CSS世界里,滾動也有冒泡機制,當內部元素滾動到邊界時,如果繼續(xù)滾動,會帶動外層祖先元素發(fā)生滾動,這種現(xiàn)象被稱為滾動鏈,為了方便記憶,你也可以把他形象的記憶為滾動冒泡。而overscroll-behavior這個屬性,就是類似event的stopPropagation方法阻止冒泡事件一樣,提供給開發(fā)者去控制內層元素是否可以發(fā)生”冒泡“帶動外層元素滾動的屬性。
意外后果:”滾動冒泡“ 或 ”滾動穿透“;
應用場景:
1)頁面存在多層滾動元素,需要單獨控制每層滾動是否引起外層滾動;
.child {
overscroll-behavior-y: contain;
overflow-y: auto;
}
demo鏈接:https://monageju.github.io/Blog/overscroll_behavior.html
場景示例:如demo鏈接示例
拓展:
理解了overscroll-behavior屬性的作用,現(xiàn)在我們來看點拓展的東西:
首先來看下overscroll-behavior的屬性值有哪些:
overscroll-behavior 屬性有 3 個值:
auto - 默認。元素的滾動會傳播給祖先元素。
contain - 阻止?jié)L動鏈接。滾動不會傳播給祖先,但會顯示元素內的原生效果。例如,Android 上的炫光效果或 iOS 上的回彈效果,當用戶觸摸滾動邊界時會通知用戶。注意:overscroll-behavior: contain 在 html 元素上使用可防止?jié)L動導航操作。
none - 和 contain 一樣,但它也可以防止節(jié)點本身的滾動效果(例如 Android 炫光或 iOS 回彈)。
這里有兩個效果:一是下拉刷新,二是炫光回彈,這里有個demo可以看到具體效果:鏈接傳送門
下拉刷新是原生支持的功能,如果項目要求自定義下拉刷新效果,除了要考慮如何實現(xiàn)自定義,還要考慮如何去掉默認原生下拉刷新,否則就會出現(xiàn)兩個并存的下拉刷新,而去掉原生的下拉刷新也很簡單,只需要在 body 或 html 元素添加如下代碼:
body {
/* 禁用滾動冒泡,但是依然可以進行下拉刷新和炫光和回彈效果以及滑動導航 */
overscroll-behavior-y: contain;
}
至于禁用炫光和回彈效果,其實是應用overscroll-behavior屬性的none屬性值,具體代碼如下:
body {
/* 禁用默認的下拉刷新和炫光和回彈效果,但是依然可以進行滑動導航 */
overscroll-behavior-y: none;
}
除了上述描述的兩個效果,其實還有一個效果:手勢導航,如左滑退出及右滑前進功能;而如果要禁用手勢導航,可以使用如下代碼:
body {
/* 禁用滑動導航 */
overscroll-behavior-x: none;
}
技能六:CSS變量默認值
背景:CSS變量可以實現(xiàn)動態(tài)控制元素屬性,但是當CSS變量未定義或無效時,造成變量值異常,此時,元素的樣式將會脫離預期,而變量默認值可以實現(xiàn)異常兜底,保證變量值異常時頁面依然能運行。需要額外說明的是,備用值并不是用于實現(xiàn)瀏覽器兼容性的。如果瀏覽器不支持CSS自定義屬性,備用值也沒什么用。它僅對支持CSS自定義屬性的瀏覽器提供了一個備份機制,該機制僅當給定值未定義或是無效值的時候生效,函數(shù)的第一個參數(shù)是自定義屬性的名稱。如果提供了第二個參數(shù),則表示備用值,當自定義屬性值無效時生效。
意外后果:因失去寬高等變量值而不顯示或變形;
應用場景:
.item {
color: var(--my-var, red); /* Red if --my-var is not defined */
}
技能七:彈性元素尺寸 min-height / min-width
背景:當需求要求完整展示某個列表數(shù)據(jù),但列表數(shù)據(jù)所占空間無法固定時,為避免部分內容過寬、過高突破固定空間破壞布局,可以使用彈性尺寸 min-* 或者 max-* , 這樣能自動適應部分內容所占空間過大或過小帶來的樣式美觀問題;
意外后果:占用空間過大或過小,破壞布局或不美觀;
應用場景:
.hero {
min-height: 350px;
}
場景示例:
max-width的使用場景:
如果對每一個元素使用固定的width,則當內容空間大于容器尺寸時,將發(fā)生溢出,此時,需要使用min-width 限制最小寬度,當超出尺寸時,能夠實現(xiàn)自動適配。
技能八:被遺忘的background-repeat
背景:使用圖片作為容器的背景圖,當容器的尺寸大于圖片尺寸時,默認背景圖會重復,如果你在開發(fā)中忽略了上述問題,則會出現(xiàn)背景圖重復的問題;
場景示例:
解決辦法:
代碼如下 :
background-image: url('..');
background-repeat: no-repeat;
解決后效果:
技能九:媒體查詢 @media
背景:媒體查詢的使用更像是CSS中的條件判斷,它會根據(jù)你定義的條件,當條件滿足時,條件內的樣式生效;
舉例:當屏幕的寬度小于600px時,body背景色為紅色;當屏幕寬度介于600-800px之間時,body背景色為黃色;當屏幕寬度大于800px時,body的背景色為藍色;
示例代碼:
/* 將 body 的背景色設置為藍色 */
body {
background-color: blue;
}
/* 在小于或等于 800 像素的屏幕上,將背景色設置為黃色 */
@media screen and (max-width: 800px) {
body {
background-color: yellow;
}
}
/* 在 600 像素或更小的屏幕上,將背景色設置為紅色 */
@media screen and (max-width: 600px) {
body {
background-color: red;
}
}
demo鏈接如下:https://monageju.github.io/Blog/media.html
技能十:圖片上的文字
背景:當需要在圖片上層展示文字時,如果圖片加載失敗,而外層容器的背景色和文字顏色接近,那么文字的展示效果就不理想;
舉例:容器背景設置為黑色,圖片為橙色,文字顏色為近黑色,當圖片加載失敗時,文字的背景色直接變?yōu)槿萜鞯谋尘吧?,文字與容器背景色重合,示例如下;
解決后效果:
解決代碼:
.card__img {
background-color: #FFF;
}
至此,即使圖片加載失敗,圖片上的問題依然可以正常顯示;至于圖片加載失敗時左上角的“破圖”標記,可以使用偽類進行遮擋美化;
技能十一:合理使用滾動條屬性
背景:當容器的空間固定時,如果內容超出容器,為正常顯示完所有內容,同時不擴展所占空間,會使用overflow屬性控制超出部分自動滾動展示,同時給與滾動條樣式提示有剩余內容,但如果該屬性使用不當,會造成樣式很難看;
舉例:overflow屬性有兩個作用很相近的屬性值,一個是scroll, 另一個是auto; 這兩個屬性值都能實現(xiàn)當內容大于所占空間時滾動展示,不同點在于使用scroll屬性無論內容是否超出容器空間,都會展示滾動條,而auto屬性會分辯條件,內容超出時才會展示滾動條,為超出時則會自動隱藏,樣式上較為美觀;
解決代碼:
.box {
overflow-y: auto;
}
場景示例:
技能十二:預留滾動條空間,避免重排
背景:接技能十一,當我們正確使用了overflow:auto就萬事大吉了嗎?也不盡然。
設想這樣一個場景:有一個寬度100vw,高度為100vh的容器盒子,容器內展示商品卡片,滑動到頁面底部時,觸發(fā)滑動加載,當觸發(fā)懶加載時,容器內商品卡片占用的高度已經超出100vh,依據(jù)外層容器設置的overflow:auto,內容超出時會展示滾動條,滾動條的出現(xiàn),使得頁面不得不給滾動條讓出一定的寬度,這個切換的場景中,由于不得不給滾動條讓位置,最外層的元素發(fā)生了元素寬度變化,產生了重排的效果,有沒有可能避免這一次不必要的重排呢?答案是有的。
大家一定還記得vue的指令中有兩個很相像的指令v-if 和 v-show, 他們倆的原理和區(qū)別是什么?分別用在什么情景下?提醒到這,是不是有思路了?如果還沒有,那也沒關系,再提示一點點,既然要避免多余的一次重排,而滑動加載又不可避免,如果我一開始就預留好滾動條的位置,只是你看不見,到了滾動條應該出場的時候再讓你看見,是不是就能避免不必要的重排了呢?現(xiàn)在再想想,這是不是就是v-show指令的設計原理?
CSS中有一個scrollbar-gutter屬性,當它的值設置為stable時,就能夠實現(xiàn)上述的這種功能,代碼如下:
.box {
scrollbar-gutter: stable;
}
舉例:
內容較短時預留滾動條空間,內容超出時顯示滾動條;
技能十三:圖片最大寬度
背景:當給固定寬高容器設置背景圖時,如果背景圖尺寸超過容器寬高,圖片會溢出,因此,最好在項目的resetCss中按照以下屬性屬性初始化:
img{
max-width: 100%;
object-fit: cover;
}
實例:
技能十四:粘性定位
說明:position的粘性定位指的是通過用戶的滾動,元素的position屬性在 position:relative 與 position:fixed 定位之間切換;這對于需要使用滾動吸頂?shù)膱鼍胺浅7奖?;是典型的依?jù)業(yè)務場景推動CSS技術發(fā)展的典例;
技能十五:瀏覽器兼容性CSS請勿批量處理
說明:根據(jù)W3C標準,批量分組選擇選擇器,如果分組中,其中一個無效,那么整個選擇器都將會失效。因此,在遇到瀏覽器兼容屬性時,切勿批量組合書寫;
實例:
如果是如下書寫方式,則該選擇器沒有任何問題,因為該分組選擇器的所有選擇器都有效:
h1, h2, h3 { font-family: sans-serif }
此時,它的作用等同于:
h1 { font-family: sans-serif }
h2 { font-family: sans-serif }
h3 { font-family: sans-serif }
但如果,是下面這種情況就不同了:
input::-webkit-input-placeholder,
input:-moz-placeholder {
color: #222;
}
該選擇器使用了分組選擇器,在確定的某一個瀏覽器中,該分組中只有一個選擇器有效,而其他選擇都是失效狀態(tài),根據(jù)規(guī)則,整個分組選擇器都將會失效,因此,正確的做法應該是分開寫,代碼如下:
input::-webkit-input-placeholder {
color: #222;
}
input:-moz-placeholder {
color: #222;
}
此時,其效果才是符合預期的。
四、結語
通常一個項目的穩(wěn)定性指的都是邏輯層穩(wěn)定和服務層穩(wěn)定,CSS是極其容易被忽視的一層;當項目發(fā)生線上故障時,邏輯層和服務器可以通過日志查詢、抓包等手段定位,而CSS問題則只能憑借經驗和項目所運行環(huán)境進行大致推斷,極難快速準確定位問題。在穩(wěn)定性建設時,CSS的書寫應該遵循“瞻前顧后”的防御性寫法,盡可能的避免意外的邊界情況,這才是防御性CSS的真實價值。