CSS Modules入门教程
為什么引入CSS Modules
或者可以這么說,CSS Modules為我們解決了什么痛點。針對以往我寫網頁樣式的經驗,具體來說可以歸納為以下幾點:
全局樣式沖突
過程是這樣的:你現在有兩個模塊,分別為A、B,你可能會單獨針對這兩個模塊編寫自己的樣式,例如a.css、b.css,看一下代碼
// A.js import './a.css' const html = '<h1 class="text">module A</h1>'// B.js import './b.css' const html = '<h1 class="text">module B</h1>'下面是樣式:
/* a.css */ .text {color: red; }/* b.css */ .text {color: blue; }導入到入口APP中
// App.js import A from './A.js' import B from './B.js'element.innerTHML = 'xxx'由于樣式是統一加載到入口中,因此實際上的樣式合在一起(這里暫定為mix.css)顯示順序為:
/* mix.css *//* a.css */ .text {color: red; }/* b.css */ .text {color: blue; }根據CSS的Layout規則,因此后面的樣式會覆蓋掉前面的樣式聲明,最終有效的就是
text的顏色為 blue的那條規則,這就是全局樣式覆蓋,同理,這在 js中也同樣存在,因此就引入了模塊化,在js中可以用立即執行函數表達式來隔離出不同的模塊 var moduleA = (function(document, undefined){// your module code })(document)var moduleB = (function(document, undefined){// your module code })(document)而在css中要想引入模塊化,那么就只能通過
namespace來實現,而這個又會帶來新的問題,這個下面會講到嵌套層次過深的選擇器
為了解決全局樣式的沖突問題,就不得不引入一些特地命名
namespace來區分 scope,但是往往有些 namespace命名得不夠清晰,就會造成要想下一個樣式不會覆蓋,就要再加一個新的 namespace來進行區分,最終可能一個元素最終的顯示樣式類似如以下: .widget .table .row .cell .content .header .title {padding: 10px 20px;font-weight: bold;font-size: 2rem; }在上一個元素的顯示上使用了7個選擇器,總結起來會有以下問題:
- 根據CSS選擇器的解析規則可以知道,層級越深,比較的次數也就越多。當然在更多的情況下,可能嵌套的層次還會更深,另外,這里單單用了類選擇器,而采用類選擇器的時候,可能對整個網頁的渲染影響更重。
- 增加了不必要的字節開銷
- 語義混亂,當文檔中出現過多的content、title以及item這些通用的類名時,你可能要花上老半天才知道它們到底是用在哪個元素上
- 可擴展性不好,約束越多,擴展性越差
【注】CSS的渲染規則可以參看這篇文章探究 CSS 解析原理
會帶來代碼的冗余
由于CSS不能使用類似于js的模塊化的功能,可能你在一個css文件中寫了一個公共的樣式類,而你在另外一個css也需要這樣一個樣式,這時候,你可能會多寫一次,類似于這樣的
/* a.css */.modal {position: absolute;top: 0;bottom: 0;left: 0;right: 0;z-index: 1;background-color: rgba(0, 0, 0, 0.7); } .text {color: red; }/* b.css */ .modal {position: absolute;top: 0;bottom: 0;left: 0;right: 0;z-index: 1;background-color: rgba(0, 0, 0, 0.7); } .text {color: blue; }那么在合并成app.css的時候,就會被編寫兩遍,雖然樣式不會被影響,但是這樣實際上也是一種字節浪費,當然,上述的這種情況完全是可以通過公用全局樣式來達到目的,但是,這種代碼重復通常是在不知情的情況下發生的。
一些解決方案
針對上述的一些問題,也有一些解決方案,具體如下:
CSS預處理器(Sass/Less等)
Sass,Less的用法這里不再贅述,如果不清楚,可以自己查閱相關資料去了解一下。
CSS預處理器最大的好處就是可以支持模塊引入,用js的方式來編寫CSS,解決了部分
scope混亂以及代碼冗余的問題,但是也不能完全避免。同時,也沒有解決全局樣式的沖突問題一個
SASS的的文件是這樣的: /* app.sass */@import './reset' @import './color' @import './font'可以實際上編譯之后,終究還是一個文件,因此不可避免的會出現沖突樣式
BEM(Block Element Modifier)
There are only two hard problems in Computer Science: cache invalidation and naming things — Phil Karlton
BEM就是為了解決命名沖突以及更好的語義化而生的。BEM名詞解釋
Block:邏輯和頁面功能都獨立的頁面組件,是一個可復用單元,特點如下:
- 可以隨意嵌套組合
- 可以放在任意頁面的任何位置,不影響功能和外觀
- 可復用,界面可以有任意多個相同Block的實例
- Element:Block的組成部分,依賴Block存在(出了Block就不能用)
[可選]定義Block和Element的外觀及行為,就像HTML屬性一樣,能讓同一種Block看起來不一樣
命名規則
Block作為最小的可復用單元,任意嵌套不會影響功能和外觀,命名可以為 header、 menu等等 <style>.header { color: #042; } </style><div class="header">...</div> Element依附Block存在,沒有單獨的含義,命名上語義盡量接近于Block,比如 title、 item之類 <style>.header { color: #042; }.header__title { color: #042; } </style><div class="header"><h1 class="header__title">Header</h1> </div> Modifier是一個元素的狀態顯示,例如 active、 current、 selected <style>.header--color-black { color: #000; }.header__title--color-red { color: #f00; } </style><div class="header header--color-black"><h1 class="header__title"><span class="header__title--color-red">Header</span></h1> </div>【說明】
- Block完全獨立,可以嵌套,一個header是一個Block,header下的搜索框也可以是一個Block
- 不可能出現Block__Element-father__Element-son_Modifer這種類名的寫法,BEM只有三級
- Modifier可以加在Block和Element上面
- Modifier作為一個額外的類名加載Block和Element上面,只是為了改變狀態,需要保留原本的class
一個完整的示例
<form class="form form--theme-xmas form--simple"><input class="form__input" type="text" /><inputclass="form__submit form__submit--disabled"type="submit" /> </form> .form { } .form--theme-xmas { } .form--simple { } .form__input { } .form__submit { } .form__submit--disabled { }參考鏈接:
- get BEM
- BEM(Block-Element-Modifier)
- 如何看待 CSS 中 BEM 的命名方式?
BEM解決了模塊復用、全局命名沖突等問題,配合預處理CSS使用時,也能得到一定程度的擴展,但是它依然有它的問題:
- 對于嵌套過深的層次在命名上會給需要語義化體現的元素造成很大的困難
- 對于多人協作上,需要統一命名規范,這同樣也會造成額外的effort
CSS Modules
說了這么多,終于要到正文了
什么是CSS Modules
根據CSS Modules的repo上的話來說是這樣的:
CSS files in which all class names and animation names are scoped locally by default.
所以CSS Modules并不是一個正式的聲明或者是瀏覽器的一個實現,而是通過構建工具(webpack or Browserify)來使所有的class達到scope的一個過程。
CSS Modules 解決了什么問題
- 全局命名沖突,因為CSS Modules只關心組件本身,只要保證組件本身命名不沖突,就不會有這樣的問題,一個組件被編譯之后的類名可能是這樣的:
命名唯一,因此保證了全局不會沖突。
- 模塊化
可以使用
composes來引入自身模塊中的樣式以及另一個模塊的樣式: .serif-font {font-family: Georgia, serif; }.display {composes: serif-font;font-size: 30px;line-height: 35px; }應用到元素上可以這樣使用:
import type from "./type.css";element.innerHTML = `<h1 class="${type.display}">This is a heading</h1>`;之后編譯出來的模板可能是這樣的:
<h1 class="Type__display__0980340 Type__serif__404840">Heading title </h1>從另一個模塊中引入,可以這樣寫:
.element {composes: dark-red from "./colors.css";font-size: 30px;line-height: 1.2; }- 解決嵌套層次過深的問題
因為CSS Modules只關注與組件本身,組件本身基本都可以使用扁平的類名來寫,類似于這樣的:
.root {composes: box from "shared/styles/layout.css";border-style: dotted;border-color: green; }.text {composes: heading from "shared/styles/typography.css";font-weight: 200;color: green; }CSS Modules 怎么用
CSS Modules不局限于你使用哪個前端庫,無論是React、Vue還是Angular,只要你能使用構建工具進行編譯打包就可以使用。
下面我使用
webpack為例,一步一步引入CSS Modules.構建最初始的應用
. ├── build │?? └── bundle.js ├── index.html ├── node_modules ├── package-lock.json ├── package.json ├── src │?? ├── index.js │?? └── styles └── webpack.config.jsindex.js作為程序入口,styles文件夾存放樣式文件,配合webpack.config.js作為webpack配置文件。
// index.js var html = `<div class="header"><h2 class="title">CSS Modules</h2> </div>`document.getElementById('container').innerHTML = html;樣式文件:
/* global.css */ * {margin: 0;padding: 0; }.container {padding: 20px; }/* index.css */ .header {font-size: 32px; }.title {border-bottom: 1px solid #ccc;padding-bottom: 20px; }模板文件:
<!-- index.html --><!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>css modules</title> </head> <body><div id="container" class="container"></div><script src="./build/bundle.js"></script> </body> </html>全局安裝依賴,配置執行腳本:
npm install webpack webpack-cli --save-devpackage.json
"scripts": {"build": "npx webpack && open index.html" }在控制臺執行
npm run build, 得到的結果為: > css-modules-demo@1.0.0 build /Users/yhhu/Documents/coding/css-modules-demo > npx webpack && open index.htmlHash: 5810d2ecd760c08cc078 Version: webpack 4.17.1 Time: 78ms Built at: 2018-08-26 15:09:31Asset Size Chunks Chunk Names bundle.js 3.97 KiB main [emitted] main Entrypoint main = bundle.js [./src/index.js] 196 bytes {main} [built]加入樣式以及loaders
package.json中加入能夠處理css的loader
module: {rules: [{test: /\.js/,loader: 'babel-loader',include: __dirname '/src',exclude: __dirname '/src/styles'},{test: /\.css$/,use: [{ loader: 'style-loader' },{loader: 'css-loader',options: { }}]}]}index.js中引入兩個CSS文件
// index.js import './styles/global.css' import './styles/index.css'const html = `<div class="header"><h2 class="title">CSS Modules</h2> </div>`document.getElementById('container').innerHTML = html;編譯之后的執行結果為:
在瀏覽器中顯示為:
提取公有樣式
可以看到打包之后的
build目錄下只有一個 bundle.js,我們現在要把樣式文件提取出來 ./build/ └── bundle.js- 安裝依賴
- 修改webpack.config.js
- 在模板中引入樣式文件
- 編譯打包
可以看到有
main.css生成開啟css modules功能
默認在
css-loader中是不開啟 css modules功能的,要開啟可以設置 modules: true即可,更多可以參看官方 css-loader使用方法修改 webpack.config.js,如下: {test: /\.css$/,use: [{loader: MiniCssExtractPlugin.loader,options: {publicPath: './build/styles'}},{ loader: "css-loader",options: {modules: true}}] }修改
index.js文件中的引用方式: import './styles/global.css' import Index from './styles/index.css'const html = `<div class=${Index.header}><h2 class=${Index.title}>CSS Modules</h2> </div>`document.getElementById('container').innerHTML = html;可以看到,之前都是直接
import一個 css文件,而現在改成了導出一個對象的形式,我們可以把 Index對象打印出來,看看具體是些什么東西:直接對應我們引用的方式,然后我們再看看生成出來的
main.css中具體有哪些東西: * {margin: 0;padding: 0; }._2BQ9qrIFipNbLIGEytIz5Q {padding: 20px; } ._3Ukt9LHwDhphmidalfey-S {font-size: 32px; }._3XpLkKvmw0hNfJyl8yU3i4 {border-bottom: 1px solid #ccc;padding-bottom: 20px; }合成一個文件之后,所有的類名都經過了哈希轉換,因此確保了類名的唯一性,我們再看看瀏覽器中
inspector中的樣式應用,如下:事實上,
container樣式我們是不需要轉換的,因為我是把它固定寫死在了容器上,那我們應該怎么做呢?全局作用域
要想一個類名不需要被裝換,那么可以使用
:global(className)來進行包裝,這樣的類不會被轉換,會被原樣輸出,下面我們修改 global.css /* global.css */ * {margin: 0;padding: 0; }:global(.container) {padding: 20px; }我們再來看看
main.css就可以發現
.container類沒有被轉換定義哈希類名
CSS Modules默認是以[hash:base64]來進行類名轉換的,可辨識度不高,因此我們需要自定義
開啟自定義,可以使用一個配置參數
localIdentName,具體配置如下: { loader: "css-loader",options: {modules: true,localIdentName: '[path][name]__[local]--[hash:base64:5]'} }類名組合
如果我們實現類似于
Sass的繼承功能,我們需要怎么做呢?CSS Modules中提供了 composes關鍵字讓我們來繼承另外一個類,修改 index.css如下: .red {color: red; }.header {font-size: 32px; }.title {composes: red;border-bottom: 1px solid #ccc;padding-bottom: 20px; }我們增加了一個
red的類名,在 title中實現繼承,編譯之后的結果為:發現多了一個
src-styles-index__red--1ihPk的類名,正是我們上面繼承的那個類除了在自身模塊中繼承,我們還可以繼承其他文件中的CSS規則,具體如下:
我們再
styles文件夾下新建一個 color.css /* color.css */ .red {color: red; }.blue {color: blue; }然后在
index.css文件中導入 /* index.css */ .red {color: red; }.header {font-size: 32px; }.title {color: green;composes: blue from './color.css';composes: red;border-bottom: 1px solid #ccc;padding-bottom: 20px; }最終我們會發現文字的顏色為綠色,可見自身模塊聲明優先級最高,如果把自身申明的
color去掉,那么自身引入和從其他文件引入的相同申明又該如何顯示呢?答案是自身引入的聲明的優先級會比較高。
總結
至此,所有的CSS Modules用法就已經介紹完畢了,至于后續的還有如何應用于
React、 Vue以及 Angular中,相信掌握了上面的內容之后就可以知道怎么寫了,如何與預處理器一起使用相信問題也不大。最后,本文代碼倉庫庫為:https://github.com/Rynxiao/css-modules-demo
參考鏈接
- CSS Modules?—?Solving the challenges of CSS at scale
- github repo
- What are CSS Modules and why do we need them?
- Getting Started with CSS Modules
- Get BEM
- CSS Modules 用法教程
- CSS Modules使用詳解
- 探究 CSS 解析原理
本文轉載于:猿2048https://www.mk2048.com/blog/blog.php?id=hhbbkaa&title=CSS Modules入門教程
總結
以上是生活随笔為你收集整理的CSS Modules入门教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PNG,GIF,JPG的区别及如何选
- 下一篇: css3总结之居中