CSS工程化
在前端的不斷髮展,css也出現了很多問題,類名衝突、重複樣式定義、css文件需要細分等問題。這篇文章我們來依次介紹如何解決這些問題的。
1、命名約定:BEM規範
BEM是一套針對css類樣式的命名方法。
其他命名方法還有:OOCSS、AMCSS、SMACSS等等
BEM全稱是:Block Element Modifier
一個完整的BEM類名:block__element_modifier
,例如: banner__dot_selected,可以表示:輪播圖中,處於選中狀態的小圓點
三個部分的具體含義爲:
- Block:頁面中的大區域,表示最頂級的劃分,例如:輪播圖(
banner
)、佈局(layout
)、文章(article
)等等 - element:區域中的組成部分,例如:輪播圖中的橫幅圖片(
banner__img
)、輪播圖中的容器(banner__container
)、佈局中的頭部(layout__header
)、文章中的標題(article_title
) - modifier:可選。通常表示狀態,例如:處於展開狀態的佈局左邊欄(
layout__left_expand
)、處於選中狀態的輪播圖小圓點(banner__dot_selected
)
在某些大型工程中,如果使用BEM命名法,還可能會增加一個前綴,來表示類名的用途,常見的前綴有:
- l: layout,表示這個樣式是用於佈局的
- c: component,表示這個樣式是一個組件,即一個功能區域
- u: util,表示這個樣式是一個通用的、工具性質的樣式
- j: javascript,表示這個樣式沒有實際意義,是專門提供給js獲取元素使用的
2、css in js
一看這個名字,我們應該差不多就能纔出來,把css寫在js中。它是把css變成js中的對象, 這樣就可以完全運用js語言的優勢。舉個例子:
const styles = {
width: "400px",
height: "500px",
backgroundColor: "#ccc",
}
css變成對象,就完全不會出現命名衝突的問題。
css in js就有了一下幾個優點:
- 絕無衝突的可能:由於它根本不存在類名,所以絕不可能出現類名衝突
- 更加靈活:可以充分利用JS語言靈活的特點,用各種招式來處理樣式
- 應用面更廣:只要支持js語言,就可以支持css in js,因此,在一些用JS語言開發移動端應用的時候非常好用,因爲移動端應用很有可能並不支持css
但css in js同時也會出現一些問題:
- 書寫不便:書寫樣式,特別是公共樣式的時候,處理起來不是很方便
- 在頁面中增加了大量冗餘內容:在頁面中處理css in js時,往往是將樣式加入到元素的style屬性中,會大量增加元素的內聯樣式,並且可能會有大量重複,不易閱讀最終的頁面代碼
那麼如何使用css in js,我們可以通過我們學過的各種技術將其應用到頁面上,比如寫個循環,使用框架等等方式,只要支持js,就可以使用。vue和react都支持css in js
3、css module
見名知意,css module就是把css寫在不同的文件中,最後通過webpack構建工具合併成一個文件。多個不同的文件有相同的類名,合併之後沒有衝突的類名。
在webpack中,我們使用css-loader來處理css文件,它就實現了css module的思想(css-loader使用在webpack常用插件中有講述)。要啓用css module,需要將css-loader的配置modules
設置爲true
。
css module
原理非常簡單,css-loader會將樣式中的類名進行轉換,轉換爲一個唯一的hash值。由於hash值是根據模塊路徑和類名生成的,因此,不同的css模塊,哪怕具有相同的類名,轉換後的hash值也不一樣。
因此css-loader使用css module後,源代碼的類名和最終生成的類名是不一樣的,而開發者只知道自己寫的源代碼中的類名,並不知道最終的類名是什麼,css-loader會導出二者的對應關係,但還包括了很多其他信息。而style-loader就是去除其他信息,僅暴露類名和對應生成的hash值
舉個例子:
/*----------index.css----------*/
body{
background-color: #ccc;
}
h1{
color: #f40;
}
:global(.yellow){
color: yellow;
}
.red{
color: red;
}
/*----------index.js----------*/
import style from "./index.css"
console.log(style)
let h1 = document.getElementsByTagName("h1")[0]
h1.className = style.red
/*----------webpack.config.js----------*/
modules:{
rules[
{
test:/\.css$/,
use:["style-loader",
{
loader:"css-loader",
options:{modules:true}
}
]
}
]
}
解釋:
- 全局類名:某些類名是全局的、靜態的,不需要進行轉換,僅需要在類名位置使用一個特殊的語法即可:
:global(.main){
...
}
- 局部類名:默認就是局部類名local,是可能會造成衝突的類名,會被css module進行轉換
因爲css-loader轉換css代碼後,交給style-loader進習性處理,sytle-loader是用一段js代碼,將樣式加到style文件中。而我們通常更需要的是生成一個css文件。於是就有了庫mini-css-extract-plugin
,這個庫提供了一個plugin和一個loader:
- plugin:負責生成css文件
- loader:負責記錄要生成的css文件的內容,同時導出開啓css-module後的樣式對象
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
module: {
rules: [
{
test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader?modules"]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename:"[name].[hash:5].css"
}) //負責生成css文件,name是chunk的name
]
}
其中"css-loader?modules"
等同於
{
loader:"css-loader",
options:{
modules:true
}
}
4、預處理器
關於預處理器看這篇文章吧!十分鐘帶你學會Less預編譯器