前段時間在公司專案中推進全域性換膚之後,發現有個後遺症。專案中存在大量寫死的顏色值
,導致部分場景無法達到動態換膚的效果,需要把這些顏色值全部找出來並替換成變數。再加上公司正在風風火火的實行UI規範大一統,對這個問題有迫切的解決需求。然而公司中現存專案數量眾多,在上萬個檔案
中找出所有寫死的顏色值,無異於大海撈針。且在各業務線緊張的迭代中,還要花費大量的人力去查詢替換這些色值明顯是不太現實的
通過工具化的方式,一鍵將專案中寫死的顏色值全部替換成相應的變數(less
變數或者 css3
變數),前提條件是公司必須有一套標準的配色表
首先,需要將 UI 設計師提供的配色錶轉成對應的變數(本篇文章以 less
為例,其他前處理器同理)。例如設計師提供的配色表,如下
色號 | 色值 |
---|---|
$ \color{#e6f7ff}{1號藍} $ | #e6f7ff |
$ \color{#bae7ff}{2號藍} $ | #bae7ff |
$ \color{#91d5ff}{3號藍} $ | #91d5ff |
$ \color{#69c0ff}{4號藍} $ | #69c0ff |
$ \color{#40a9ff}{5號藍} $ | #40a9ff |
$ \color{#1890ff}{6號藍} $ | #1890ff |
$ \color{#096dd9}{7號藍} $ | #096dd9 |
$ \color{#0050b3}{8號藍} $ | #0050b3 |
$ \color{#003a8c}{9號藍} $ | #003a8c |
$ \color{#002766}{10號藍} $ | #002766 |
轉換成 less
變數之後,形式如下,姑且把這個檔案命名為:color-table.less
@blue-1: #e6f7ff;
@blue-2: #bae7ff;
@blue-3: #91d5ff;
@blue-4: #69c0ff;
@blue-5: #40a9ff;
@blue-6: #1890ff;
@blue-7: #096dd9;
@blue-8: #0050b3;
@blue-9: #003a8c;
@blue-10: #002766;
前面準備工作做好之後,接下來就是怎麼把顏色找出來了。嗯?那麼多檔案怎麼找?很容易想到,可以通過 fs.readdir
、fs.readFile
配合正規表示式進行查詢匹配,進而可以得出以下表示式(可精確匹配 rgba
與 hex
格式的顏色值,暫不考慮英文字面量顏色與hsl格式
,無他,專案中基本不用)
/(#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3}))|([rR][gG][Bb][Aa]?[\(]([\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),){2}[\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?[\s]*(0\.\d{1,2}|1|0)?[\)]{1})/g
這麼長一串看著暈嗎?我也是這種感覺!那咱們把它視覺化一下,最後長這樣:
666,瞬間清晰多了
正規表示式有了之後,需要把它利用起來才能找到顏色值,這一塊邏輯的實現程式碼如下:
// 匹配 rgba + hex 格式顏色值正則
const HEX_AND_RGB_COLOR_REG =
/(#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3}))|([rR][gG][Bb][Aa]?[\(]([\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),){2}[\s]*(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?[\s]*(0?\.\d{1,2}|1|0)?[\)]{1})/g;
// 讀取檔案
const readFile = (filePath) => {
fs.readFile(filePath, function (err, data) {
if (err) {
return err;
}
let str = data.toString();
// 匹配出來的顏色值
const matchColor = str.match(HEX_AND_RGB_COLOR_REG);
console.log(matchColor);
});
};
// 讀取資料夾
const readDir = (filePath) => {
//遍歷目標資料夾
fs.readdir(filePath + "", function (err, files) {
if (err) {
console.log(err);
return err;
}
if (files.length !== 0) {
files.forEach((item) => {
const fileP = path.resolve(filePath, item);
//判斷檔案的狀態,用於區分檔名/資料夾
fs.stat(fileP, function (err, status) {
if (err) {
return err;
}
const isFile = status.isFile(); //是檔案
const isDir = status.isDirectory(); //是資料夾
if (isFile) {
readFile(fileP);
}
if (isDir) {
readDir(fileP);
}
});
});
}
});
}
配色表有了,專案中的顏色也找出來了。接下來到了最關鍵的一步:把顏色之間的相似度計算出來。也就是說需要知道兩個顏色之間的「距離」才能知道從專案中找出來的這個顏色值需要被替換成配色表中的哪個變數
在這之前需要把配色表檔案 color-table.less
處理一下,使之成為 key-vlaue
的物件形式,方便後續的匹配操作
// 讀取配色表中的內容
const colorTableStr = fs.readFileSync("./color-table.less", "utf-8");
// 去除行末的分號
const colorTableStrNext = colorTableStr.replace(/;/g, "");
// 將每一行資料裝進陣列,形如:['@blue-1: #e6f7ff', '@blue-2: #bae7ff']
const colorTableArr = colorTableStrNext.split("\n").filter((item) => !!item);
// 配色表物件,形如:{'#e6f7ff': '@blue-1', '#bae7ff': '@blue-2'}
const colorVarObj = {};
colorTableArr.forEach((item) => {
item = item.toString();
if (item) {
const [key, value] = item.split(":");
// 去除空格
const val = value.replace(/\s/g, "");
colorVarObj[val] = key;
}
});
經過上述處理之後的配色表物件 colorVarObj
格式為:{'#e6f7ff': '@blue-1', '#bae7ff': '@blue-2'}
既然是計算,那肯定得有一套計算邏輯,首先想到的計算方法是:
RGB
格式;這種計算方法對於大部分顏色來講是沒問題的,但是對於某些顏色就行不通了,假設有下面這一組顏色
$ \color{#969696}{color1: rgb(150,150,150)} $
$ \color{#787878}{color2: rgb(120,120,120)} $
$ \color{#AA8282}{color3: rgb(170,130,130)} $
從計算結果來看,color3
與 color1
的色差 34.64
,明顯小於 color2
與 color1
的色差 51.96
。但是從視覺效果上來看,color2
比 color3
更接近 color1
,所以這個方法行不通
經過查閱相關資料,找到了一個相對比較完美的計算方案:
LAB
格式CIEDE2000
公式 $ \Delta E_{00}^* = \sqrt{ (\frac{\Delta L\prime}{K_LS_L})2 + (\frac{\Delta C\prime}{K_CS_C})2 + (\frac{\Delta H\prime}{K_HS_H})2 + R_T\frac{\Delta C^\prime}{K_CS_C}\frac{\Delta H^\prime}{K_HS_H} } $ 來計算,這個方案用 js
程式碼實現之後,跟 Photoshop
的色差計算結果相差無幾,基本上可以說是一模一樣。上述公式中各字母代表的含義及計算方法:
接下來,咱們來稍微實現一下這個公式。。。可惜啊,已經有大佬實現了這個計算公式,秉著不重複造輪子觀念,就直接用這個庫吧(其實就是能力不行,實現不了