最近在用element UI開發一個toB系統時,發現設計稿和UI庫有不小的出入,由於不是內部系統,所以這塊的還原度沒辦法“得過且過”。我整理了一些覆蓋UI庫樣式的“手段”
為什么UI庫(這里用的是element UI)的組件不好直接覆蓋?
我們通常的vue工程都是用vue-cli自動生成出來的,不知道大家有沒有發現一個細節——生成的*.vue文件上會默認帶上“scoped”,如下圖:
UI庫不好覆蓋的問題也基本從這里開始了。首先看“scoped”是什么?首先“scoped”並不是vue的專利,(“scoped”屬性是HTML5的新特性,如果使用該屬性,則樣式僅僅應用到style元素的父元素及其子元素。)說人話就是vue用了scoped屬性,導致當前*.vue文件里的style僅僅作用於當前組件的元素,而對部分element UI的組件無效(一些簡單的組件,例如el-button這種簡單替換的還是可以覆蓋的)。
“scoped”在工程中是如何工作的?
我們可以用自己的工程運行起來看一下。看看生成的頁面是什么樣的。
可以看到,在vue中引入了scoped這個概念,scoped的設計思想就是讓當前組件的樣式不會修改到其它地方的樣式,使用了data-v-hash的方式來使css有了它對應模塊的標識,這樣寫css的時候不需要加太多額外的選擇器,方便很多。
但是要注意scoped的作用域,因為權重的問題,如果是在子組件使用了scoped,那么在父組件中是不能直接修改子組件的樣式的,需要在父組件中使用vue的深度作用選擇器。
問題來啦,我們在自己組件上用scoped初衷是好的。還拿上面的例子來說,我們data-v-a2a7b732是我們自己組件的模塊的標識,這里element UI對“簡單組件”並沒有用data-v-hash管理,我們再舉個“復雜組件”的例子,比如帶浮層的例如el-select,我們想把【全部數據的按鈕邊框去掉】
這樣寫出來發現在瀏覽器的選擇器里並沒有生效。對於“el-input__inner”生的只有庫本身的css樣式,我們看似“合理”的css繼承關系為什么沒有生效呢?我們來看一下我們自己的代碼到底生成了什么:
首先父級長這樣:data-type有了一個屬性選擇器確保唯一
工程生成的長這樣: 發現問題了嗎?
- 給HTML的DOM節點加一個不重復
data
屬性(形如:data-v-a2a7b732
)來表示他的唯一性 - 在每句css選擇器的末尾(編譯后的生成的css語句)加一個當前組件的
data
屬性選擇器(如[data-v-a2a7b732]
)來私有化樣式
也就是說scope的操作是這樣的:我們的組件作為父級組件,調用其他組件(element UI)的場景下,scope僅僅作用於我們的當前組件,我們的組件每一級dom上多了一個data-v-hash,生成的css結尾加上了屬性"[data-v-hash]"(注意是每個css規則的結尾),這樣做的策略是保證css命中的葉子節點是在scope規則下的。那么我們在父級嵌套element ui時“el-input__inner”作為葉子節點即一條css規則的末尾被加上了后綴"[data-v-hash]",但是實際渲染DOM上element UI組件並不會加上屬性"[data-v-hash]"。因此后綴"[data-v-hash]"的css無法匹配屬性"[data-v-hash]"的DOM元素。也就不會生效。
為什么有的時候能覆蓋?
剛才簡單提到過,比較簡單的elementUI組件能搞定,我們看看為什么。這里有一個按鈕,我要附加一些樣式:
注意這里我就是正常寫了,給button加上一個自己的class:add-account,但是生效了。
原因在於,elememt-ui中有一些組件,其實是單層級組件,比如:<el-button>替換成了<button class="el-button">而且會拷貝所有的class,因此element替換后仍是當前組件里的scoped控制,而上面提到的情況是組件內部深層元素不受scoped影響的情況。所以scoped場景下,仍可以控制其他組件的最外層dom,深層次規則就會出現前面的情況。而element-ui的“簡單組件”基本是單層結構,也就是當前層仍能添加"[data-v-hash]"。
下面我們來看一下幾種解決方案:
1、去掉scoped
剛才提到css不能覆蓋的原因是屬性"[data-v-hash]"導致的,那么最簡單的方法就是去掉“scoped”,但是一旦這樣做,當前組件中的css就可能污染組件外的空間,vue工程本來就比較龐大復雜,一個頁面很可能會加載很多的組件,這些組件頁有可能多人維護,難免名字相同,除非你的css有比較好的“命名空間管理”就像element-ui一樣。我個人建議還是不要輕易去掉。
2、多個<style></style>
一個vue文件可以寫
多個<style>標簽,我們可以把大部分代碼寫在<style scoped>里,少數需要覆蓋子組件的寫在普通<style>中。但是這樣只是減小了污染,並沒有解決
3、/deep/ 或者 >>> 深度作用選擇器
還用最開始的el-select舉例,我添加了/deep/如下圖:
生效了!沒有border了
某些預處理器(如Sass)可能無法>>>正確解析。在這些情況下,您可以使用/deep/組合器 - 它是別名>>>並且工作完全相同。這種方法,我比較推薦,頁很好用,但是並不是萬能的。
4、css import
有一些element-ui組件會產生脫離當前從屬結構的DOM元素,比如el-dialog會在body中插入一段html,這個dom就不符合當前組件的從屬關系了,並不是當前組件的子元素。第一種方法是css import,這種元素仍可以產生一些特征來減少污染,element-ui對這類組件提供了一個css的命名權,即,你可以對子組件的某個結構單獨命名。例如el-dialog和el-table都有這樣的屬性:
或
這樣在body中插入的html就有了一個和組件名相關的class。我們可以在組件路徑下封一個單獨的css處理(不推薦寫到common里,不好維護)。
5、style-function
這也是我發現element獨特支持的方法,還用剛才的el-table舉例。
我們可以傳入一個函數,return你要的樣式。
或
這種方法相當於是向特定dom上加上style。完全不污染全局,但是依賴ui庫自身提供接口。但是可以根據具體參數靈活計算。