styimat 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +317 -0
- package/convert-styimat.js +89 -0
- package/package.json +40 -0
- package/styimat.js +1166 -0
- package/styimat.min.js +1 -0
package/styimat.js
ADDED
|
@@ -0,0 +1,1166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS 变量预处理库 - 增强版
|
|
3
|
+
* 支持 CSS 变量预处理、嵌套选择器和嵌套变量
|
|
4
|
+
* 支持 Lab 和 LCH 颜色空间转换为 RGB(CSS标准格式)
|
|
5
|
+
* 支持 lab# 和 lch# 十六进制语法
|
|
6
|
+
* 支持 Display P3 广色域显示器
|
|
7
|
+
*/
|
|
8
|
+
const styimat = (function() {
|
|
9
|
+
// 默认配置
|
|
10
|
+
let defaultConfig = {
|
|
11
|
+
rootSelector: ':root',
|
|
12
|
+
variablePrefix: '--',
|
|
13
|
+
preserveOriginal: false,
|
|
14
|
+
indentSize: 2,
|
|
15
|
+
enableNesting: true,
|
|
16
|
+
autoProcessStyleTags: true,
|
|
17
|
+
styleTagAttribute: 'e',
|
|
18
|
+
convertLabToRGB: true, // 是否将lab颜色转换为rgb
|
|
19
|
+
convertLchToRGB: true, // 是否将lch颜色转换为rgb
|
|
20
|
+
enableP3: true, // 是否启用P3广色域支持
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 全局P3支持检测结果
|
|
24
|
+
let p3Supported = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 检测浏览器是否支持 Display P3
|
|
28
|
+
* @returns {boolean} 是否支持P3
|
|
29
|
+
*/
|
|
30
|
+
function detectP3Support() {
|
|
31
|
+
if (p3Supported !== null) return p3Supported;
|
|
32
|
+
|
|
33
|
+
if (typeof window === 'undefined' || !window.CSS) {
|
|
34
|
+
p3Supported = false;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
p3Supported = CSS.supports('color', 'color(display-p3 1 0 0)');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn('P3支持检测失败:', error);
|
|
42
|
+
p3Supported = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return p3Supported;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 解析一行CSS(可能多个语句在同一行,可能最后一个语句没有分号)
|
|
50
|
+
* 返回格式:[{属性名:值}, {属性名:值}, ...]
|
|
51
|
+
* @param {string} cssString - 要解析的CSS字符串
|
|
52
|
+
* @returns {Array} 包含属性名值对象的数组
|
|
53
|
+
*/
|
|
54
|
+
function parseSingleLineCSS(cssString) {
|
|
55
|
+
// 移除首尾空白字符
|
|
56
|
+
let str = cssString.trim();
|
|
57
|
+
|
|
58
|
+
// 如果以分号结尾,移除最后一个分号
|
|
59
|
+
if (str.endsWith(';')) {
|
|
60
|
+
str = str.slice(0, -1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 如果字符串为空,返回空数组
|
|
64
|
+
if (!str) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 分割多个CSS声明(以分号分隔)
|
|
69
|
+
const declarations = [];
|
|
70
|
+
let currentDeclaration = '';
|
|
71
|
+
let inParens = 0; // 记录括号嵌套层数,用于处理函数内的分号
|
|
72
|
+
let inQuotes = false; // 是否在引号内
|
|
73
|
+
let quoteChar = ''; // 当前引号字符
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < str.length; i++) {
|
|
76
|
+
const char = str[i];
|
|
77
|
+
|
|
78
|
+
// 处理引号
|
|
79
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
80
|
+
inQuotes = true;
|
|
81
|
+
quoteChar = char;
|
|
82
|
+
} else if (char === quoteChar && inQuotes) {
|
|
83
|
+
inQuotes = false;
|
|
84
|
+
quoteChar = '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 处理括号(不在引号内时)
|
|
88
|
+
if (!inQuotes) {
|
|
89
|
+
if (char === '(') {
|
|
90
|
+
inParens++;
|
|
91
|
+
} else if (char === ')') {
|
|
92
|
+
inParens--;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 如果遇到分号且不在括号和引号内,则分割声明
|
|
97
|
+
if (char === ';' && !inQuotes && inParens === 0) {
|
|
98
|
+
if (currentDeclaration.trim()) {
|
|
99
|
+
declarations.push(currentDeclaration.trim());
|
|
100
|
+
currentDeclaration = '';
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
currentDeclaration += char;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 添加最后一个声明(如果没有分号结尾)
|
|
108
|
+
if (currentDeclaration.trim()) {
|
|
109
|
+
declarations.push(currentDeclaration.trim());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 解析每个声明为对象
|
|
113
|
+
const result = [];
|
|
114
|
+
|
|
115
|
+
for (const declaration of declarations) {
|
|
116
|
+
// 跳过空声明
|
|
117
|
+
if (!declaration.trim()) continue;
|
|
118
|
+
|
|
119
|
+
// 查找第一个冒号的位置
|
|
120
|
+
const colonIndex = findFirstColonOutsideQuotes(declaration);
|
|
121
|
+
|
|
122
|
+
if (colonIndex === -1) {
|
|
123
|
+
console.warn(`无效的CSS声明: "${declaration}"`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 分割属性名和值
|
|
128
|
+
const property = declaration.substring(0, colonIndex).trim();
|
|
129
|
+
let value = declaration.substring(colonIndex + 1).trim();
|
|
130
|
+
|
|
131
|
+
// 如果值以分号结尾,移除它(理论上不应该有,但处理一下)
|
|
132
|
+
if (value.endsWith(';')) {
|
|
133
|
+
value = value.slice(0, -1).trim();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 添加到结果数组
|
|
137
|
+
result.push({ [property]: value });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 查找不在引号内的第一个冒号位置
|
|
145
|
+
* @param {string} str
|
|
146
|
+
* @returns {number}
|
|
147
|
+
*/
|
|
148
|
+
function findFirstColonOutsideQuotes(str) {
|
|
149
|
+
let inQuotes = false;
|
|
150
|
+
let quoteChar = '';
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < str.length; i++) {
|
|
153
|
+
const char = str[i];
|
|
154
|
+
|
|
155
|
+
// 处理引号
|
|
156
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
157
|
+
inQuotes = true;
|
|
158
|
+
quoteChar = char;
|
|
159
|
+
} else if (char === quoteChar && inQuotes) {
|
|
160
|
+
inQuotes = false;
|
|
161
|
+
quoteChar = '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 如果找到冒号且不在引号内,返回位置
|
|
165
|
+
if (char === ':' && !inQuotes) {
|
|
166
|
+
return i;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return -1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
|
|
175
|
+
* @param {string} cssValue - CSS属性值
|
|
176
|
+
* @param {Object} config - 配置对象
|
|
177
|
+
* @returns {string} 转换后的CSS值
|
|
178
|
+
*/
|
|
179
|
+
function convertAllLabLchColors(cssValue, config) {
|
|
180
|
+
if (!config.convertLabToRGB && !config.convertLchToRGB) {
|
|
181
|
+
return cssValue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let result = cssValue;
|
|
185
|
+
|
|
186
|
+
// 首先处理特殊的十六进制格式
|
|
187
|
+
result = parseHexColorFormats(result, config);
|
|
188
|
+
|
|
189
|
+
// 一次性处理所有的 lab() 和 lch() 函数
|
|
190
|
+
const colorFunctionRegex = /(lab|lch)\([^)]+\)/gi;
|
|
191
|
+
|
|
192
|
+
// 存储已经处理过的颜色字符串,避免重复处理
|
|
193
|
+
const processedColors = new Map();
|
|
194
|
+
|
|
195
|
+
// 使用一个函数来递归处理嵌套的颜色函数
|
|
196
|
+
const processColorFunctions = (str) => {
|
|
197
|
+
return str.replace(colorFunctionRegex, (match) => {
|
|
198
|
+
// 如果这个颜色已经处理过,直接返回缓存结果
|
|
199
|
+
if (processedColors.has(match)) {
|
|
200
|
+
return processedColors.get(match);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let converted = match;
|
|
204
|
+
|
|
205
|
+
// 根据函数类型处理
|
|
206
|
+
if (match.toLowerCase().startsWith('lab(')) {
|
|
207
|
+
if (config.convertLabToRGB) {
|
|
208
|
+
converted = convertSingleLabColor(match, config);
|
|
209
|
+
}
|
|
210
|
+
} else if (match.toLowerCase().startsWith('lch(')) {
|
|
211
|
+
if (config.convertLchToRGB) {
|
|
212
|
+
converted = convertSingleLchColor(match, config);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 缓存结果
|
|
217
|
+
processedColors.set(match, converted);
|
|
218
|
+
return converted;
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// 递归处理,直到没有更多颜色函数
|
|
223
|
+
let lastResult;
|
|
224
|
+
do {
|
|
225
|
+
lastResult = result;
|
|
226
|
+
result = processColorFunctions(result);
|
|
227
|
+
} while (result !== lastResult);
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 解析十六进制颜色格式:lab#L16A16B16 或 lch#L16C16H
|
|
234
|
+
* @param {string} value - 颜色值
|
|
235
|
+
* @param {Object} config - 配置对象
|
|
236
|
+
* @returns {string} 转换后的CSS颜色
|
|
237
|
+
*/
|
|
238
|
+
function parseHexColorFormats(value, config) {
|
|
239
|
+
let result = value;
|
|
240
|
+
|
|
241
|
+
// 使用正则表达式一次性匹配所有 lab# 和 lch# 格式
|
|
242
|
+
const hexLabRegex = /lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;
|
|
243
|
+
const hexLchRegex = /lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
|
|
244
|
+
|
|
245
|
+
// 存储处理过的十六进制颜色,避免重复处理
|
|
246
|
+
const processedHexColors = new Map();
|
|
247
|
+
|
|
248
|
+
// 处理 lab# 格式
|
|
249
|
+
result = result.replace(hexLabRegex, (match, L_hex, A_hex, B_hex) => {
|
|
250
|
+
if (processedHexColors.has(match)) {
|
|
251
|
+
return processedHexColors.get(match);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// 将十六进制转换为十进制,并映射到Lab范围
|
|
256
|
+
const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
|
|
257
|
+
const A = (parseInt(A_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
258
|
+
const B = (parseInt(B_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
259
|
+
|
|
260
|
+
const converted = generateColorString(L, A, B, config);
|
|
261
|
+
processedHexColors.set(match, converted);
|
|
262
|
+
return converted;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.warn(`无法解析lab#十六进制颜色: ${match}`, error);
|
|
265
|
+
return match;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// 处理 lch# 格式
|
|
270
|
+
result = result.replace(hexLchRegex, (match, L_hex, C_hex, H_dec) => {
|
|
271
|
+
if (processedHexColors.has(match)) {
|
|
272
|
+
return processedHexColors.get(match);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
// 将十六进制转换为十进制,并映射到LCH范围
|
|
277
|
+
const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
|
|
278
|
+
const C = parseInt(C_hex, 16) / 255 * 150; // 0-150
|
|
279
|
+
const H = parseInt(H_dec) / 100 * 360; // 0-360
|
|
280
|
+
|
|
281
|
+
const lab = lchToLab(L, C, H);
|
|
282
|
+
const converted = generateColorString(lab.L, lab.a, lab.b, config);
|
|
283
|
+
processedHexColors.set(match, converted);
|
|
284
|
+
return converted;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.warn(`无法解析lch#十六进制颜色: ${match}`, error);
|
|
287
|
+
return match;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 转换单个 lab() 颜色函数
|
|
296
|
+
* @param {string} labString - lab() 颜色字符串
|
|
297
|
+
* @param {Object} config - 配置对象
|
|
298
|
+
* @returns {string} 转换后的颜色字符串
|
|
299
|
+
*/
|
|
300
|
+
function convertSingleLabColor(labString, config) {
|
|
301
|
+
// 使用更精确的正则匹配 lab() 函数
|
|
302
|
+
const labRegex = /lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
303
|
+
const match = labString.match(labRegex);
|
|
304
|
+
|
|
305
|
+
if (!match) {
|
|
306
|
+
return labString;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
// 转换为数字
|
|
311
|
+
let L = parseFloat(match[1]);
|
|
312
|
+
|
|
313
|
+
// 如果L有百分号,转换为0-100范围的值
|
|
314
|
+
if (match[2] === '%') {
|
|
315
|
+
L = L; // 百分比值直接使用 (0-100)
|
|
316
|
+
} else {
|
|
317
|
+
// 没有百分号,确保在0-100范围内
|
|
318
|
+
if (L < 0) L = 0;
|
|
319
|
+
if (L > 100) L = 100;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const A = parseFloat(match[3]);
|
|
323
|
+
const B = parseFloat(match[4]);
|
|
324
|
+
|
|
325
|
+
// 处理 alpha 通道(如果有)
|
|
326
|
+
const alphaValue = match[5] !== undefined
|
|
327
|
+
? match[5].includes('%')
|
|
328
|
+
? parseFloat(match[5]) / 100
|
|
329
|
+
: parseFloat(match[5])
|
|
330
|
+
: null;
|
|
331
|
+
|
|
332
|
+
return generateColorString(L, A, B, config, alphaValue);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.warn(`无法转换LAB颜色: ${labString}`, error);
|
|
335
|
+
return labString;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 转换单个 lch() 颜色函数
|
|
341
|
+
* @param {string} lchString - lch() 颜色字符串
|
|
342
|
+
* @param {Object} config - 配置对象
|
|
343
|
+
* @returns {string} 转换后的颜色字符串
|
|
344
|
+
*/
|
|
345
|
+
function convertSingleLchColor(lchString, config) {
|
|
346
|
+
// 使用更精确的正则匹配 lch() 函数
|
|
347
|
+
const lchRegex = /lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
348
|
+
const match = lchString.match(lchRegex);
|
|
349
|
+
|
|
350
|
+
if (!match) {
|
|
351
|
+
return lchString;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
// 转换为数字
|
|
356
|
+
let L = parseFloat(match[1]);
|
|
357
|
+
|
|
358
|
+
// 如果L有百分号,转换为0-100范围的值
|
|
359
|
+
if (match[2] === '%') {
|
|
360
|
+
L = L; // 百分比值直接使用 (0-100)
|
|
361
|
+
} else {
|
|
362
|
+
// 没有百分号,确保在0-100范围内
|
|
363
|
+
if (L < 0) L = 0;
|
|
364
|
+
if (L > 100) L = 100;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const C = parseFloat(match[3]);
|
|
368
|
+
let H = parseFloat(match[4]);
|
|
369
|
+
|
|
370
|
+
// 验证C范围:>= 0
|
|
371
|
+
if (C < 0) {
|
|
372
|
+
console.warn(`LCH中的C值不能为负: ${C}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// 验证H范围:0-360,有deg单位或无单位都是0-360
|
|
376
|
+
H = ((H % 360) + 360) % 360; // 标准化到0-360
|
|
377
|
+
|
|
378
|
+
// LCH -> Lab
|
|
379
|
+
const lab = lchToLab(L, C, H);
|
|
380
|
+
|
|
381
|
+
// 处理 alpha 通道(如果有)
|
|
382
|
+
const alphaValue = match[6] !== undefined
|
|
383
|
+
? match[6].includes('%')
|
|
384
|
+
? parseFloat(match[6]) / 100
|
|
385
|
+
: parseFloat(match[6])
|
|
386
|
+
: null;
|
|
387
|
+
|
|
388
|
+
return generateColorString(lab.L, lab.a, lab.b, config, alphaValue);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.warn(`无法转换LCH颜色: ${lchString}`, error);
|
|
391
|
+
return lchString;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* LCH 转换为 Lab (CSS标准)
|
|
397
|
+
* @param {number} L - 明度 0-100 或 0%-100%
|
|
398
|
+
* @param {number} C - 色度 >=0
|
|
399
|
+
* @param {number} H - 色相角 0-360 度
|
|
400
|
+
* @returns {Object} Lab 值 {L, a, b}
|
|
401
|
+
*/
|
|
402
|
+
function lchToLab(L, C, H) {
|
|
403
|
+
// 角度转换为弧度
|
|
404
|
+
const H_rad = H * Math.PI / 180;
|
|
405
|
+
|
|
406
|
+
// LCH -> Lab 转换公式
|
|
407
|
+
const a = C * Math.cos(H_rad);
|
|
408
|
+
const b = C * Math.sin(H_rad);
|
|
409
|
+
|
|
410
|
+
return { L, a, b };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 精确的 Lab -> sRGB 转换函数
|
|
414
|
+
function preciseLabToRGB(L, a, b) {
|
|
415
|
+
// 1. Lab -> XYZ (D65白点)
|
|
416
|
+
const labToXyz = (L, a, b) => {
|
|
417
|
+
// D65 白点参考值
|
|
418
|
+
const refX = 0.95047;
|
|
419
|
+
const refY = 1.00000;
|
|
420
|
+
const refZ = 1.08883;
|
|
421
|
+
|
|
422
|
+
const epsilon = 0.008856;
|
|
423
|
+
const kappa = 903.3;
|
|
424
|
+
|
|
425
|
+
const fy = (L + 16) / 116;
|
|
426
|
+
const fx = a / 500 + fy;
|
|
427
|
+
const fz = fy - b / 200;
|
|
428
|
+
|
|
429
|
+
const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
|
|
430
|
+
const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
|
|
431
|
+
const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
|
|
432
|
+
|
|
433
|
+
return [
|
|
434
|
+
xr * refX,
|
|
435
|
+
yr * refY,
|
|
436
|
+
zr * refZ
|
|
437
|
+
];
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// 2. XYZ -> Linear RGB
|
|
441
|
+
const xyzToLinearRgb = (x, y, z) => {
|
|
442
|
+
// sRGB 转换矩阵 (D65)
|
|
443
|
+
const M = [
|
|
444
|
+
[ 3.2404542, -1.5371385, -0.4985314],
|
|
445
|
+
[-0.9692660, 1.8760108, 0.0415560],
|
|
446
|
+
[ 0.0556434, -0.2040259, 1.0572252]
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
|
|
450
|
+
const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
|
|
451
|
+
const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
|
|
452
|
+
|
|
453
|
+
return [r, g, b];
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// 3. sRGB Gamma 校正
|
|
457
|
+
const applyGamma = (c) => {
|
|
458
|
+
const sign = c < 0 ? -1 : 1;
|
|
459
|
+
const absC = Math.abs(c);
|
|
460
|
+
|
|
461
|
+
if (absC <= 0.0031308) {
|
|
462
|
+
return sign * 12.92 * absC;
|
|
463
|
+
} else {
|
|
464
|
+
return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// 4. 钳制到 [0, 255]
|
|
469
|
+
const clamp = (value) => {
|
|
470
|
+
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// 执行转换
|
|
474
|
+
const [X, Y, Z] = labToXyz(L, a, b);
|
|
475
|
+
const [linearR, linearG, linearB] = xyzToLinearRgb(X, Y, Z);
|
|
476
|
+
|
|
477
|
+
const r = applyGamma(linearR);
|
|
478
|
+
const g = applyGamma(linearG);
|
|
479
|
+
const bOut = applyGamma(linearB);
|
|
480
|
+
|
|
481
|
+
// 返回钳制后的 RGB 值
|
|
482
|
+
return {
|
|
483
|
+
r: clamp(r),
|
|
484
|
+
g: clamp(g),
|
|
485
|
+
b: clamp(bOut)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Lab -> Display P3 转换
|
|
491
|
+
* @param {number} L - 明度 0-100
|
|
492
|
+
* @param {number} a - a分量
|
|
493
|
+
* @param {number} b - b分量
|
|
494
|
+
* @returns {Object} P3颜色 {r, g, b}
|
|
495
|
+
*/
|
|
496
|
+
function labToP3(L, a, b) {
|
|
497
|
+
// 1. Lab -> XYZ (使用与RGB相同的转换)
|
|
498
|
+
const labToXyz = (L, a, b) => {
|
|
499
|
+
const refX = 0.95047;
|
|
500
|
+
const refY = 1.00000;
|
|
501
|
+
const refZ = 1.08883;
|
|
502
|
+
|
|
503
|
+
const epsilon = 0.008856;
|
|
504
|
+
const kappa = 903.3;
|
|
505
|
+
|
|
506
|
+
const fy = (L + 16) / 116;
|
|
507
|
+
const fx = a / 500 + fy;
|
|
508
|
+
const fz = fy - b / 200;
|
|
509
|
+
|
|
510
|
+
const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
|
|
511
|
+
const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
|
|
512
|
+
const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
|
|
513
|
+
|
|
514
|
+
return [
|
|
515
|
+
xr * refX,
|
|
516
|
+
yr * refY,
|
|
517
|
+
zr * refZ
|
|
518
|
+
];
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// 2. XYZ -> Linear P3
|
|
522
|
+
const xyzToLinearP3 = (x, y, z) => {
|
|
523
|
+
// Display P3 转换矩阵 (D65)
|
|
524
|
+
const M = [
|
|
525
|
+
[ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],
|
|
526
|
+
[-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
|
|
527
|
+
[ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
|
|
531
|
+
const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
|
|
532
|
+
const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
|
|
533
|
+
|
|
534
|
+
return [r, g, b];
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// 3. P3 Gamma 校正(与sRGB相同)
|
|
538
|
+
const applyGamma = (c) => {
|
|
539
|
+
const sign = c < 0 ? -1 : 1;
|
|
540
|
+
const absC = Math.abs(c);
|
|
541
|
+
|
|
542
|
+
if (absC <= 0.0031308) {
|
|
543
|
+
return sign * 12.92 * absC;
|
|
544
|
+
} else {
|
|
545
|
+
return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
|
|
550
|
+
const clamp = (value) => {
|
|
551
|
+
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// 执行转换
|
|
555
|
+
try {
|
|
556
|
+
const [X, Y, Z] = labToXyz(L, a, b);
|
|
557
|
+
const [linearR, linearG, linearB] = xyzToLinearP3(X, Y, Z);
|
|
558
|
+
|
|
559
|
+
const r = applyGamma(linearR);
|
|
560
|
+
const g = applyGamma(linearG);
|
|
561
|
+
const bOut = applyGamma(linearB);
|
|
562
|
+
|
|
563
|
+
// 转换为0-1范围的浮点数用于P3颜色格式
|
|
564
|
+
return {
|
|
565
|
+
r: Math.max(0, Math.min(1, r)),
|
|
566
|
+
g: Math.max(0, Math.min(1, g)),
|
|
567
|
+
b: Math.max(0, Math.min(1, bOut))
|
|
568
|
+
};
|
|
569
|
+
} catch (error) {
|
|
570
|
+
console.warn('P3转换失败:', error);
|
|
571
|
+
const rgb = preciseLabToRGB(L, a, b);
|
|
572
|
+
return {
|
|
573
|
+
r: rgb.r / 255,
|
|
574
|
+
g: rgb.g / 255,
|
|
575
|
+
b: rgb.b / 255
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* 生成颜色字符串(根据P3支持情况输出P3或RGB)
|
|
582
|
+
* @param {number} L - Lab L值
|
|
583
|
+
* @param {number} a - Lab a值
|
|
584
|
+
* @param {number} b - Lab b值
|
|
585
|
+
* @param {Object} config - 配置对象
|
|
586
|
+
* @param {number|null} alpha - 透明度值
|
|
587
|
+
* @returns {string} CSS颜色字符串
|
|
588
|
+
*/
|
|
589
|
+
function generateColorString(L, a, b, config, alpha = null) {
|
|
590
|
+
if (!config.enableP3 || !detectP3Support()) {
|
|
591
|
+
// 使用RGB
|
|
592
|
+
const rgb = preciseLabToRGB(L, a, b);
|
|
593
|
+
if (alpha !== null) {
|
|
594
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
|
|
595
|
+
}
|
|
596
|
+
return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
597
|
+
} else {
|
|
598
|
+
// 使用P3
|
|
599
|
+
const p3 = labToP3(L, a, b);
|
|
600
|
+
if (alpha !== null) {
|
|
601
|
+
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`;
|
|
602
|
+
}
|
|
603
|
+
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)})`;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* 处理CSS值,根据配置转换LAB和LCH颜色
|
|
609
|
+
* @param {string} value - CSS属性值
|
|
610
|
+
* @param {Object} config - 配置对象
|
|
611
|
+
* @returns {string} 处理后的值
|
|
612
|
+
*/
|
|
613
|
+
function processCSSValue(value, config) {
|
|
614
|
+
// 直接调用全局转换函数
|
|
615
|
+
return convertAllLabLchColors(value, config);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// 私有方法:提取变量定义并移除(支持嵌套作用域)
|
|
619
|
+
function extractVariablesAndCSS(cssText, config) {
|
|
620
|
+
const lines = cssText.split('\n');
|
|
621
|
+
const globalVariables = {};
|
|
622
|
+
const selectorVariables = new Map(); // 选择器 -> 变量映射
|
|
623
|
+
let cssWithoutVars = '';
|
|
624
|
+
let currentSelector = null;
|
|
625
|
+
let inSelectorBlock = false;
|
|
626
|
+
let currentIndent = 0;
|
|
627
|
+
|
|
628
|
+
for (let line of lines) {
|
|
629
|
+
const trimmed = line.trim();
|
|
630
|
+
|
|
631
|
+
// 检查是否是变量定义
|
|
632
|
+
const varMatch = trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);
|
|
633
|
+
|
|
634
|
+
if (varMatch) {
|
|
635
|
+
const [, varName, varValue] = varMatch;
|
|
636
|
+
// 处理变量值中的LAB/LCH颜色转换
|
|
637
|
+
const processedValue = convertAllLabLchColors(
|
|
638
|
+
replaceVariableUsesInValue(varValue.trim(), {
|
|
639
|
+
...globalVariables,
|
|
640
|
+
...(currentSelector ? selectorVariables.get(currentSelector) || {} : {})
|
|
641
|
+
}),
|
|
642
|
+
config
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
if (currentSelector) {
|
|
646
|
+
// 选择器内部的变量
|
|
647
|
+
if (!selectorVariables.has(currentSelector)) {
|
|
648
|
+
selectorVariables.set(currentSelector, {});
|
|
649
|
+
}
|
|
650
|
+
selectorVariables.get(currentSelector)[varName] = processedValue;
|
|
651
|
+
} else {
|
|
652
|
+
// 全局变量(:root 中)
|
|
653
|
+
globalVariables[varName] = processedValue;
|
|
654
|
+
}
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// 检查是否是选择器开始
|
|
659
|
+
if (trimmed.endsWith('{')) {
|
|
660
|
+
currentSelector = trimmed.slice(0, -1).trim();
|
|
661
|
+
inSelectorBlock = true;
|
|
662
|
+
cssWithoutVars += line + '\n';
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// 检查是否是选择器结束
|
|
667
|
+
if (trimmed === '}') {
|
|
668
|
+
if (inSelectorBlock) {
|
|
669
|
+
// 在选择器结束前插入变量声明
|
|
670
|
+
if (currentSelector && selectorVariables.has(currentSelector)) {
|
|
671
|
+
const vars = selectorVariables.get(currentSelector);
|
|
672
|
+
const indent = ' '.repeat(currentIndent);
|
|
673
|
+
for (const [varName, varValue] of Object.entries(vars)) {
|
|
674
|
+
cssWithoutVars += `${indent} --${varName}: ${varValue};\n`;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
inSelectorBlock = false;
|
|
679
|
+
currentSelector = null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
cssWithoutVars += line + '\n';
|
|
683
|
+
|
|
684
|
+
// 更新缩进级别
|
|
685
|
+
if (line.includes('{')) {
|
|
686
|
+
currentIndent += 2;
|
|
687
|
+
}
|
|
688
|
+
if (line.includes('}')) {
|
|
689
|
+
currentIndent = Math.max(0, currentIndent - 2);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
globalVariables,
|
|
695
|
+
selectorVariables,
|
|
696
|
+
cssWithoutVars: cssWithoutVars.trim()
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// 修复的嵌套解析函数
|
|
701
|
+
function parseNestedRules(cssText, config) {
|
|
702
|
+
const lines = cssText.split('\n');
|
|
703
|
+
const stack = []; // 存储规则信息:{selector, properties, children}
|
|
704
|
+
const rootRules = [];
|
|
705
|
+
let currentIndent = 0;
|
|
706
|
+
|
|
707
|
+
for (let i = 0; i < lines.length; i++) {
|
|
708
|
+
const line = lines[i];
|
|
709
|
+
const trimmed = line.trim();
|
|
710
|
+
|
|
711
|
+
if (!trimmed) continue;
|
|
712
|
+
|
|
713
|
+
// 计算缩进级别(假设使用空格缩进)
|
|
714
|
+
const indent = line.match(/^(\s*)/)[0].length;
|
|
715
|
+
const indentLevel = Math.floor(indent / config.indentSize);
|
|
716
|
+
|
|
717
|
+
// 处理规则块结束
|
|
718
|
+
if (trimmed === '}') {
|
|
719
|
+
if (stack.length > 0) {
|
|
720
|
+
const rule = stack.pop();
|
|
721
|
+
|
|
722
|
+
// 如果这个规则有父规则,添加到父规则的children中
|
|
723
|
+
if (stack.length > 0) {
|
|
724
|
+
const parent = stack[stack.length - 1];
|
|
725
|
+
if (!parent.children) parent.children = [];
|
|
726
|
+
parent.children.push(rule);
|
|
727
|
+
} else {
|
|
728
|
+
// 否则是根级规则
|
|
729
|
+
rootRules.push(rule);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 处理规则开始
|
|
736
|
+
if (trimmed.endsWith('{')) {
|
|
737
|
+
const selector = trimmed.slice(0, -1).trim();
|
|
738
|
+
|
|
739
|
+
// 创建新规则
|
|
740
|
+
const rule = {
|
|
741
|
+
selector: selector,
|
|
742
|
+
properties: [],
|
|
743
|
+
children: [],
|
|
744
|
+
indentLevel: indentLevel
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// 推入堆栈
|
|
748
|
+
stack.push(rule);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// 处理属性行(不包含 { 或 } 的行)
|
|
753
|
+
if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
|
|
754
|
+
if (stack.length > 0) {
|
|
755
|
+
const currentRule = stack[stack.length - 1];
|
|
756
|
+
// 处理属性值中的LAB/LCH颜色
|
|
757
|
+
const parsed = parseSingleLineCSS(trimmed);
|
|
758
|
+
parsed.forEach(obj => {
|
|
759
|
+
const key = Object.keys(obj)[0];
|
|
760
|
+
const value = convertAllLabLchColors(obj[key], config);
|
|
761
|
+
currentRule.properties.push(`${key}: ${value}`);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 处理栈中剩余规则
|
|
769
|
+
while (stack.length > 0) {
|
|
770
|
+
const rule = stack.pop();
|
|
771
|
+
if (stack.length === 0) {
|
|
772
|
+
rootRules.push(rule);
|
|
773
|
+
} else {
|
|
774
|
+
const parent = stack[stack.length - 1];
|
|
775
|
+
if (!parent.children) parent.children = [];
|
|
776
|
+
parent.children.push(rule);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// 将规则树转换为CSS字符串
|
|
781
|
+
return convertRulesToCSS(rootRules, config);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// 将规则树转换为CSS字符串
|
|
785
|
+
function convertRulesToCSS(rules, config, parentSelector = '') {
|
|
786
|
+
let result = '';
|
|
787
|
+
const isParentAt = parentSelector.startsWith("@");
|
|
788
|
+
|
|
789
|
+
for (const rule of rules) {
|
|
790
|
+
// 构建完整选择器
|
|
791
|
+
const isAt = rule.selector.startsWith("@");
|
|
792
|
+
let fullSelector = (isParentAt ? ' ' : '') + rule.selector;
|
|
793
|
+
|
|
794
|
+
if (isAt) {
|
|
795
|
+
fullSelector = rule.selector + " {\n";
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (parentSelector) {
|
|
799
|
+
// 处理 & 选择器
|
|
800
|
+
if (fullSelector.includes('&')) {
|
|
801
|
+
fullSelector = fullSelector.replace(/&/g, parentSelector);
|
|
802
|
+
} else if (fullSelector.trim().startsWith(':')) {
|
|
803
|
+
fullSelector = parentSelector + fullSelector;
|
|
804
|
+
} else {
|
|
805
|
+
fullSelector = parentSelector + (isParentAt ? '' : ' ') + fullSelector;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// 如果有属性,生成规则块
|
|
810
|
+
if (rule.properties.length > 0) {
|
|
811
|
+
result += (isAt ? "" : fullSelector) + ' {\n';
|
|
812
|
+
for (const prop of rule.properties) {
|
|
813
|
+
// 再次处理属性值(确保LAB/LCH颜色被转换)
|
|
814
|
+
const parsed = parseSingleLineCSS(prop);
|
|
815
|
+
parsed.forEach(obj => {
|
|
816
|
+
const key = Object.keys(obj)[0];
|
|
817
|
+
const value = convertAllLabLchColors(obj[key], config);
|
|
818
|
+
result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
result += isParentAt ? ' }\n' : '}\n\n';
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 递归处理子规则
|
|
825
|
+
if (rule.children && rule.children.length > 0) {
|
|
826
|
+
result += convertRulesToCSS(rule.children, config, fullSelector);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (isParentAt){
|
|
830
|
+
result += "}\n\n";
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return result.trim() + (isParentAt ? "\n\n" : "");
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 替换变量值中的变量引用
|
|
838
|
+
function replaceVariableUsesInValue(value, variables) {
|
|
839
|
+
return value.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
840
|
+
if (variables[varName]) {
|
|
841
|
+
return `var(--${varName})`;
|
|
842
|
+
}
|
|
843
|
+
return match;
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 替换变量使用
|
|
848
|
+
function replaceVariableUses(cssText, globalVariables, selectorVariables, config) {
|
|
849
|
+
let result = cssText;
|
|
850
|
+
|
|
851
|
+
// 先替换全局变量
|
|
852
|
+
for (const [varName, varValue] of Object.entries(globalVariables)) {
|
|
853
|
+
const varRegex = new RegExp(`\\$${varName}(?![a-zA-Z0-9_-])`, 'g');
|
|
854
|
+
result = result.replace(varRegex, `var(--${varName})`);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// 然后处理选择器特定的变量
|
|
858
|
+
result = result.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
859
|
+
return `var(--${varName})`;
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// 最后处理所有的LAB和LCH颜色(一次性全局处理)
|
|
863
|
+
if (config.convertLabToRGB || config.convertLchToRGB) {
|
|
864
|
+
result = convertAllLabLchColors(result, config);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// 生成根规则
|
|
871
|
+
function generateRootRule(variables, config) {
|
|
872
|
+
if (Object.keys(variables).length === 0) {
|
|
873
|
+
return '';
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const declarations = Object.entries(variables)
|
|
877
|
+
.map(([name, value]) => {
|
|
878
|
+
const processedValue = convertAllLabLchColors(
|
|
879
|
+
replaceVariableUsesInValue(value, variables),
|
|
880
|
+
config
|
|
881
|
+
);
|
|
882
|
+
return ` --${name}: ${processedValue};`;
|
|
883
|
+
})
|
|
884
|
+
.join('\n');
|
|
885
|
+
|
|
886
|
+
return `${config.rootSelector} {\n${declarations}\n}\n\n`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// 处理选择器内部的变量
|
|
890
|
+
function injectSelectorVariables(cssText, selectorVariables, config) {
|
|
891
|
+
let result = '';
|
|
892
|
+
const lines = cssText.split('\n');
|
|
893
|
+
let currentSelector = null;
|
|
894
|
+
|
|
895
|
+
for (let line of lines) {
|
|
896
|
+
const trimmed = line.trim();
|
|
897
|
+
|
|
898
|
+
if (trimmed.endsWith('{')) {
|
|
899
|
+
currentSelector = trimmed.slice(0, -1).trim();
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (trimmed === '}' && currentSelector) {
|
|
903
|
+
// 在选择器结束前插入变量声明
|
|
904
|
+
if (selectorVariables.has(currentSelector)) {
|
|
905
|
+
const vars = selectorVariables.get(currentSelector);
|
|
906
|
+
const indent = line.match(/^(\s*)/)[0];
|
|
907
|
+
for (const [varName, varValue] of Object.entries(vars)) {
|
|
908
|
+
result += indent + ' --' + varName + ': ' + varValue + ';\n';
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
currentSelector = null;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// 使用全局转换函数处理所有颜色
|
|
915
|
+
if (config.convertLabToRGB || config.convertLchToRGB) {
|
|
916
|
+
result += convertAllLabLchColors(line, config) + '\n';
|
|
917
|
+
} else {
|
|
918
|
+
result += line + '\n';
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return result.trim();
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 主转换函数
|
|
926
|
+
function convert(cssText, customConfig = {}) {
|
|
927
|
+
const config = { ...defaultConfig, ...customConfig };
|
|
928
|
+
|
|
929
|
+
// 1. 提取变量定义(区分全局和选择器局部)
|
|
930
|
+
const { globalVariables, selectorVariables, cssWithoutVars } =
|
|
931
|
+
extractVariablesAndCSS(cssText, config);
|
|
932
|
+
|
|
933
|
+
// 2. 解析嵌套规则(如果启用)
|
|
934
|
+
let processedCSS = cssWithoutVars.trim();
|
|
935
|
+
if (config.enableNesting && cssWithoutVars.includes('{')) {
|
|
936
|
+
try {
|
|
937
|
+
processedCSS = parseNestedRules(cssWithoutVars, config);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.warn('嵌套解析失败,使用原始CSS:', error);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// 3. 生成根规则(全局变量)
|
|
944
|
+
const rootRule = generateRootRule(globalVariables, config);
|
|
945
|
+
|
|
946
|
+
// 4. 注入选择器局部变量
|
|
947
|
+
let finalCSS = processedCSS;
|
|
948
|
+
if (selectorVariables.size > 0) {
|
|
949
|
+
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// 5. 替换变量使用
|
|
953
|
+
finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, config);
|
|
954
|
+
|
|
955
|
+
// 6. 组合结果
|
|
956
|
+
return rootRule + finalCSS;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// 自动处理带有 e 属性的 style 标签
|
|
960
|
+
function autoProcessStyleTags(customConfig = {}) {
|
|
961
|
+
const config = { ...defaultConfig, ...customConfig };
|
|
962
|
+
const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
|
|
963
|
+
|
|
964
|
+
styleTags.forEach(styleTag => {
|
|
965
|
+
const originalCSS = styleTag.textContent;
|
|
966
|
+
const convertedCSS = convert(originalCSS, config);
|
|
967
|
+
|
|
968
|
+
// 创建新的 style 标签
|
|
969
|
+
const newStyleTag = document.createElement('style');
|
|
970
|
+
newStyleTag.textContent = convertedCSS;
|
|
971
|
+
|
|
972
|
+
// 插入到原标签后面
|
|
973
|
+
styleTag.parentNode.insertBefore(newStyleTag, styleTag.nextSibling);
|
|
974
|
+
|
|
975
|
+
// 可选:移除原标签
|
|
976
|
+
if (!config.preserveOriginal) {
|
|
977
|
+
styleTag.remove();
|
|
978
|
+
} else {
|
|
979
|
+
styleTag.style.display = 'none';
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
return styleTags.length;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function config(config = {}) {
|
|
987
|
+
defaultConfig = { ...defaultConfig, ...config };
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// 初始化自动处理
|
|
991
|
+
function apply(cssText, customConfig = {}) {
|
|
992
|
+
const config = { ...defaultConfig, ...customConfig };
|
|
993
|
+
|
|
994
|
+
if (cssText) {
|
|
995
|
+
const converted = convert(cssText, config);
|
|
996
|
+
const styleEl = document.createElement('style');
|
|
997
|
+
styleEl.textContent = converted;
|
|
998
|
+
document.head.appendChild(styleEl);
|
|
999
|
+
return styleEl;
|
|
1000
|
+
} else {
|
|
1001
|
+
if (document.readyState === 'loading') {
|
|
1002
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1003
|
+
autoProcessStyleTags(config);
|
|
1004
|
+
});
|
|
1005
|
+
} else {
|
|
1006
|
+
autoProcessStyleTags(config);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// 返回公共 API
|
|
1012
|
+
const api = {
|
|
1013
|
+
convert,
|
|
1014
|
+
apply,
|
|
1015
|
+
config,
|
|
1016
|
+
version: '1.8.0',
|
|
1017
|
+
|
|
1018
|
+
// 检测P3支持
|
|
1019
|
+
supportsP3: detectP3Support(),
|
|
1020
|
+
|
|
1021
|
+
// 重新检测P3支持(如果配置变化)
|
|
1022
|
+
detectP3Support: detectP3Support,
|
|
1023
|
+
|
|
1024
|
+
// 颜色转换工具方法
|
|
1025
|
+
colorUtils: {
|
|
1026
|
+
labToRGB: preciseLabToRGB,
|
|
1027
|
+
lchToLab: lchToLab,
|
|
1028
|
+
lchToRGB: function(L, C, H) {
|
|
1029
|
+
const lab = lchToLab(L, C, H);
|
|
1030
|
+
return preciseLabToRGB(lab.L, lab.a, lab.b);
|
|
1031
|
+
},
|
|
1032
|
+
labToP3: labToP3,
|
|
1033
|
+
lchToP3: function(L, C, H) {
|
|
1034
|
+
const lab = lchToLab(L, C, H);
|
|
1035
|
+
return labToP3(lab.L, lab.a, lab.b);
|
|
1036
|
+
},
|
|
1037
|
+
parseHexLab: function(hexString) {
|
|
1038
|
+
const match = hexString.match(/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);
|
|
1039
|
+
if (!match) return null;
|
|
1040
|
+
|
|
1041
|
+
const L_hex = match[1];
|
|
1042
|
+
const A_hex = match[2];
|
|
1043
|
+
const B_hex = match[3];
|
|
1044
|
+
|
|
1045
|
+
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
1046
|
+
const A = (parseInt(A_hex, 16) - 128) * 1.5;
|
|
1047
|
+
const B = (parseInt(B_hex, 16) - 128) * 1.5;
|
|
1048
|
+
|
|
1049
|
+
return preciseLabToRGB(L, A, B);
|
|
1050
|
+
},
|
|
1051
|
+
parseHexLch: function(hexString) {
|
|
1052
|
+
const match = hexString.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);
|
|
1053
|
+
if (!match) return null;
|
|
1054
|
+
|
|
1055
|
+
const L_hex = match[1];
|
|
1056
|
+
const C_hex = match[2];
|
|
1057
|
+
const H_dec = match[3];
|
|
1058
|
+
|
|
1059
|
+
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
1060
|
+
const C = parseInt(C_hex, 16) / 255 * 150;
|
|
1061
|
+
const H = parseInt(H_dec) / 100 * 360;
|
|
1062
|
+
|
|
1063
|
+
const lab = lchToLab(L, C, H);
|
|
1064
|
+
return preciseLabToRGB(lab.L, lab.a, lab.b);
|
|
1065
|
+
},
|
|
1066
|
+
/**
|
|
1067
|
+
* 生成颜色字符串(根据P3支持情况)
|
|
1068
|
+
* @param {number} L - Lab L值
|
|
1069
|
+
* @param {number} a - Lab a值
|
|
1070
|
+
* @param {number} b - Lab b值
|
|
1071
|
+
* @param {number|null} alpha - 透明度
|
|
1072
|
+
* @param {boolean} useP3 - 是否使用P3(自动检测)
|
|
1073
|
+
* @returns {string} CSS颜色字符串
|
|
1074
|
+
*/
|
|
1075
|
+
generateColor: function(L, a, b, alpha = null, useP3 = true) {
|
|
1076
|
+
return generateColorString(L, a, b, { enableP3: useP3 }, alpha);
|
|
1077
|
+
},
|
|
1078
|
+
/**
|
|
1079
|
+
* 解析CSS颜色字符串
|
|
1080
|
+
* @param {string} colorString - CSS颜色字符串
|
|
1081
|
+
* @returns {Object} 包含原始Lab值和颜色字符串的对象
|
|
1082
|
+
*/
|
|
1083
|
+
parseColor: function(colorString) {
|
|
1084
|
+
try {
|
|
1085
|
+
// 尝试解析lab()函数格式
|
|
1086
|
+
const labMatch = colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1087
|
+
if (labMatch) {
|
|
1088
|
+
let L = parseFloat(labMatch[1]);
|
|
1089
|
+
const A = parseFloat(labMatch[3]);
|
|
1090
|
+
const B = parseFloat(labMatch[4]);
|
|
1091
|
+
const alpha = labMatch[5] ?
|
|
1092
|
+
(labMatch[5].includes('%') ? parseFloat(labMatch[5]) / 100 : parseFloat(labMatch[5])) :
|
|
1093
|
+
null;
|
|
1094
|
+
|
|
1095
|
+
const colorStr = generateColorString(L, A, B, defaultConfig, alpha);
|
|
1096
|
+
|
|
1097
|
+
return {
|
|
1098
|
+
L, A, B, alpha,
|
|
1099
|
+
rgb: preciseLabToRGB(L, A, B),
|
|
1100
|
+
p3: labToP3(L, A, B),
|
|
1101
|
+
colorString: colorStr
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// 尝试解析lch()函数格式
|
|
1106
|
+
const lchMatch = colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1107
|
+
if (lchMatch) {
|
|
1108
|
+
let L = parseFloat(lchMatch[1]);
|
|
1109
|
+
const C = parseFloat(lchMatch[3]);
|
|
1110
|
+
let H = parseFloat(lchMatch[4]);
|
|
1111
|
+
const alpha = lchMatch[6] ?
|
|
1112
|
+
(lchMatch[6].includes('%') ? parseFloat(lchMatch[6]) / 100 : parseFloat(lchMatch[6])) :
|
|
1113
|
+
null;
|
|
1114
|
+
|
|
1115
|
+
const lab = lchToLab(L, C, H);
|
|
1116
|
+
const colorStr = generateColorString(lab.L, lab.a, lab.b, defaultConfig, alpha);
|
|
1117
|
+
|
|
1118
|
+
return {
|
|
1119
|
+
L, C, H, alpha,
|
|
1120
|
+
lab: lab,
|
|
1121
|
+
rgb: preciseLabToRGB(lab.L, lab.a, lab.b),
|
|
1122
|
+
p3: labToP3(lab.L, lab.a, lab.b),
|
|
1123
|
+
colorString: colorStr
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
return null;
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
console.warn('无法解析颜色:', colorString, error);
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// 自动初始化
|
|
1137
|
+
if (typeof window !== 'undefined') {
|
|
1138
|
+
apply();
|
|
1139
|
+
Object.defineProperty(window.HTMLElement.prototype, 'cssVar', {
|
|
1140
|
+
get() {
|
|
1141
|
+
const element = this;
|
|
1142
|
+
return new Proxy({}, {
|
|
1143
|
+
get(target, prop) {
|
|
1144
|
+
const varName = prop.startsWith('--') ? prop : `--${prop}`;
|
|
1145
|
+
return element.style.getPropertyValue(varName);
|
|
1146
|
+
},
|
|
1147
|
+
|
|
1148
|
+
set(target, prop, value) {
|
|
1149
|
+
const varName = prop.startsWith('--') ? prop : `--${prop}`;
|
|
1150
|
+
element.style.setProperty(varName, value);
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
return api;
|
|
1159
|
+
})();
|
|
1160
|
+
|
|
1161
|
+
// 导出
|
|
1162
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1163
|
+
module.exports = styimat;
|
|
1164
|
+
} else {
|
|
1165
|
+
window.styimat = styimat;
|
|
1166
|
+
}
|