CSS 的發展歷程:
- 手寫原生 CSS 規則,代表:
BEM
命名法 - 預處理器
Sass
、Less
和Stylus
等 - 後處理器
PostCSS
,插件式,如autoprefix
CSS Modules
,需要搭配webpack
、Gulp
或Parcel
等構建工具CSS in JS
,代表:基於React
的styled-component
原生的 CSS 規則是全局生效的。為了避免樣式衝突,出現了很多種解決方案。
BEM 命名法#
為了從開發層面上避免命名衝突問題,同時讓類名更有意義,獲得更多的描述和更加清晰的結構,從其名字可以知道某個標記的含義,讓 CSS 更具可讀性,出現了 BEM
(即:Block
、Element
、Modifier
,由 Yandex 團隊提出)前端 CSS 命名規範:
-
中劃線:僅作連字符使用,表示某個塊、子元素的多單詞之間的連接記號__
雙下劃線:雙下劃線用來連接塊和塊的子元素--
雙中劃線:雙中劃線用來描述、修飾元素的狀態、種類等
/* Block 可以理解為開發的單個組件、模塊(Component) */
.article-detail {
display: flex;
}
/* Element 是 Block 的組成部分 */
.article-detail__button {
width: 120px;
height: 36px;
}
/* Modifier 用來描述、修飾元素的狀態、種類等 */
.article-detail__button--primary {
color: #fff;
background-color: #3af;
}
但缺點也很明顯,BEM
命名方式手寫很繁瑣,開發效率低,難以維護。
CSS Modules#
CSS Modules
實質上還是 CSS 文件,它不能單獨使用,需要搭配打包構建工具使用。它賦予了原生 CSS 許多新的特性:
- 支持顯式的編寫局部與全局 CSS 規則
- 允許以模塊的方式被加載和使用到
JS
文件當中 - 打包時會將類名轉換成哈希值,杜絕 CSS 類名衝突
/* style.css */
.className {
color: green;
background: red;
}
.otherClassName {
/* 支持樣式組合(composes) */
composes: className;
color: yellow;
}
.otherClassName {
/* 支持從其他文件導入 */
composes: className from './style.css';
}
/* 以上樣式,默認是局部作用域 */
/* 局部作用域 */
:local(p) {
color: #333;
}
/* 全局作用域 */
:global(p) {
color: #333;
}
import styles from './style.css'
// import { className } from "./style.css";
element.innerHTML = '<div class="' + styles.className + '">'
// element.innerHTML = '<div class="' + styles['class-name'] + '">';
搭配 webpack
使用,配置如下:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: {
loader: 'css-loader',
options: {
modules: {
// 自定義 hash 名稱,可用變量
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}
}
}
]
}
}
打包後的效果(類名被轉換為自定義的哈希名稱格式):
._2DHwuiHWMnKTOYG45T0x34 {
color: red;
}
._10B-buq6_BEOTOl9urIjf8 {
background-color: blue;
}
Sass、Less 和 Stylus#
為了方便前端開發人員編寫 CSS,出現了很多預處理器:
總的來說,他們除了部分語法和特性的差異之外,大都具有以下特徵:
- 默認局部作用域
- 支持嵌套規則
- 支持樣式組合、繼承
- 支持同一預處理器的外部樣式文件引入(
@import
) - 允許使用變量、函數(顏色函數等)
- 打包編譯會將類名做哈希處理,不存在衝突問題
除了這些,不同的預處理器還具有條件語句、循環語句等不同的特性,詳情參考對應預處理器文檔。
這裡需要注意一點,如果 @import
引入的是原生的 CSS 格式文件,那就會額外產生 http
請求。
而預處理器中的 @import
只要是引入對應預處理器的文件,是在語法語義層面引入,而不是引入原生 CSS,那就會在打包編譯時被處理,最後只生成一個 CSS 文件,不會增加 http
請求。
/* 引入原生 CSS,會增加 http 請求 */
@import 'reset.css';
/* 預處理器格式,less 為對應預處理器的文件拓展名,如 scss,styl */
/* body.less */
body {
background: #eee;
}
/* style.less */
@import 'body.less';
PostCSS#
隨著前端工程化的不斷發展,越來越多的工具被開發出來,希望把所有重複性的工作都交給構建工具去完成,在 CSS 領域,PostCSS
興起了。
關於 PostCSS
,有一句話我覺得說的非常在理:
PostCSS
可以被稱為 CSS 界的Babel
。
PostCSS
通過分析 CSS 的語法樹(AST
),並對分析結果進行處理來完成一系列在前端開發者看來十分繁瑣複雜的工作。常見的使用場景有:
- 搭配語言語法校驗工具實現語法錯誤檢測,如 stylelint
- 將 CSS 下一代版本的語法規則做轉譯(
Transpilers
)和兼容(polyfill
)處理 - 搭配插件實現特定功能,比如使用
autoprefix
實現自動添加瀏覽器前綴的功能
關於 CSS 層面的性能優化#
常見的 CSS 性能優化的方式:
- 減少文件拆分,請求多個 CSS 文件時會受到網絡因素的制約
- 減少 CSS 嵌套,建議不超過三層
- 刪除不必要的 CSS 選擇器,合理選用 CSS 選擇器,比如:id 選擇器前無需再添加其他選擇器
- 多復用同類元素的樣式,如
button
、input
元素等 - 減少通配選擇器(
*
)與屬性選擇器([name=nav]
)的使用,這類選擇器通常需要遍歷所有元素 - 刪除無效、重複的樣式,比如:部分屬性擁有繼承的特點,如果父元素定義了,子元素就無需再次設置
- 分離頁面間公共的 CSS 規則成單獨的 CSS 文件,比如
normalize.css
,瀏覽器只需加載一次(下次使用緩存) - 多使用
CSS Sprite
(或者叫做精靈圖、雪碧圖),減少圖片、圖標的請求次數 - 使用 CSS 壓縮工具或者項目的編譯打包工具對 CSS 進行壓縮優化處理
- 減少
@import
的使用,會產生額外的請求且會影響加載的順序(CSS 預處理語言除外) - 減少大幅頻繁的佈局重排(窗口、元素、文字的大小改變、佈局切換、尺寸計算等),減少渲染消耗
- 避免使用
JS
改變單條 CSS 樣式,如是必要,可以先定義 CSS 類,然後通過改變類名來改變樣式 - 減少使用複雜、性能要求高的 CSS 動畫