styimat 1.5.0 → 1.6.1
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/README.md +28 -7
- package/package.json +1 -1
- package/styimat.js +1436 -1429
- package/styimat.min.js +126 -126
package/styimat.js
CHANGED
|
@@ -11,1530 +11,1537 @@
|
|
|
11
11
|
* 增强 math() 函数,支持复杂数学计算
|
|
12
12
|
*/
|
|
13
13
|
(function(root, factory) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
if (typeof define === 'function' && define.amd) {
|
|
15
|
+
// AMD 支持 (RequireJS)
|
|
16
|
+
define([], factory);
|
|
17
|
+
} else if (typeof module === 'object' && module.exports) {
|
|
18
|
+
// CommonJS 支持 (Node.js, Browserify, Webpack)
|
|
19
|
+
module.exports = factory();
|
|
20
|
+
} else {
|
|
21
|
+
// 浏览器全局变量
|
|
22
|
+
root.styimat = factory();
|
|
23
|
+
}
|
|
24
24
|
}(typeof self !== 'undefined' ? self : this, function() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
// 默认配置
|
|
26
|
+
let defaultConfig = {
|
|
27
|
+
rootSelector: ':root',
|
|
28
|
+
variablePrefix: '--',
|
|
29
|
+
preserveOriginal: false,
|
|
30
|
+
indentSize: 2,
|
|
31
|
+
enableNesting: true,
|
|
32
|
+
autoProcessStyleTags: true,
|
|
33
|
+
styleTagAttribute: 'e',
|
|
34
|
+
convertLabToRGB: true,
|
|
35
|
+
convertLchToRGB: true,
|
|
36
|
+
enableP3: true,
|
|
37
|
+
enableMath: true, // 启用math()函数增强
|
|
38
|
+
mathPrecision: 6, // 数学计算精度
|
|
39
|
+
};
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// 全局P3支持检测结果
|
|
42
|
+
let p3Supported = null;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
/**
|
|
45
|
+
* 检测浏览器是否支持 Display P3
|
|
46
|
+
* @returns {boolean} 是否支持P3
|
|
47
|
+
*/
|
|
48
|
+
function detectP3Support() {
|
|
49
|
+
if (p3Supported !== null) return p3Supported;
|
|
50
|
+
|
|
51
|
+
if (typeof window === 'undefined' || !window.CSS) {
|
|
52
|
+
p3Supported = false;
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
p3Supported = CSS.supports('color', 'color(display-p3 1 0 0)');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn('P3支持检测失败:', error);
|
|
60
|
+
p3Supported = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return p3Supported;
|
|
61
64
|
}
|
|
62
|
-
|
|
63
|
-
return p3Supported;
|
|
64
|
-
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
66
|
+
/**
|
|
67
|
+
* 解析一行CSS(可能多个语句在同一行,可能最后一个语句没有分号)
|
|
68
|
+
* 返回格式:[{属性名:值}, {属性名:值}, ...]
|
|
69
|
+
* @param {string} cssString - 要解析的CSS字符串
|
|
70
|
+
* @returns {Array} 包含属性名值对象的数组
|
|
71
|
+
*/
|
|
72
|
+
function parseSingleLineCSS(cssString) {
|
|
73
|
+
// 移除首尾空白字符
|
|
74
|
+
let str = cssString.trim();
|
|
75
|
+
|
|
76
|
+
// 如果以分号结尾,移除最后一个分号
|
|
77
|
+
if (str.endsWith(';')) {
|
|
78
|
+
str = str.slice(0, -1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 如果字符串为空,返回空数组
|
|
82
|
+
if (!str) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 分割多个CSS声明(以分号分隔)
|
|
87
|
+
const declarations = [];
|
|
88
|
+
let currentDeclaration = '';
|
|
89
|
+
let inParens = 0; // 记录括号嵌套层数,用于处理函数内的分号
|
|
90
|
+
let inQuotes = false; // 是否在引号内
|
|
91
|
+
let quoteChar = ''; // 当前引号字符
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < str.length; i++) {
|
|
94
|
+
const char = str[i];
|
|
95
|
+
|
|
96
|
+
// 处理引号
|
|
97
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
98
|
+
inQuotes = true;
|
|
99
|
+
quoteChar = char;
|
|
100
|
+
} else if (char === quoteChar && inQuotes) {
|
|
101
|
+
inQuotes = false;
|
|
102
|
+
quoteChar = '';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 处理括号(不在引号内时)
|
|
106
|
+
if (!inQuotes) {
|
|
107
|
+
if (char === '(') {
|
|
108
|
+
inParens++;
|
|
109
|
+
} else if (char === ')') {
|
|
110
|
+
inParens--;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 如果遇到分号且不在括号和引号内,则分割声明
|
|
115
|
+
if (char === ';' && !inQuotes && inParens === 0) {
|
|
116
|
+
if (currentDeclaration.trim()) {
|
|
117
|
+
declarations.push(currentDeclaration.trim());
|
|
118
|
+
currentDeclaration = '';
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
currentDeclaration += char;
|
|
122
|
+
}
|
|
111
123
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// 如果遇到分号且不在括号和引号内,则分割声明
|
|
115
|
-
if (char === ';' && !inQuotes && inParens === 0) {
|
|
124
|
+
|
|
125
|
+
// 添加最后一个声明(如果没有分号结尾)
|
|
116
126
|
if (currentDeclaration.trim()) {
|
|
117
|
-
|
|
118
|
-
currentDeclaration = '';
|
|
127
|
+
declarations.push(currentDeclaration.trim());
|
|
119
128
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (value.endsWith(';')) {
|
|
151
|
-
value = value.slice(0, -1).trim();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 添加到结果数组
|
|
155
|
-
result.push({ [property]: value });
|
|
129
|
+
|
|
130
|
+
// 解析每个声明为对象
|
|
131
|
+
const result = [];
|
|
132
|
+
|
|
133
|
+
for (const declaration of declarations) {
|
|
134
|
+
// 跳过空声明
|
|
135
|
+
if (!declaration.trim()) continue;
|
|
136
|
+
|
|
137
|
+
// 查找第一个冒号的位置
|
|
138
|
+
const colonIndex = findFirstColonOutsideQuotes(declaration);
|
|
139
|
+
|
|
140
|
+
if (colonIndex === -1) {
|
|
141
|
+
console.warn(`无效的CSS声明: "${declaration}"`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 分割属性名和值
|
|
146
|
+
const property = declaration.substring(0, colonIndex).trim();
|
|
147
|
+
let value = declaration.substring(colonIndex + 1).trim();
|
|
148
|
+
|
|
149
|
+
// 如果值以分号结尾,移除它(理论上不应该有,但处理一下)
|
|
150
|
+
if (value.endsWith(';')) {
|
|
151
|
+
value = value.slice(0, -1).trim();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 添加到结果数组
|
|
155
|
+
result.push({ [property]: value });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result;
|
|
156
159
|
}
|
|
157
|
-
|
|
158
|
-
return result;
|
|
159
|
-
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
161
|
+
/**
|
|
162
|
+
* 查找不在引号内的第一个冒号位置
|
|
163
|
+
* @param {string} str
|
|
164
|
+
* @returns {number}
|
|
165
|
+
*/
|
|
166
|
+
function findFirstColonOutsideQuotes(str) {
|
|
167
|
+
let inQuotes = false;
|
|
168
|
+
let quoteChar = '';
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < str.length; i++) {
|
|
171
|
+
const char = str[i];
|
|
172
|
+
|
|
173
|
+
// 处理引号
|
|
174
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
175
|
+
inQuotes = true;
|
|
176
|
+
quoteChar = char;
|
|
177
|
+
} else if (char === quoteChar && inQuotes) {
|
|
178
|
+
inQuotes = false;
|
|
179
|
+
quoteChar = '';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 如果找到冒号且不在引号内,返回位置
|
|
183
|
+
if (char === ':' && !inQuotes) {
|
|
184
|
+
return i;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return -1;
|
|
186
189
|
}
|
|
187
|
-
|
|
188
|
-
return -1;
|
|
189
|
-
}
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
191
|
+
/**
|
|
192
|
+
* 增强的数学表达式解析和计算
|
|
193
|
+
* @param {string} expression - 数学表达式
|
|
194
|
+
* @param {Object} config - 配置对象
|
|
195
|
+
* @returns {string} 计算结果
|
|
196
|
+
*/
|
|
197
|
+
function evaluateMathExpression(expression, config) {
|
|
198
|
+
// 如果禁用math()增强,则返回原始表达式
|
|
199
|
+
if (!config.enableMath) {
|
|
200
|
+
return `math(${expression})`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// 清理表达式:移除空白字符
|
|
205
|
+
let cleanExpr = expression.replace(/\s+/g, '');
|
|
206
|
+
|
|
207
|
+
// 解析数学表达式
|
|
208
|
+
const result = parseMathExpression(cleanExpr, config);
|
|
209
|
+
|
|
210
|
+
// 如果结果包含单位,保留原始格式
|
|
211
|
+
if (hasUnits(cleanExpr)) {
|
|
212
|
+
// 处理带单位的表达式
|
|
213
|
+
return processUnitExpression(cleanExpr, result);
|
|
214
|
+
} else {
|
|
215
|
+
// 纯数字表达式,直接计算结果
|
|
216
|
+
return roundNumber(result, config.mathPrecision);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn('math()表达式解析失败:', expression, error);
|
|
220
|
+
return `math(${expression})`;
|
|
221
|
+
}
|
|
221
222
|
}
|
|
222
|
-
}
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
224
|
+
/**
|
|
225
|
+
* 解析数学表达式
|
|
226
|
+
* @param {string} expr - 清理后的表达式
|
|
227
|
+
* @param {Object} config - 配置对象
|
|
228
|
+
* @returns {number} 计算结果
|
|
229
|
+
*/
|
|
230
|
+
function parseMathExpression(expr, config) {
|
|
231
|
+
// 处理括号
|
|
232
|
+
while (expr.includes('(') && expr.includes(')')) {
|
|
233
|
+
const start = expr.lastIndexOf('(');
|
|
234
|
+
const end = expr.indexOf(')', start);
|
|
235
|
+
|
|
236
|
+
if (end === -1) break;
|
|
237
|
+
|
|
238
|
+
const inner = expr.substring(start + 1, end);
|
|
239
|
+
const innerResult = parseMathExpression(inner, config);
|
|
240
|
+
|
|
241
|
+
expr = expr.substring(0, start) + innerResult + expr.substring(end + 1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 现在expr应该没有括号了
|
|
245
|
+
return evaluateSimpleExpression(expr, config);
|
|
242
246
|
}
|
|
243
|
-
|
|
244
|
-
// 现在expr应该没有括号了
|
|
245
|
-
return evaluateSimpleExpression(expr, config);
|
|
246
|
-
}
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
248
|
+
/**
|
|
249
|
+
* 评估简单表达式(无括号)
|
|
250
|
+
* @param {string} expr - 简单表达式
|
|
251
|
+
* @param {Object} config - 配置对象
|
|
252
|
+
* @returns {number} 计算结果
|
|
253
|
+
*/
|
|
254
|
+
function evaluateSimpleExpression(expr, config) {
|
|
255
|
+
// 处理操作符优先级:乘除优先于加减
|
|
256
|
+
const operators = [
|
|
257
|
+
{ regex: /([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/, handler: multiply },
|
|
258
|
+
{ regex: /([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/, handler: divide },
|
|
259
|
+
{ regex: /([\d.]+(?:[a-zA-Z%]+)?)\+([\d.]+(?:[a-zA-Z%]+)?)/, handler: add },
|
|
260
|
+
{ regex: /([\d.]+(?:[a-zA-Z%]+)?)-([\d.]+(?:[a-zA-Z%]+)?)/, handler: subtract }
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
let lastExpr = expr;
|
|
264
|
+
|
|
265
|
+
// 按照优先级处理操作符
|
|
266
|
+
for (const op of operators) {
|
|
267
|
+
let match;
|
|
268
|
+
while ((match = expr.match(op.regex)) !== null) {
|
|
269
|
+
const left = parseValueWithUnit(match[1]);
|
|
270
|
+
const right = parseValueWithUnit(match[2]);
|
|
271
|
+
const result = op.handler(left, right);
|
|
272
|
+
|
|
273
|
+
// 替换匹配的部分
|
|
274
|
+
expr = expr.substring(0, match.index) +
|
|
275
|
+
result.value +
|
|
276
|
+
expr.substring(match.index + match[0].length);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 如果表达式无法进一步简化,尝试解析为数字
|
|
281
|
+
if (expr !== lastExpr) {
|
|
282
|
+
return evaluateSimpleExpression(expr, config);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 解析最终结果
|
|
286
|
+
const parsed = parseValueWithUnit(expr);
|
|
287
|
+
return parsed.value;
|
|
283
288
|
}
|
|
284
|
-
|
|
285
|
-
// 解析最终结果
|
|
286
|
-
const parsed = parseValueWithUnit(expr);
|
|
287
|
-
return parsed.value;
|
|
288
|
-
}
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
290
|
+
/**
|
|
291
|
+
* 解析带单位的数值
|
|
292
|
+
* @param {string} str - 字符串值
|
|
293
|
+
* @returns {Object} {value: number, unit: string}
|
|
294
|
+
*/
|
|
295
|
+
function parseValueWithUnit(str) {
|
|
296
|
+
// 匹配数值和单位
|
|
297
|
+
const match = str.match(/^([\d.]+)([a-zA-Z%]*)$/);
|
|
298
|
+
|
|
299
|
+
if (!match) {
|
|
300
|
+
throw new Error(`无法解析值: ${str}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const value = parseFloat(match[1]);
|
|
304
|
+
const unit = match[2] || '';
|
|
305
|
+
|
|
306
|
+
return { value, unit };
|
|
301
307
|
}
|
|
302
|
-
|
|
303
|
-
const value = parseFloat(match[1]);
|
|
304
|
-
const unit = match[2] || '';
|
|
305
|
-
|
|
306
|
-
return { value, unit };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* 检查表达式是否包含单位
|
|
311
|
-
* @param {string} expr - 表达式
|
|
312
|
-
* @returns {boolean} 是否包含单位
|
|
313
|
-
*/
|
|
314
|
-
function hasUnits(expr) {
|
|
315
|
-
return /[a-zA-Z%]/.test(expr);
|
|
316
|
-
}
|
|
317
308
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// 提取原始表达式中的单位
|
|
326
|
-
const unitMatch = originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);
|
|
327
|
-
|
|
328
|
-
if (unitMatch) {
|
|
329
|
-
return result + unitMatch[1];
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// 如果没有明确单位,检查是否为百分比
|
|
333
|
-
if (originalExpr.includes('%')) {
|
|
334
|
-
return result + '%';
|
|
309
|
+
/**
|
|
310
|
+
* 检查表达式是否包含单位
|
|
311
|
+
* @param {string} expr - 表达式
|
|
312
|
+
* @returns {boolean} 是否包含单位
|
|
313
|
+
*/
|
|
314
|
+
function hasUnits(expr) {
|
|
315
|
+
return /[a-zA-Z%]/.test(expr);
|
|
335
316
|
}
|
|
336
|
-
|
|
337
|
-
// 默认为像素
|
|
338
|
-
return result + 'px';
|
|
339
|
-
}
|
|
340
317
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
318
|
+
/**
|
|
319
|
+
* 处理带单位的表达式
|
|
320
|
+
* @param {string} originalExpr - 原始表达式
|
|
321
|
+
* @param {number} result - 计算结果
|
|
322
|
+
* @returns {string} 处理后的表达式
|
|
323
|
+
*/
|
|
324
|
+
function processUnitExpression(originalExpr, result) {
|
|
325
|
+
// 提取原始表达式中的单位
|
|
326
|
+
const unitMatch = originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);
|
|
327
|
+
|
|
328
|
+
if (unitMatch) {
|
|
329
|
+
return result + unitMatch[1];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 如果没有明确单位,检查是否为百分比
|
|
333
|
+
if (originalExpr.includes('%')) {
|
|
334
|
+
return result + '%';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 默认为像素
|
|
338
|
+
return result + 'px';
|
|
345
339
|
}
|
|
346
|
-
|
|
347
|
-
// 单位不匹配,返回原始表达式
|
|
348
|
-
return { value: `${a.value}${a.unit}*${b.value}${b.unit}`, unit: '' };
|
|
349
|
-
}
|
|
350
340
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
} else if (a.unit === b.unit) {
|
|
360
|
-
// 如 100px / 2px
|
|
361
|
-
return { value: a.value / b.value, unit: '' };
|
|
341
|
+
// 数学运算函数
|
|
342
|
+
function multiply(a, b) {
|
|
343
|
+
if (a.unit === b.unit || (!a.unit && !b.unit)) {
|
|
344
|
+
return { value: a.value * b.value, unit: a.unit || b.unit };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 单位不匹配,返回原始表达式
|
|
348
|
+
return { value: `${a.value}${a.unit}*${b.value}${b.unit}`, unit: '' };
|
|
362
349
|
}
|
|
363
|
-
|
|
364
|
-
// 单位不匹配,返回原始表达式
|
|
365
|
-
return { value: `${a.value}${a.unit}/${b.value}${b.unit}`, unit: '' };
|
|
366
|
-
}
|
|
367
350
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
351
|
+
function divide(a, b) {
|
|
352
|
+
if (b.value === 0) {
|
|
353
|
+
throw new Error('除以零');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (a.unit && !b.unit) {
|
|
357
|
+
// 如 100px / 2
|
|
358
|
+
return { value: a.value / b.value, unit: a.unit };
|
|
359
|
+
} else if (a.unit === b.unit) {
|
|
360
|
+
// 如 100px / 2px
|
|
361
|
+
return { value: a.value / b.value, unit: '' };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 单位不匹配,返回原始表达式
|
|
365
|
+
return { value: `${a.value}${a.unit}/${b.value}${b.unit}`, unit: '' };
|
|
371
366
|
}
|
|
372
|
-
|
|
373
|
-
// 单位不匹配,返回原始表达式
|
|
374
|
-
return { value: `${a.value}${a.unit}+${b.value}${b.unit}`, unit: '' };
|
|
375
|
-
}
|
|
376
367
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
368
|
+
function add(a, b) {
|
|
369
|
+
if (a.unit === b.unit) {
|
|
370
|
+
return { value: a.value + b.value, unit: a.unit };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 单位不匹配,返回原始表达式
|
|
374
|
+
return { value: `${a.value}${a.unit}+${b.value}${b.unit}`, unit: '' };
|
|
380
375
|
}
|
|
381
|
-
|
|
382
|
-
// 单位不匹配,返回原始表达式
|
|
383
|
-
return { value: `${a.value}${a.unit}-${b.value}${b.unit}`, unit: '' };
|
|
384
|
-
}
|
|
385
376
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const factor = Math.pow(10, precision);
|
|
394
|
-
const rounded = Math.round(num * factor) / factor;
|
|
395
|
-
|
|
396
|
-
// 移除不必要的尾随零
|
|
397
|
-
const str = rounded.toString();
|
|
398
|
-
if (str.includes('.')) {
|
|
399
|
-
return str.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
|
|
377
|
+
function subtract(a, b) {
|
|
378
|
+
if (a.unit === b.unit) {
|
|
379
|
+
return { value: a.value - b.value, unit: a.unit };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 单位不匹配,返回原始表达式
|
|
383
|
+
return { value: `${a.value}${a.unit}-${b.value}${b.unit}`, unit: '' };
|
|
400
384
|
}
|
|
401
|
-
return str;
|
|
402
|
-
}
|
|
403
385
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
386
|
+
/**
|
|
387
|
+
* 四舍五入到指定精度
|
|
388
|
+
* @param {number} num - 要四舍五入的数字
|
|
389
|
+
* @param {number} precision - 精度
|
|
390
|
+
* @returns {string} 四舍五入后的数字字符串
|
|
391
|
+
*/
|
|
392
|
+
function roundNumber(num, precision = 6) {
|
|
393
|
+
const factor = Math.pow(10, precision);
|
|
394
|
+
const rounded = Math.round(num * factor) / factor;
|
|
395
|
+
|
|
396
|
+
// 移除不必要的尾随零
|
|
397
|
+
const str = rounded.toString();
|
|
398
|
+
if (str.includes('.')) {
|
|
399
|
+
return str.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
|
|
400
|
+
}
|
|
401
|
+
return str;
|
|
413
402
|
}
|
|
414
|
-
|
|
415
|
-
let result = cssValue;
|
|
416
|
-
|
|
417
|
-
// 匹配math()函数
|
|
418
|
-
const mathRegex = /math\(([^)]+)\)/gi;
|
|
419
|
-
|
|
420
|
-
// 递归处理嵌套的math()函数
|
|
421
|
-
const processMath = (str) => {
|
|
422
|
-
return str.replace(mathRegex, (match, expression) => {
|
|
423
|
-
return evaluateMathExpression(expression, config);
|
|
424
|
-
});
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
// 递归处理,直到没有更多math()函数
|
|
428
|
-
let lastResult;
|
|
429
|
-
do {
|
|
430
|
-
lastResult = result;
|
|
431
|
-
result = processMath(result);
|
|
432
|
-
} while (result !== lastResult && result.includes('math('));
|
|
433
|
-
|
|
434
|
-
return result;
|
|
435
|
-
}
|
|
436
403
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
let result = cssValue;
|
|
449
|
-
|
|
450
|
-
// 首先处理特殊的十六进制格式
|
|
451
|
-
result = parseHexColorFormats(result, config);
|
|
452
|
-
|
|
453
|
-
// 一次性处理所有的 lab() 和 lch() 函数
|
|
454
|
-
const colorFunctionRegex = /(lab|lch)\([^)]+\)/gi;
|
|
455
|
-
|
|
456
|
-
// 存储已经处理过的颜色字符串,避免重复处理
|
|
457
|
-
const processedColors = new Map();
|
|
458
|
-
|
|
459
|
-
// 使用一个函数来递归处理嵌套的颜色函数
|
|
460
|
-
const processColorFunctions = (str) => {
|
|
461
|
-
return str.replace(colorFunctionRegex, (match) => {
|
|
462
|
-
// 如果这个颜色已经处理过,直接返回缓存结果
|
|
463
|
-
if (processedColors.has(match)) {
|
|
464
|
-
return processedColors.get(match);
|
|
404
|
+
/**
|
|
405
|
+
* 处理所有的math()函数
|
|
406
|
+
* @param {string} cssValue - CSS属性值
|
|
407
|
+
* @param {Object} config - 配置对象
|
|
408
|
+
* @returns {string} 转换后的CSS值
|
|
409
|
+
*/
|
|
410
|
+
function processMathFunctions(cssValue, config) {
|
|
411
|
+
if (!config.enableMath) {
|
|
412
|
+
return cssValue;
|
|
465
413
|
}
|
|
466
414
|
|
|
467
|
-
let
|
|
468
|
-
|
|
469
|
-
// 根据函数类型处理
|
|
470
|
-
if (match.toLowerCase().startsWith('lab(')) {
|
|
471
|
-
if (config.convertLabToRGB) {
|
|
472
|
-
converted = convertSingleLabColor(match, config);
|
|
473
|
-
}
|
|
474
|
-
} else if (match.toLowerCase().startsWith('lch(')) {
|
|
475
|
-
if (config.convertLchToRGB) {
|
|
476
|
-
converted = convertSingleLchColor(match, config);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
415
|
+
let result = cssValue;
|
|
479
416
|
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* @param {string} value - 颜色值
|
|
499
|
-
* @param {Object} config - 配置对象
|
|
500
|
-
* @returns {string} 转换后的CSS颜色
|
|
501
|
-
*/
|
|
502
|
-
function parseHexColorFormats(value, config) {
|
|
503
|
-
let result = value;
|
|
504
|
-
|
|
505
|
-
// 使用正则表达式一次性匹配所有 lab# 和 lch# 格式
|
|
506
|
-
const hexLabRegex = /lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;
|
|
507
|
-
const hexLchRegex = /lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
|
|
508
|
-
|
|
509
|
-
// 存储处理过的十六进制颜色,避免重复处理
|
|
510
|
-
const processedHexColors = new Map();
|
|
511
|
-
|
|
512
|
-
// 处理 lab# 格式
|
|
513
|
-
result = result.replace(hexLabRegex, (match, L_hex, A_hex, B_hex) => {
|
|
514
|
-
if (processedHexColors.has(match)) {
|
|
515
|
-
return processedHexColors.get(match);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
try {
|
|
519
|
-
// 将十六进制转换为十进制,并映射到Lab范围
|
|
520
|
-
const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
|
|
521
|
-
const A = (parseInt(A_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
522
|
-
const B = (parseInt(B_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
523
|
-
|
|
524
|
-
const converted = generateColorString(L, A, B, config);
|
|
525
|
-
processedHexColors.set(match, converted);
|
|
526
|
-
return converted;
|
|
527
|
-
} catch (error) {
|
|
528
|
-
console.warn(`无法解析lab#十六进制颜色: ${match}`, error);
|
|
529
|
-
return match;
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
// 处理 lch# 格式
|
|
534
|
-
result = result.replace(hexLchRegex, (match, L_hex, C_hex, H_dec) => {
|
|
535
|
-
if (processedHexColors.has(match)) {
|
|
536
|
-
return processedHexColors.get(match);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
// 将十六进制转换为十进制,并映射到LCH范围
|
|
541
|
-
const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
|
|
542
|
-
const C = parseInt(C_hex, 16) / 255 * 150; // 0-150
|
|
543
|
-
const H = parseInt(H_dec) / 100 * 360; // 0-360
|
|
544
|
-
|
|
545
|
-
const lab = lchToLab(L, C, H);
|
|
546
|
-
const converted = generateColorString(lab.L, lab.a, lab.b, config);
|
|
547
|
-
processedHexColors.set(match, converted);
|
|
548
|
-
return converted;
|
|
549
|
-
} catch (error) {
|
|
550
|
-
console.warn(`无法解析lch#十六进制颜色: ${match}`, error);
|
|
551
|
-
return match;
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
return result;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* 转换单个 lab() 颜色函数
|
|
560
|
-
* @param {string} labString - lab() 颜色字符串
|
|
561
|
-
* @param {Object} config - 配置对象
|
|
562
|
-
* @returns {string} 转换后的颜色字符串
|
|
563
|
-
*/
|
|
564
|
-
function convertSingleLabColor(labString, config) {
|
|
565
|
-
// 使用更精确的正则匹配 lab() 函数
|
|
566
|
-
const labRegex = /lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
567
|
-
const match = labString.match(labRegex);
|
|
568
|
-
|
|
569
|
-
if (!match) {
|
|
570
|
-
return labString;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
try {
|
|
574
|
-
// 转换为数字
|
|
575
|
-
let L = parseFloat(match[1]);
|
|
576
|
-
|
|
577
|
-
// 如果L有百分号,转换为0-100范围的值
|
|
578
|
-
if (match[2] === '%') {
|
|
579
|
-
L = L; // 百分比值直接使用 (0-100)
|
|
580
|
-
} else {
|
|
581
|
-
// 没有百分号,确保在0-100范围内
|
|
582
|
-
if (L < 0) L = 0;
|
|
583
|
-
if (L > 100) L = 100;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const A = parseFloat(match[3]);
|
|
587
|
-
const B = parseFloat(match[4]);
|
|
588
|
-
|
|
589
|
-
// 处理 alpha 通道(如果有)
|
|
590
|
-
const alphaValue = match[5] !== undefined
|
|
591
|
-
? match[5].includes('%')
|
|
592
|
-
? parseFloat(match[5]) / 100
|
|
593
|
-
: parseFloat(match[5])
|
|
594
|
-
: null;
|
|
595
|
-
|
|
596
|
-
return generateColorString(L, A, B, config, alphaValue);
|
|
597
|
-
} catch (error) {
|
|
598
|
-
console.warn(`无法转换LAB颜色: ${labString}`, error);
|
|
599
|
-
return labString;
|
|
417
|
+
// 匹配math()函数
|
|
418
|
+
const mathRegex = /math\(([^)]+)\)/gi;
|
|
419
|
+
|
|
420
|
+
// 递归处理嵌套的math()函数
|
|
421
|
+
const processMath = (str) => {
|
|
422
|
+
return str.replace(mathRegex, (match, expression) => {
|
|
423
|
+
return evaluateMathExpression(expression, config);
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// 递归处理,直到没有更多math()函数
|
|
428
|
+
let lastResult;
|
|
429
|
+
do {
|
|
430
|
+
lastResult = result;
|
|
431
|
+
result = processMath(result);
|
|
432
|
+
} while (result !== lastResult && result.includes('math('));
|
|
433
|
+
|
|
434
|
+
return result;
|
|
600
435
|
}
|
|
601
|
-
}
|
|
602
436
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
437
|
+
/**
|
|
438
|
+
* 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
|
|
439
|
+
* @param {string} cssValue - CSS属性值
|
|
440
|
+
* @param {Object} config - 配置对象
|
|
441
|
+
* @returns {string} 转换后的CSS值
|
|
442
|
+
*/
|
|
443
|
+
function convertAllLabLchColors(cssValue, config) {
|
|
444
|
+
if (!config.convertLabToRGB && !config.convertLchToRGB) {
|
|
445
|
+
return cssValue;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let result = cssValue;
|
|
449
|
+
|
|
450
|
+
// 首先处理特殊的十六进制格式
|
|
451
|
+
result = parseHexColorFormats(result, config);
|
|
452
|
+
|
|
453
|
+
// 一次性处理所有的 lab() 和 lch() 函数
|
|
454
|
+
const colorFunctionRegex = /(lab|lch)\([^)]+\)/gi;
|
|
455
|
+
|
|
456
|
+
// 存储已经处理过的颜色字符串,避免重复处理
|
|
457
|
+
const processedColors = new Map();
|
|
458
|
+
|
|
459
|
+
// 使用一个函数来递归处理嵌套的颜色函数
|
|
460
|
+
const processColorFunctions = (str) => {
|
|
461
|
+
return str.replace(colorFunctionRegex, (match) => {
|
|
462
|
+
// 如果这个颜色已经处理过,直接返回缓存结果
|
|
463
|
+
if (processedColors.has(match)) {
|
|
464
|
+
return processedColors.get(match);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let converted = match;
|
|
468
|
+
|
|
469
|
+
// 根据函数类型处理
|
|
470
|
+
if (match.toLowerCase().startsWith('lab(')) {
|
|
471
|
+
if (config.convertLabToRGB) {
|
|
472
|
+
converted = convertSingleLabColor(match, config);
|
|
473
|
+
}
|
|
474
|
+
} else if (match.toLowerCase().startsWith('lch(')) {
|
|
475
|
+
if (config.convertLchToRGB) {
|
|
476
|
+
converted = convertSingleLchColor(match, config);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 缓存结果
|
|
481
|
+
processedColors.set(match, converted);
|
|
482
|
+
return converted;
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// 递归处理,直到没有更多颜色函数
|
|
487
|
+
let lastResult;
|
|
488
|
+
do {
|
|
489
|
+
lastResult = result;
|
|
490
|
+
result = processColorFunctions(result);
|
|
491
|
+
} while (result !== lastResult);
|
|
492
|
+
|
|
493
|
+
return result;
|
|
656
494
|
}
|
|
657
|
-
}
|
|
658
495
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
496
|
+
/**
|
|
497
|
+
* 解析十六进制颜色格式:lab#L16A16B16 或 lch#L16C16H
|
|
498
|
+
* @param {string} value - 颜色值
|
|
499
|
+
* @param {Object} config - 配置对象
|
|
500
|
+
* @returns {string} 转换后的CSS颜色
|
|
501
|
+
*/
|
|
502
|
+
function parseHexColorFormats(value, config) {
|
|
503
|
+
let result = value;
|
|
504
|
+
|
|
505
|
+
// 使用正则表达式一次性匹配所有 lab# 和 lch# 格式
|
|
506
|
+
const hexLabRegex = /lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;
|
|
507
|
+
const hexLchRegex = /lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
|
|
508
|
+
|
|
509
|
+
// 存储处理过的十六进制颜色,避免重复处理
|
|
510
|
+
const processedHexColors = new Map();
|
|
511
|
+
|
|
512
|
+
// 处理 lab# 格式
|
|
513
|
+
result = result.replace(hexLabRegex, (match, L_hex, A_hex, B_hex) => {
|
|
514
|
+
if (processedHexColors.has(match)) {
|
|
515
|
+
return processedHexColors.get(match);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
// 将十六进制转换为十进制,并映射到Lab范围
|
|
520
|
+
const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
|
|
521
|
+
const A = (parseInt(A_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
522
|
+
const B = (parseInt(B_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
523
|
+
|
|
524
|
+
const converted = generateColorString(L, A, B, config);
|
|
525
|
+
processedHexColors.set(match, converted);
|
|
526
|
+
return converted;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.warn(`无法解析lab#十六进制颜色: ${match}`, error);
|
|
529
|
+
return match;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// 处理 lch# 格式
|
|
534
|
+
result = result.replace(hexLchRegex, (match, L_hex, C_hex, H_dec) => {
|
|
535
|
+
if (processedHexColors.has(match)) {
|
|
536
|
+
return processedHexColors.get(match);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
// 将十六进制转换为十进制,并映射到LCH范围
|
|
541
|
+
const L = parseInt(L_hex, 16) / 255 * 100; // 0-100
|
|
542
|
+
const C = parseInt(C_hex, 16) / 255 * 150; // 0-150
|
|
543
|
+
const H = parseInt(H_dec) / 100 * 360; // 0-360
|
|
544
|
+
|
|
545
|
+
const lab = lchToLab(L, C, H);
|
|
546
|
+
const converted = generateColorString(lab.L, lab.a, lab.b, config);
|
|
547
|
+
processedHexColors.set(match, converted);
|
|
548
|
+
return converted;
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.warn(`无法解析lch#十六进制颜色: ${match}`, error);
|
|
551
|
+
return match;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
676
557
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
const applyGamma = (c) => {
|
|
722
|
-
const sign = c < 0 ? -1 : 1;
|
|
723
|
-
const absC = Math.abs(c);
|
|
724
|
-
|
|
725
|
-
if (absC <= 0.0031308) {
|
|
726
|
-
return sign * 12.92 * absC;
|
|
727
|
-
} else {
|
|
728
|
-
return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
// 4. 钳制到 [0, 255]
|
|
733
|
-
const clamp = (value) => {
|
|
734
|
-
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
// 执行转换
|
|
738
|
-
const [X, Y, Z] = labToXyz(L, a, b);
|
|
739
|
-
const [linearR, linearG, linearB] = xyzToLinearRgb(X, Y, Z);
|
|
740
|
-
|
|
741
|
-
const r = applyGamma(linearR);
|
|
742
|
-
const g = applyGamma(linearG);
|
|
743
|
-
const bOut = applyGamma(linearB);
|
|
744
|
-
|
|
745
|
-
// 返回钳制后的 RGB 值
|
|
746
|
-
return {
|
|
747
|
-
r: clamp(r),
|
|
748
|
-
g: clamp(g),
|
|
749
|
-
b: clamp(bOut)
|
|
750
|
-
};
|
|
751
|
-
}
|
|
558
|
+
/**
|
|
559
|
+
* 转换单个 lab() 颜色函数
|
|
560
|
+
* @param {string} labString - lab() 颜色字符串
|
|
561
|
+
* @param {Object} config - 配置对象
|
|
562
|
+
* @returns {string} 转换后的颜色字符串
|
|
563
|
+
*/
|
|
564
|
+
function convertSingleLabColor(labString, config) {
|
|
565
|
+
// 使用更精确的正则匹配 lab() 函数
|
|
566
|
+
const labRegex = /lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
567
|
+
const match = labString.match(labRegex);
|
|
568
|
+
|
|
569
|
+
if (!match) {
|
|
570
|
+
return labString;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
// 转换为数字
|
|
575
|
+
let L = parseFloat(match[1]);
|
|
576
|
+
|
|
577
|
+
// 如果L有百分号,转换为0-100范围的值
|
|
578
|
+
if (match[2] === '%') {
|
|
579
|
+
L = L; // 百分比值直接使用 (0-100)
|
|
580
|
+
} else {
|
|
581
|
+
// 没有百分号,确保在0-100范围内
|
|
582
|
+
if (L < 0) L = 0;
|
|
583
|
+
if (L > 100) L = 100;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const A = parseFloat(match[3]);
|
|
587
|
+
const B = parseFloat(match[4]);
|
|
588
|
+
|
|
589
|
+
// 处理 alpha 通道(如果有)
|
|
590
|
+
const alphaValue = match[5] !== undefined
|
|
591
|
+
? match[5].includes('%')
|
|
592
|
+
? parseFloat(match[5]) / 100
|
|
593
|
+
: parseFloat(match[5])
|
|
594
|
+
: null;
|
|
595
|
+
|
|
596
|
+
return generateColorString(L, A, B, config, alphaValue);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
console.warn(`无法转换LAB颜色: ${labString}`, error);
|
|
599
|
+
return labString;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
752
602
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
return sign * 12.92 * absC;
|
|
808
|
-
} else {
|
|
809
|
-
return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
// 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
|
|
814
|
-
const clamp = (value) => {
|
|
815
|
-
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
// 执行转换
|
|
819
|
-
try {
|
|
820
|
-
const [X, Y, Z] = labToXyz(L, a, b);
|
|
821
|
-
const [linearR, linearG, linearB] = xyzToLinearP3(X, Y, Z);
|
|
822
|
-
|
|
823
|
-
const r = applyGamma(linearR);
|
|
824
|
-
const g = applyGamma(linearG);
|
|
825
|
-
const bOut = applyGamma(linearB);
|
|
826
|
-
|
|
827
|
-
// 转换为0-1范围的浮点数用于P3颜色格式
|
|
828
|
-
return {
|
|
829
|
-
r: Math.max(0, Math.min(1, r)),
|
|
830
|
-
g: Math.max(0, Math.min(1, g)),
|
|
831
|
-
b: Math.max(0, Math.min(1, bOut))
|
|
832
|
-
};
|
|
833
|
-
} catch (error) {
|
|
834
|
-
console.warn('P3转换失败:', error);
|
|
835
|
-
const rgb = preciseLabToRGB(L, a, b);
|
|
836
|
-
return {
|
|
837
|
-
r: rgb.r / 255,
|
|
838
|
-
g: rgb.g / 255,
|
|
839
|
-
b: rgb.b / 255
|
|
840
|
-
};
|
|
603
|
+
/**
|
|
604
|
+
* 转换单个 lch() 颜色函数
|
|
605
|
+
* @param {string} lchString - lch() 颜色字符串
|
|
606
|
+
* @param {Object} config - 配置对象
|
|
607
|
+
* @returns {string} 转换后的颜色字符串
|
|
608
|
+
*/
|
|
609
|
+
function convertSingleLchColor(lchString, config) {
|
|
610
|
+
// 使用更精确的正则匹配 lch() 函数
|
|
611
|
+
const lchRegex = /lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
612
|
+
const match = lchString.match(lchRegex);
|
|
613
|
+
|
|
614
|
+
if (!match) {
|
|
615
|
+
return lchString;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
// 转换为数字
|
|
620
|
+
let L = parseFloat(match[1]);
|
|
621
|
+
|
|
622
|
+
// 如果L有百分号,转换为0-100范围的值
|
|
623
|
+
if (match[2] === '%') {
|
|
624
|
+
L = L; // 百分比值直接使用 (0-100)
|
|
625
|
+
} else {
|
|
626
|
+
// 没有百分号,确保在0-100范围内
|
|
627
|
+
if (L < 0) L = 0;
|
|
628
|
+
if (L > 100) L = 100;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const C = parseFloat(match[3]);
|
|
632
|
+
let H = parseFloat(match[4]);
|
|
633
|
+
|
|
634
|
+
// 验证C范围:>= 0
|
|
635
|
+
if (C < 0) {
|
|
636
|
+
console.warn(`LCH中的C值不能为负: ${C}`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// 验证H范围:0-360,有deg单位或无单位都是0-360
|
|
640
|
+
H = ((H % 360) + 360) % 360; // 标准化到0-360
|
|
641
|
+
|
|
642
|
+
// LCH -> Lab
|
|
643
|
+
const lab = lchToLab(L, C, H);
|
|
644
|
+
|
|
645
|
+
// 处理 alpha 通道(如果有)
|
|
646
|
+
const alphaValue = match[6] !== undefined
|
|
647
|
+
? match[6].includes('%')
|
|
648
|
+
? parseFloat(match[6]) / 100
|
|
649
|
+
: parseFloat(match[6])
|
|
650
|
+
: null;
|
|
651
|
+
|
|
652
|
+
return generateColorString(lab.L, lab.a, lab.b, config, alphaValue);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.warn(`无法转换LCH颜色: ${lchString}`, error);
|
|
655
|
+
return lchString;
|
|
656
|
+
}
|
|
841
657
|
}
|
|
842
|
-
}
|
|
843
658
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
861
|
-
} else {
|
|
862
|
-
// 使用P3
|
|
863
|
-
const p3 = labToP3(L, a, b);
|
|
864
|
-
if (alpha !== null) {
|
|
865
|
-
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`;
|
|
866
|
-
}
|
|
867
|
-
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)})`;
|
|
659
|
+
/**
|
|
660
|
+
* LCH 转换为 Lab (CSS标准)
|
|
661
|
+
* @param {number} L - 明度 0-100 或 0%-100%
|
|
662
|
+
* @param {number} C - 色度 >=0
|
|
663
|
+
* @param {number} H - 色相角 0-360 度
|
|
664
|
+
* @returns {Object} Lab 值 {L, a, b}
|
|
665
|
+
*/
|
|
666
|
+
function lchToLab(L, C, H) {
|
|
667
|
+
// 角度转换为弧度
|
|
668
|
+
const H_rad = H * Math.PI / 180;
|
|
669
|
+
|
|
670
|
+
// LCH -> Lab 转换公式
|
|
671
|
+
const a = C * Math.cos(H_rad);
|
|
672
|
+
const b = C * Math.sin(H_rad);
|
|
673
|
+
|
|
674
|
+
return { L, a, b };
|
|
868
675
|
}
|
|
869
|
-
}
|
|
870
676
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
677
|
+
// 精确的 Lab -> sRGB 转换函数
|
|
678
|
+
function preciseLabToRGB(L, a, b) {
|
|
679
|
+
// 1. Lab -> XYZ (D65白点)
|
|
680
|
+
const labToXyz = (L, a, b) => {
|
|
681
|
+
// D65 白点参考值
|
|
682
|
+
const refX = 0.95047;
|
|
683
|
+
const refY = 1.00000;
|
|
684
|
+
const refZ = 1.08883;
|
|
685
|
+
|
|
686
|
+
const epsilon = 0.008856;
|
|
687
|
+
const kappa = 903.3;
|
|
688
|
+
|
|
689
|
+
const fy = (L + 16) / 116;
|
|
690
|
+
const fx = a / 500 + fy;
|
|
691
|
+
const fz = fy - b / 200;
|
|
692
|
+
|
|
693
|
+
const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
|
|
694
|
+
const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
|
|
695
|
+
const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
|
|
696
|
+
|
|
697
|
+
return [
|
|
698
|
+
xr * refX,
|
|
699
|
+
yr * refY,
|
|
700
|
+
zr * refZ
|
|
701
|
+
];
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// 2. XYZ -> Linear RGB
|
|
705
|
+
const xyzToLinearRgb = (x, y, z) => {
|
|
706
|
+
// sRGB 转换矩阵 (D65)
|
|
707
|
+
const M = [
|
|
708
|
+
[ 3.2404542, -1.5371385, -0.4985314],
|
|
709
|
+
[-0.9692660, 1.8760108, 0.0415560],
|
|
710
|
+
[ 0.0556434, -0.2040259, 1.0572252]
|
|
711
|
+
];
|
|
712
|
+
|
|
713
|
+
const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
|
|
714
|
+
const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
|
|
715
|
+
const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
|
|
716
|
+
|
|
717
|
+
return [r, g, b];
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// 3. sRGB Gamma 校正
|
|
721
|
+
const applyGamma = (c) => {
|
|
722
|
+
const sign = c < 0 ? -1 : 1;
|
|
723
|
+
const absC = Math.abs(c);
|
|
724
|
+
|
|
725
|
+
if (absC <= 0.0031308) {
|
|
726
|
+
return sign * 12.92 * absC;
|
|
727
|
+
} else {
|
|
728
|
+
return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// 4. 钳制到 [0, 255]
|
|
733
|
+
const clamp = (value) => {
|
|
734
|
+
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// 执行转换
|
|
738
|
+
const [X, Y, Z] = labToXyz(L, a, b);
|
|
739
|
+
const [linearR, linearG, linearB] = xyzToLinearRgb(X, Y, Z);
|
|
740
|
+
|
|
741
|
+
const r = applyGamma(linearR);
|
|
742
|
+
const g = applyGamma(linearG);
|
|
743
|
+
const bOut = applyGamma(linearB);
|
|
744
|
+
|
|
745
|
+
// 返回钳制后的 RGB 值
|
|
746
|
+
return {
|
|
747
|
+
r: clamp(r),
|
|
748
|
+
g: clamp(g),
|
|
749
|
+
b: clamp(bOut)
|
|
750
|
+
};
|
|
883
751
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Lab -> Display P3 转换
|
|
755
|
+
* @param {number} L - 明度 0-100
|
|
756
|
+
* @param {number} a - a分量
|
|
757
|
+
* @param {number} b - b分量
|
|
758
|
+
* @returns {Object} P3颜色 {r, g, b}
|
|
759
|
+
*/
|
|
760
|
+
function labToP3(L, a, b) {
|
|
761
|
+
// 1. Lab -> XYZ (使用与RGB相同的转换)
|
|
762
|
+
const labToXyz = (L, a, b) => {
|
|
763
|
+
const refX = 0.95047;
|
|
764
|
+
const refY = 1.00000;
|
|
765
|
+
const refZ = 1.08883;
|
|
766
|
+
|
|
767
|
+
const epsilon = 0.008856;
|
|
768
|
+
const kappa = 903.3;
|
|
769
|
+
|
|
770
|
+
const fy = (L + 16) / 116;
|
|
771
|
+
const fx = a / 500 + fy;
|
|
772
|
+
const fz = fy - b / 200;
|
|
773
|
+
|
|
774
|
+
const xr = fx ** 3 > epsilon ? fx ** 3 : (116 * fx - 16) / kappa;
|
|
775
|
+
const yr = L > kappa * epsilon ? ((L + 16) / 116) ** 3 : L / kappa;
|
|
776
|
+
const zr = fz ** 3 > epsilon ? fz ** 3 : (116 * fz - 16) / kappa;
|
|
777
|
+
|
|
778
|
+
return [
|
|
779
|
+
xr * refX,
|
|
780
|
+
yr * refY,
|
|
781
|
+
zr * refZ
|
|
782
|
+
];
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// 2. XYZ -> Linear P3
|
|
786
|
+
const xyzToLinearP3 = (x, y, z) => {
|
|
787
|
+
// Display P3 转换矩阵 (D65)
|
|
788
|
+
const M = [
|
|
789
|
+
[ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],
|
|
790
|
+
[-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
|
|
791
|
+
[ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]
|
|
792
|
+
];
|
|
793
|
+
|
|
794
|
+
const r = M[0][0] * x + M[0][1] * y + M[0][2] * z;
|
|
795
|
+
const g = M[1][0] * x + M[1][1] * y + M[1][2] * z;
|
|
796
|
+
const b = M[2][0] * x + M[2][1] * y + M[2][2] * z;
|
|
797
|
+
|
|
798
|
+
return [r, g, b];
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
// 3. P3 Gamma 校正(与sRGB相同)
|
|
802
|
+
const applyGamma = (c) => {
|
|
803
|
+
const sign = c < 0 ? -1 : 1;
|
|
804
|
+
const absC = Math.abs(c);
|
|
805
|
+
|
|
806
|
+
if (absC <= 0.0031308) {
|
|
807
|
+
return sign * 12.92 * absC;
|
|
808
|
+
} else {
|
|
809
|
+
return sign * (1.055 * Math.pow(absC, 1/2.4) - 0.055);
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
// 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
|
|
814
|
+
const clamp = (value) => {
|
|
815
|
+
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
// 执行转换
|
|
819
|
+
try {
|
|
820
|
+
const [X, Y, Z] = labToXyz(L, a, b);
|
|
821
|
+
const [linearR, linearG, linearB] = xyzToLinearP3(X, Y, Z);
|
|
822
|
+
|
|
823
|
+
const r = applyGamma(linearR);
|
|
824
|
+
const g = applyGamma(linearG);
|
|
825
|
+
const bOut = applyGamma(linearB);
|
|
826
|
+
|
|
827
|
+
// 转换为0-1范围的浮点数用于P3颜色格式
|
|
828
|
+
return {
|
|
829
|
+
r: Math.max(0, Math.min(1, r)),
|
|
830
|
+
g: Math.max(0, Math.min(1, g)),
|
|
831
|
+
b: Math.max(0, Math.min(1, bOut))
|
|
832
|
+
};
|
|
833
|
+
} catch (error) {
|
|
834
|
+
console.warn('P3转换失败:', error);
|
|
835
|
+
const rgb = preciseLabToRGB(L, a, b);
|
|
836
|
+
return {
|
|
837
|
+
r: rgb.r / 255,
|
|
838
|
+
g: rgb.g / 255,
|
|
839
|
+
b: rgb.b / 255
|
|
840
|
+
};
|
|
841
|
+
}
|
|
888
842
|
}
|
|
889
|
-
|
|
890
|
-
return result;
|
|
891
|
-
}
|
|
892
843
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const [, varName, varValue] = varMatch;
|
|
911
|
-
// 处理变量值中的数学表达式和颜色转换
|
|
912
|
-
const processedValue = processCSSValue(
|
|
913
|
-
replaceVariableUsesInValue(varValue.trim(), {
|
|
914
|
-
...globalVariables,
|
|
915
|
-
...(currentSelector ? selectorVariables.get(currentSelector) || {} : {})
|
|
916
|
-
}),
|
|
917
|
-
config
|
|
918
|
-
);
|
|
919
|
-
|
|
920
|
-
if (currentSelector) {
|
|
921
|
-
// 选择器内部的变量
|
|
922
|
-
if (!selectorVariables.has(currentSelector)) {
|
|
923
|
-
selectorVariables.set(currentSelector, {});
|
|
924
|
-
}
|
|
925
|
-
selectorVariables.get(currentSelector)[varName] = processedValue;
|
|
844
|
+
/**
|
|
845
|
+
* 生成颜色字符串(根据P3支持情况输出P3或RGB)
|
|
846
|
+
* @param {number} L - Lab L值
|
|
847
|
+
* @param {number} a - Lab a值
|
|
848
|
+
* @param {number} b - Lab b值
|
|
849
|
+
* @param {Object} config - 配置对象
|
|
850
|
+
* @param {number|null} alpha - 透明度值
|
|
851
|
+
* @returns {string} CSS颜色字符串
|
|
852
|
+
*/
|
|
853
|
+
function generateColorString(L, a, b, config, alpha = null) {
|
|
854
|
+
if (!config.enableP3 || !detectP3Support()) {
|
|
855
|
+
// 使用RGB
|
|
856
|
+
const rgb = preciseLabToRGB(L, a, b);
|
|
857
|
+
if (alpha !== null) {
|
|
858
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
|
|
859
|
+
}
|
|
860
|
+
return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
926
861
|
} else {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// 检查是否是选择器开始
|
|
934
|
-
if (trimmed.endsWith('{')) {
|
|
935
|
-
currentSelector = trimmed.slice(0, -1).trim();
|
|
936
|
-
inSelectorBlock = true;
|
|
937
|
-
cssWithoutVars += line + '\n';
|
|
938
|
-
continue;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// 检查是否是选择器结束
|
|
942
|
-
if (trimmed === '}') {
|
|
943
|
-
if (inSelectorBlock) {
|
|
944
|
-
// 在选择器结束前插入变量声明
|
|
945
|
-
if (currentSelector && selectorVariables.has(currentSelector)) {
|
|
946
|
-
const vars = selectorVariables.get(currentSelector);
|
|
947
|
-
const indent = ' '.repeat(currentIndent);
|
|
948
|
-
for (const [varName, varValue] of Object.entries(vars)) {
|
|
949
|
-
cssWithoutVars += `${indent} --${varName}: ${varValue};\n`;
|
|
862
|
+
// 使用P3
|
|
863
|
+
const p3 = labToP3(L, a, b);
|
|
864
|
+
if (alpha !== null) {
|
|
865
|
+
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`;
|
|
950
866
|
}
|
|
951
|
-
|
|
867
|
+
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)})`;
|
|
952
868
|
}
|
|
953
|
-
inSelectorBlock = false;
|
|
954
|
-
currentSelector = null;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
cssWithoutVars += line + '\n';
|
|
958
|
-
|
|
959
|
-
// 更新缩进级别
|
|
960
|
-
if (line.includes('{')) {
|
|
961
|
-
currentIndent += 2;
|
|
962
|
-
}
|
|
963
|
-
if (line.includes('}')) {
|
|
964
|
-
currentIndent = Math.max(0, currentIndent - 2);
|
|
965
|
-
}
|
|
966
869
|
}
|
|
967
|
-
|
|
968
|
-
return {
|
|
969
|
-
globalVariables,
|
|
970
|
-
selectorVariables,
|
|
971
|
-
cssWithoutVars: cssWithoutVars.trim()
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
870
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
// 计算缩进级别(假设使用空格缩进)
|
|
989
|
-
const indent = line.match(/^(\s*)/)[0].length;
|
|
990
|
-
const indentLevel = Math.floor(indent / config.indentSize);
|
|
991
|
-
|
|
992
|
-
// 处理规则块结束
|
|
993
|
-
if (trimmed === '}') {
|
|
994
|
-
if (stack.length > 0) {
|
|
995
|
-
const rule = stack.pop();
|
|
996
|
-
|
|
997
|
-
// 如果这个规则有父规则,添加到父规则的children中
|
|
998
|
-
if (stack.length > 0) {
|
|
999
|
-
const parent = stack[stack.length - 1];
|
|
1000
|
-
if (!parent.children) parent.children = [];
|
|
1001
|
-
parent.children.push(rule);
|
|
1002
|
-
} else {
|
|
1003
|
-
// 否则是根级规则
|
|
1004
|
-
rootRules.push(rule);
|
|
1005
|
-
}
|
|
871
|
+
/**
|
|
872
|
+
* 处理CSS值,根据配置转换LAB、LCH颜色和math()函数
|
|
873
|
+
* @param {string} value - CSS属性值
|
|
874
|
+
* @param {Object} config - 配置对象
|
|
875
|
+
* @returns {string} 处理后的值
|
|
876
|
+
*/
|
|
877
|
+
function processCSSValue(value, config) {
|
|
878
|
+
let result = value;
|
|
879
|
+
|
|
880
|
+
// 1. 首先处理math()函数
|
|
881
|
+
if (config.enableMath) {
|
|
882
|
+
result = processMathFunctions(result, config);
|
|
1006
883
|
}
|
|
1007
|
-
continue;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// 处理规则开始
|
|
1011
|
-
if (trimmed.endsWith('{')) {
|
|
1012
|
-
const selector = trimmed.slice(0, -1).trim();
|
|
1013
|
-
|
|
1014
|
-
// 创建新规则
|
|
1015
|
-
const rule = {
|
|
1016
|
-
selector: selector,
|
|
1017
|
-
properties: [],
|
|
1018
|
-
children: [],
|
|
1019
|
-
indentLevel: indentLevel
|
|
1020
|
-
};
|
|
1021
884
|
|
|
1022
|
-
//
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// 处理属性行(不包含 { 或 } 的行)
|
|
1028
|
-
if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
|
|
1029
|
-
if (stack.length > 0) {
|
|
1030
|
-
const currentRule = stack[stack.length - 1];
|
|
1031
|
-
// 处理属性值中的math()函数和颜色转换
|
|
1032
|
-
const parsed = parseSingleLineCSS(trimmed);
|
|
1033
|
-
parsed.forEach(obj => {
|
|
1034
|
-
const key = Object.keys(obj)[0];
|
|
1035
|
-
const value = processCSSValue(obj[key], config);
|
|
1036
|
-
currentRule.properties.push(`${key}: ${value}`);
|
|
1037
|
-
});
|
|
885
|
+
// 2. 然后处理LAB和LCH颜色
|
|
886
|
+
if (config.convertLabToRGB || config.convertLchToRGB) {
|
|
887
|
+
result = convertAllLabLchColors(result, config);
|
|
1038
888
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
889
|
+
|
|
890
|
+
return result;
|
|
1041
891
|
}
|
|
1042
|
-
|
|
1043
|
-
//
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
892
|
+
|
|
893
|
+
// 私有方法:提取变量定义并移除(支持嵌套作用域)
|
|
894
|
+
function extractVariablesAndCSS(cssText, config) {
|
|
895
|
+
const lines = cssText.split('\n');
|
|
896
|
+
const globalVariables = {};
|
|
897
|
+
const selectorVariables = new Map();
|
|
898
|
+
let cssWithoutVars = '';
|
|
899
|
+
let currentSelector = null;
|
|
900
|
+
let inSelectorBlock = false;
|
|
901
|
+
let currentIndent = 0;
|
|
902
|
+
|
|
903
|
+
for (let line of lines) {
|
|
904
|
+
const trimmed = line.trim();
|
|
905
|
+
|
|
906
|
+
// 检查是否是变量定义
|
|
907
|
+
const varMatch = trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);
|
|
908
|
+
|
|
909
|
+
if (varMatch) {
|
|
910
|
+
const [, varName, varValue] = varMatch;
|
|
911
|
+
// 处理变量值中的数学表达式和颜色转换
|
|
912
|
+
const processedValue = processCSSValue(
|
|
913
|
+
replaceVariableUsesInValue(varValue.trim(), {
|
|
914
|
+
...globalVariables,
|
|
915
|
+
...(currentSelector ? selectorVariables.get(currentSelector) || {} : {})
|
|
916
|
+
}),
|
|
917
|
+
config
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
if (currentSelector) {
|
|
921
|
+
// 选择器内部的变量
|
|
922
|
+
if (!selectorVariables.has(currentSelector)) {
|
|
923
|
+
selectorVariables.set(currentSelector, {});
|
|
924
|
+
}
|
|
925
|
+
selectorVariables.get(currentSelector)[varName] = processedValue;
|
|
926
|
+
} else {
|
|
927
|
+
// 全局变量(:root 中)
|
|
928
|
+
globalVariables[varName] = processedValue;
|
|
929
|
+
}
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// 检查是否是选择器开始
|
|
934
|
+
if (trimmed.endsWith('{')) {
|
|
935
|
+
currentSelector = trimmed.slice(0, -1).trim();
|
|
936
|
+
inSelectorBlock = true;
|
|
937
|
+
cssWithoutVars += line + '\n';
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// 检查是否是选择器结束
|
|
942
|
+
if (trimmed === '}') {
|
|
943
|
+
if (inSelectorBlock) {
|
|
944
|
+
// 在选择器结束前插入变量声明
|
|
945
|
+
if (currentSelector && selectorVariables.has(currentSelector)) {
|
|
946
|
+
const vars = selectorVariables.get(currentSelector);
|
|
947
|
+
const indent = ' '.repeat(currentIndent);
|
|
948
|
+
for (const [varName, varValue] of Object.entries(vars)) {
|
|
949
|
+
cssWithoutVars += `${indent} --${varName}: ${varValue};\n`;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
inSelectorBlock = false;
|
|
954
|
+
currentSelector = null;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
cssWithoutVars += line + '\n';
|
|
958
|
+
|
|
959
|
+
// 更新缩进级别
|
|
960
|
+
if (line.includes('{')) {
|
|
961
|
+
currentIndent += 2;
|
|
962
|
+
}
|
|
963
|
+
if (line.includes('}')) {
|
|
964
|
+
currentIndent = Math.max(0, currentIndent - 2);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return {
|
|
969
|
+
globalVariables,
|
|
970
|
+
selectorVariables,
|
|
971
|
+
cssWithoutVars: cssWithoutVars.trim()
|
|
972
|
+
};
|
|
1053
973
|
}
|
|
1054
|
-
|
|
1055
|
-
//
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
974
|
+
|
|
975
|
+
// 修复的嵌套解析函数
|
|
976
|
+
function parseNestedRules(cssText, config) {
|
|
977
|
+
const lines = cssText.split('\n');
|
|
978
|
+
const stack = []; // 存储规则信息:{selector, properties, children}
|
|
979
|
+
const rootRules = [];
|
|
980
|
+
let currentIndent = 0;
|
|
981
|
+
|
|
982
|
+
for (let i = 0; i < lines.length; i++) {
|
|
983
|
+
const line = lines[i];
|
|
984
|
+
const trimmed = line.trim();
|
|
985
|
+
|
|
986
|
+
if (!trimmed) continue;
|
|
987
|
+
|
|
988
|
+
// 计算缩进级别(假设使用空格缩进)
|
|
989
|
+
const indent = line.match(/^(\s*)/)[0].length;
|
|
990
|
+
const indentLevel = Math.floor(indent / config.indentSize);
|
|
991
|
+
|
|
992
|
+
// 处理规则块结束
|
|
993
|
+
if (trimmed === '}') {
|
|
994
|
+
if (stack.length > 0) {
|
|
995
|
+
const rule = stack.pop();
|
|
996
|
+
|
|
997
|
+
// 如果这个规则有父规则,添加到父规则的children中
|
|
998
|
+
if (stack.length > 0) {
|
|
999
|
+
const parent = stack[stack.length - 1];
|
|
1000
|
+
if (!parent.children) parent.children = [];
|
|
1001
|
+
parent.children.push(rule);
|
|
1002
|
+
} else {
|
|
1003
|
+
// 否则是根级规则
|
|
1004
|
+
rootRules.push(rule);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// 处理规则开始
|
|
1011
|
+
if (trimmed.endsWith('{')) {
|
|
1012
|
+
const selector = trimmed.slice(0, -1).trim();
|
|
1013
|
+
|
|
1014
|
+
// 创建新规则
|
|
1015
|
+
const rule = {
|
|
1016
|
+
selector: selector,
|
|
1017
|
+
properties: [],
|
|
1018
|
+
children: [],
|
|
1019
|
+
indentLevel: indentLevel
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
// 推入堆栈
|
|
1023
|
+
stack.push(rule);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// 处理属性行(不包含 { 或 } 的行)
|
|
1028
|
+
if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
|
|
1029
|
+
if (stack.length > 0) {
|
|
1030
|
+
const currentRule = stack[stack.length - 1];
|
|
1031
|
+
// 处理属性值中的math()函数和颜色转换
|
|
1032
|
+
const parsed = parseSingleLineCSS(trimmed);
|
|
1033
|
+
parsed.forEach(obj => {
|
|
1034
|
+
const key = Object.keys(obj)[0];
|
|
1035
|
+
const value = processCSSValue(obj[key], config);
|
|
1036
|
+
currentRule.properties.push(`${key}: ${value}`);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1081
1041
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
|
|
1094
|
-
});
|
|
1042
|
+
|
|
1043
|
+
// 处理栈中剩余规则
|
|
1044
|
+
while (stack.length > 0) {
|
|
1045
|
+
const rule = stack.pop();
|
|
1046
|
+
if (stack.length === 0) {
|
|
1047
|
+
rootRules.push(rule);
|
|
1048
|
+
} else {
|
|
1049
|
+
const parent = stack[stack.length - 1];
|
|
1050
|
+
if (!parent.children) parent.children = [];
|
|
1051
|
+
parent.children.push(rule);
|
|
1052
|
+
}
|
|
1095
1053
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
// 递归处理子规则
|
|
1100
|
-
if (rule.children && rule.children.length > 0) {
|
|
1101
|
-
result += convertRulesToCSS(rule.children, config, fullSelector);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
if (isParentAt){
|
|
1105
|
-
result += "}\n\n";
|
|
1106
|
-
}
|
|
1054
|
+
|
|
1055
|
+
// 将规则树转换为CSS字符串
|
|
1056
|
+
return convertRulesToCSS(rootRules, config);
|
|
1107
1057
|
}
|
|
1108
1058
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1059
|
+
// 将规则树转换为CSS字符串
|
|
1060
|
+
function convertRulesToCSS(rules, config, parentSelector = '') {
|
|
1061
|
+
let result = '';
|
|
1062
|
+
const isParentAt = parentSelector.startsWith("@");
|
|
1063
|
+
|
|
1064
|
+
for (const rule of rules) {
|
|
1065
|
+
// 构建完整选择器
|
|
1066
|
+
const isAt = rule.selector.startsWith("@");
|
|
1067
|
+
let fullSelector = (isParentAt ? ' ' : '') + rule.selector;
|
|
1068
|
+
|
|
1069
|
+
if (isAt) {
|
|
1070
|
+
fullSelector = rule.selector + " {\n";
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (parentSelector) {
|
|
1074
|
+
// 处理 & 选择器
|
|
1075
|
+
if (fullSelector.includes('&')) {
|
|
1076
|
+
fullSelector = fullSelector.replace(/&/g, parentSelector);
|
|
1077
|
+
} else if (fullSelector.trim().startsWith(':')) {
|
|
1078
|
+
fullSelector = parentSelector + fullSelector;
|
|
1079
|
+
} else {
|
|
1080
|
+
fullSelector = parentSelector + (isParentAt ? '' : ' ') + fullSelector;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// 如果有属性,生成规则块
|
|
1085
|
+
if (rule.properties.length > 0) {
|
|
1086
|
+
result += (isAt ? "" : fullSelector) + ' {\n';
|
|
1087
|
+
for (const prop of rule.properties) {
|
|
1088
|
+
// 再次处理属性值(确保math()函数和颜色被转换)
|
|
1089
|
+
const parsed = parseSingleLineCSS(prop);
|
|
1090
|
+
parsed.forEach(obj => {
|
|
1091
|
+
const key = Object.keys(obj)[0];
|
|
1092
|
+
const value = processCSSValue(obj[key], config);
|
|
1093
|
+
result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
result += isParentAt ? ' }\n' : '}\n\n';
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// 递归处理子规则
|
|
1100
|
+
if (rule.children && rule.children.length > 0) {
|
|
1101
|
+
result += convertRulesToCSS(rule.children, config, fullSelector);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (isParentAt){
|
|
1105
|
+
result += "}\n\n";
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return result.trim() + (isParentAt ? "\n\n" : "");
|
|
1130
1110
|
}
|
|
1131
|
-
|
|
1132
|
-
// 然后处理选择器特定的变量
|
|
1133
|
-
result = result.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
1134
|
-
return `var(--${varName})`;
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
// 最后处理所有的LAB、LCH颜色和math()函数
|
|
1138
|
-
result = processCSSValue(result, config);
|
|
1139
|
-
|
|
1140
|
-
return result;
|
|
1141
|
-
}
|
|
1142
1111
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1112
|
+
// 替换变量值中的变量引用
|
|
1113
|
+
function replaceVariableUsesInValue(value, variables) {
|
|
1114
|
+
return value.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
1115
|
+
if (variables[varName]) {
|
|
1116
|
+
return `var(--${varName})`;
|
|
1117
|
+
}
|
|
1118
|
+
return match;
|
|
1119
|
+
});
|
|
1147
1120
|
}
|
|
1148
|
-
|
|
1149
|
-
const declarations = Object.entries(variables)
|
|
1150
|
-
.map(([name, value]) => {
|
|
1151
|
-
const processedValue = processCSSValue(
|
|
1152
|
-
replaceVariableUsesInValue(value, variables),
|
|
1153
|
-
config
|
|
1154
|
-
);
|
|
1155
|
-
return ` --${name}: ${processedValue};`;
|
|
1156
|
-
})
|
|
1157
|
-
.join('\n');
|
|
1158
|
-
|
|
1159
|
-
return `${config.rootSelector} {\n${declarations}\n}\n\n`;
|
|
1160
|
-
}
|
|
1161
1121
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (trimmed.endsWith('{')) {
|
|
1172
|
-
currentSelector = trimmed.slice(0, -1).trim();
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
if (trimmed === '}' && currentSelector) {
|
|
1176
|
-
// 在选择器结束前插入变量声明
|
|
1177
|
-
if (selectorVariables.has(currentSelector)) {
|
|
1178
|
-
const vars = selectorVariables.get(currentSelector);
|
|
1179
|
-
const indent = line.match(/^(\s*)/)[0];
|
|
1180
|
-
for (const [varName, varValue] of Object.entries(vars)) {
|
|
1181
|
-
result += indent + ' --' + varName + ': ' + varValue + ';\n';
|
|
1182
|
-
}
|
|
1122
|
+
// 替换变量使用
|
|
1123
|
+
function replaceVariableUses(cssText, globalVariables, selectorVariables, config) {
|
|
1124
|
+
let result = cssText;
|
|
1125
|
+
|
|
1126
|
+
// 先替换全局变量
|
|
1127
|
+
for (const [varName, varValue] of Object.entries(globalVariables)) {
|
|
1128
|
+
const varRegex = new RegExp(`\\$${varName}(?![a-zA-Z0-9_-])`, 'g');
|
|
1129
|
+
result = result.replace(varRegex, `var(--${varName})`);
|
|
1183
1130
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1131
|
+
|
|
1132
|
+
// 然后处理选择器特定的变量
|
|
1133
|
+
result = result.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
1134
|
+
return `var(--${varName})`;
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// 最后处理所有的LAB、LCH颜色和math()函数
|
|
1138
|
+
result = processCSSValue(result, config);
|
|
1139
|
+
|
|
1140
|
+
return result;
|
|
1189
1141
|
}
|
|
1190
|
-
|
|
1191
|
-
return result.trim();
|
|
1192
|
-
}
|
|
1193
1142
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
// 3. 生成根规则(全局变量)
|
|
1213
|
-
const rootRule = generateRootRule(globalVariables, config);
|
|
1214
|
-
|
|
1215
|
-
// 4. 注入选择器局部变量
|
|
1216
|
-
let finalCSS = processedCSS;
|
|
1217
|
-
if (selectorVariables.size > 0) {
|
|
1218
|
-
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
|
|
1143
|
+
// 生成根规则
|
|
1144
|
+
function generateRootRule(variables, config) {
|
|
1145
|
+
if (Object.keys(variables).length === 0) {
|
|
1146
|
+
return '';
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const declarations = Object.entries(variables)
|
|
1150
|
+
.map(([name, value]) => {
|
|
1151
|
+
const processedValue = processCSSValue(
|
|
1152
|
+
replaceVariableUsesInValue(value, variables),
|
|
1153
|
+
config
|
|
1154
|
+
);
|
|
1155
|
+
return ` --${name}: ${processedValue};`;
|
|
1156
|
+
})
|
|
1157
|
+
.join('\n');
|
|
1158
|
+
|
|
1159
|
+
return `${config.rootSelector} {\n${declarations}\n}\n\n`;
|
|
1219
1160
|
}
|
|
1220
|
-
|
|
1221
|
-
// 5. 替换变量使用
|
|
1222
|
-
finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, config);
|
|
1223
|
-
|
|
1224
|
-
// 6. 组合结果
|
|
1225
|
-
return rootRule + finalCSS;
|
|
1226
|
-
}
|
|
1227
1161
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1162
|
+
// 处理选择器内部的变量
|
|
1163
|
+
function injectSelectorVariables(cssText, selectorVariables, config) {
|
|
1164
|
+
let result = '';
|
|
1165
|
+
const lines = cssText.split('\n');
|
|
1166
|
+
let currentSelector = null;
|
|
1167
|
+
|
|
1168
|
+
for (let line of lines) {
|
|
1169
|
+
const trimmed = line.trim();
|
|
1170
|
+
|
|
1171
|
+
if (trimmed.endsWith('{')) {
|
|
1172
|
+
currentSelector = trimmed.slice(0, -1).trim();
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (trimmed === '}' && currentSelector) {
|
|
1176
|
+
// 在选择器结束前插入变量声明
|
|
1177
|
+
if (selectorVariables.has(currentSelector)) {
|
|
1178
|
+
const vars = selectorVariables.get(currentSelector);
|
|
1179
|
+
const indent = line.match(/^(\s*)/)[0];
|
|
1180
|
+
for (const [varName, varValue] of Object.entries(vars)) {
|
|
1181
|
+
result += indent + ' --' + varName + ': ' + varValue + ';\n';
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
currentSelector = null;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// 使用全局处理函数处理所有math()和颜色
|
|
1188
|
+
result += processCSSValue(line, config) + '\n';
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return result.trim();
|
|
1192
|
+
}
|
|
1254
1193
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1194
|
+
// 主转换函数
|
|
1195
|
+
function convert(cssText, customConfig = {}) {
|
|
1196
|
+
const config = { ...defaultConfig, ...customConfig };
|
|
1197
|
+
|
|
1198
|
+
// 1. 提取变量定义(区分全局和选择器局部)
|
|
1199
|
+
const { globalVariables, selectorVariables, cssWithoutVars } =
|
|
1200
|
+
extractVariablesAndCSS(cssText, config);
|
|
1201
|
+
|
|
1202
|
+
// 2. 解析嵌套规则(如果启用)
|
|
1203
|
+
let processedCSS = cssWithoutVars.trim();
|
|
1204
|
+
if (config.enableNesting && cssWithoutVars.includes('{')) {
|
|
1205
|
+
try {
|
|
1206
|
+
processedCSS = parseNestedRules(cssWithoutVars, config);
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
console.warn('嵌套解析失败,使用原始CSS:', error);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// 3. 生成根规则(全局变量)
|
|
1213
|
+
const rootRule = generateRootRule(globalVariables, config);
|
|
1214
|
+
|
|
1215
|
+
// 4. 注入选择器局部变量
|
|
1216
|
+
let finalCSS = processedCSS;
|
|
1217
|
+
if (selectorVariables.size > 0) {
|
|
1218
|
+
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// 5. 替换变量使用
|
|
1222
|
+
finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, config);
|
|
1223
|
+
|
|
1224
|
+
// 6. 组合结果
|
|
1225
|
+
return rootRule + finalCSS;
|
|
1226
|
+
}
|
|
1258
1227
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1228
|
+
// 自动处理带有 e 属性的 style 标签
|
|
1229
|
+
function autoProcessStyleTags(customConfig = {}) {
|
|
1230
|
+
const config = { ...defaultConfig, ...customConfig };
|
|
1231
|
+
const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
|
|
1232
|
+
|
|
1233
|
+
styleTags.forEach(styleTag => {
|
|
1234
|
+
const originalCSS = styleTag.textContent;
|
|
1235
|
+
const convertedCSS = convert(originalCSS, config);
|
|
1236
|
+
|
|
1237
|
+
// 创建新的 style 标签
|
|
1238
|
+
const newStyleTag = document.createElement('style');
|
|
1239
|
+
newStyleTag.textContent = convertedCSS;
|
|
1240
|
+
|
|
1241
|
+
// 插入到原标签后面
|
|
1242
|
+
styleTag.parentNode.insertBefore(newStyleTag, styleTag.nextSibling);
|
|
1243
|
+
|
|
1244
|
+
// 可选:移除原标签
|
|
1245
|
+
if (!config.preserveOriginal) {
|
|
1246
|
+
styleTag.remove();
|
|
1247
|
+
} else {
|
|
1248
|
+
styleTag.style.display = 'none';
|
|
1249
|
+
}
|
|
1273
1250
|
});
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
}
|
|
1251
|
+
|
|
1252
|
+
return styleTags.length;
|
|
1277
1253
|
}
|
|
1278
|
-
}
|
|
1279
1254
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
},
|
|
1303
|
-
|
|
1304
|
-
/**
|
|
1305
|
-
* 解析带单位的数值
|
|
1306
|
-
* @param {string} value - 带单位的字符串
|
|
1307
|
-
* @returns {Object} {value: number, unit: string}
|
|
1308
|
-
*/
|
|
1309
|
-
parseUnit: parseValueWithUnit,
|
|
1310
|
-
|
|
1311
|
-
/**
|
|
1312
|
-
* 四舍五入数字
|
|
1313
|
-
* @param {number} num - 要四舍五入的数字
|
|
1314
|
-
* @param {number} precision - 精度
|
|
1315
|
-
* @returns {string} 四舍五入后的数字字符串
|
|
1316
|
-
*/
|
|
1317
|
-
round: roundNumber,
|
|
1318
|
-
|
|
1319
|
-
/**
|
|
1320
|
-
* 测试数学表达式
|
|
1321
|
-
* @param {string} expression - 要测试的表达式
|
|
1322
|
-
* @param {Object} testConfig - 测试配置
|
|
1323
|
-
* @returns {Object} 测试结果
|
|
1324
|
-
*/
|
|
1325
|
-
test: function(expression, testConfig = {}) {
|
|
1326
|
-
const config = { ...defaultConfig, ...testConfig };
|
|
1327
|
-
try {
|
|
1328
|
-
const result = evaluateMathExpression(expression, config);
|
|
1329
|
-
return {
|
|
1330
|
-
success: true,
|
|
1331
|
-
expression,
|
|
1332
|
-
result,
|
|
1333
|
-
parsed: parseValueWithUnit(result.replace(/^calc\(|\)$/g, ''))
|
|
1334
|
-
};
|
|
1335
|
-
} catch (error) {
|
|
1336
|
-
return {
|
|
1337
|
-
success: false,
|
|
1338
|
-
expression,
|
|
1339
|
-
error: error.message
|
|
1340
|
-
};
|
|
1255
|
+
function config(config = {}) {
|
|
1256
|
+
defaultConfig = { ...defaultConfig, ...config };
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// 初始化自动处理
|
|
1260
|
+
function apply(cssText, customConfig = {}) {
|
|
1261
|
+
const config = { ...defaultConfig, ...customConfig };
|
|
1262
|
+
|
|
1263
|
+
if (cssText) {
|
|
1264
|
+
const converted = convert(cssText, config);
|
|
1265
|
+
const styleEl = document.createElement('style');
|
|
1266
|
+
styleEl.textContent = converted;
|
|
1267
|
+
document.head.appendChild(styleEl);
|
|
1268
|
+
return styleEl;
|
|
1269
|
+
} else {
|
|
1270
|
+
if (document.readyState === 'loading') {
|
|
1271
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1272
|
+
autoProcessStyleTags(config);
|
|
1273
|
+
});
|
|
1274
|
+
} else {
|
|
1275
|
+
autoProcessStyleTags(config);
|
|
1276
|
+
}
|
|
1341
1277
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const A = (parseInt(A_hex, 16) - 128) * 1.5;
|
|
1368
|
-
const B = (parseInt(B_hex, 16) - 128) * 1.5;
|
|
1369
|
-
|
|
1370
|
-
return preciseLabToRGB(L, A, B);
|
|
1371
|
-
},
|
|
1372
|
-
parseHexLch: function(hexString) {
|
|
1373
|
-
const match = hexString.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);
|
|
1374
|
-
if (!match) return null;
|
|
1375
|
-
|
|
1376
|
-
const L_hex = match[1];
|
|
1377
|
-
const C_hex = match[2];
|
|
1378
|
-
const H_dec = match[3];
|
|
1379
|
-
|
|
1380
|
-
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
1381
|
-
const C = parseInt(C_hex, 16) / 255 * 150;
|
|
1382
|
-
const H = parseInt(H_dec) / 100 * 360;
|
|
1383
|
-
|
|
1384
|
-
const lab = lchToLab(L, C, H);
|
|
1385
|
-
return preciseLabToRGB(lab.L, lab.a, lab.b);
|
|
1386
|
-
},
|
|
1387
|
-
/**
|
|
1388
|
-
* 生成颜色字符串(根据P3支持情况)
|
|
1389
|
-
* @param {number} L - Lab L值
|
|
1390
|
-
* @param {number} a - Lab a值
|
|
1391
|
-
* @param {number} b - Lab b值
|
|
1392
|
-
* @param {number|null} alpha - 透明度
|
|
1393
|
-
* @param {boolean} useP3 - 是否使用P3(自动检测)
|
|
1394
|
-
* @returns {string} CSS颜色字符串
|
|
1395
|
-
*/
|
|
1396
|
-
generateColor: function(L, a, b, alpha = null, useP3 = true) {
|
|
1397
|
-
return generateColorString(L, a, b, { enableP3: useP3 }, alpha);
|
|
1398
|
-
},
|
|
1399
|
-
/**
|
|
1400
|
-
* 解析CSS颜色字符串
|
|
1401
|
-
* @param {string} colorString - CSS颜色字符串
|
|
1402
|
-
* @returns {Object} 包含原始Lab值和颜色字符串的对象
|
|
1403
|
-
*/
|
|
1404
|
-
parseColor: function(colorString) {
|
|
1405
|
-
try {
|
|
1406
|
-
// 尝试解析lab()函数格式
|
|
1407
|
-
const labMatch = colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1408
|
-
if (labMatch) {
|
|
1409
|
-
let L = parseFloat(labMatch[1]);
|
|
1410
|
-
const A = parseFloat(labMatch[3]);
|
|
1411
|
-
const B = parseFloat(labMatch[4]);
|
|
1412
|
-
const alpha = labMatch[5] ?
|
|
1413
|
-
(labMatch[5].includes('%') ? parseFloat(labMatch[5]) / 100 : parseFloat(labMatch[5])) :
|
|
1414
|
-
null;
|
|
1415
|
-
|
|
1416
|
-
const colorStr = generateColorString(L, A, B, defaultConfig, alpha);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// 返回公共 API
|
|
1281
|
+
const api = {
|
|
1282
|
+
convert,
|
|
1283
|
+
apply,
|
|
1284
|
+
config,
|
|
1285
|
+
version: '1.9.0',
|
|
1286
|
+
|
|
1287
|
+
// 检测P3支持
|
|
1288
|
+
supportsP3: detectP3Support(),
|
|
1289
|
+
|
|
1290
|
+
// 重新检测P3支持(如果配置变化)
|
|
1291
|
+
detectP3Support: detectP3Support,
|
|
1292
|
+
|
|
1293
|
+
// 数学计算工具方法
|
|
1294
|
+
math: {
|
|
1295
|
+
/**
|
|
1296
|
+
* 计算数学表达式
|
|
1297
|
+
* @param {string} expression - 数学表达式
|
|
1298
|
+
* @returns {string} 计算结果
|
|
1299
|
+
*/
|
|
1300
|
+
evaluate: function(expression) {
|
|
1301
|
+
return evaluateMathExpression(expression, defaultConfig);
|
|
1302
|
+
},
|
|
1417
1303
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// 尝试解析lch()函数格式
|
|
1427
|
-
const lchMatch = colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1428
|
-
if (lchMatch) {
|
|
1429
|
-
let L = parseFloat(lchMatch[1]);
|
|
1430
|
-
const C = parseFloat(lchMatch[3]);
|
|
1431
|
-
let H = parseFloat(lchMatch[4]);
|
|
1432
|
-
const alpha = lchMatch[6] ?
|
|
1433
|
-
(lchMatch[6].includes('%') ? parseFloat(lchMatch[6]) / 100 : parseFloat(lchMatch[6])) :
|
|
1434
|
-
null;
|
|
1304
|
+
/**
|
|
1305
|
+
* 解析带单位的数值
|
|
1306
|
+
* @param {string} value - 带单位的字符串
|
|
1307
|
+
* @returns {Object} {value: number, unit: string}
|
|
1308
|
+
*/
|
|
1309
|
+
parseUnit: parseValueWithUnit,
|
|
1435
1310
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1311
|
+
/**
|
|
1312
|
+
* 四舍五入数字
|
|
1313
|
+
* @param {number} num - 要四舍五入的数字
|
|
1314
|
+
* @param {number} precision - 精度
|
|
1315
|
+
* @returns {string} 四舍五入后的数字字符串
|
|
1316
|
+
*/
|
|
1317
|
+
round: roundNumber,
|
|
1438
1318
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1319
|
+
/**
|
|
1320
|
+
* 测试数学表达式
|
|
1321
|
+
* @param {string} expression - 要测试的表达式
|
|
1322
|
+
* @param {Object} testConfig - 测试配置
|
|
1323
|
+
* @returns {Object} 测试结果
|
|
1324
|
+
*/
|
|
1325
|
+
test: function(expression, testConfig = {}) {
|
|
1326
|
+
const config = { ...defaultConfig, ...testConfig };
|
|
1327
|
+
try {
|
|
1328
|
+
const result = evaluateMathExpression(expression, config);
|
|
1329
|
+
return {
|
|
1330
|
+
success: true,
|
|
1331
|
+
expression,
|
|
1332
|
+
result,
|
|
1333
|
+
parsed: parseValueWithUnit(result.replace(/^calc\(|\)$/g, ''))
|
|
1334
|
+
};
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
return {
|
|
1337
|
+
success: false,
|
|
1338
|
+
expression,
|
|
1339
|
+
error: error.message
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
|
|
1345
|
+
// 颜色转换工具方法
|
|
1346
|
+
colorUtils: {
|
|
1347
|
+
labToRGB: preciseLabToRGB,
|
|
1348
|
+
lchToLab: lchToLab,
|
|
1349
|
+
lchToRGB: function(L, C, H) {
|
|
1350
|
+
const lab = lchToLab(L, C, H);
|
|
1351
|
+
return preciseLabToRGB(lab.L, lab.a, lab.b);
|
|
1352
|
+
},
|
|
1353
|
+
labToP3: labToP3,
|
|
1354
|
+
lchToP3: function(L, C, H) {
|
|
1355
|
+
const lab = lchToLab(L, C, H);
|
|
1356
|
+
return labToP3(lab.L, lab.a, lab.b);
|
|
1357
|
+
},
|
|
1358
|
+
parseHexLab: function(hexString) {
|
|
1359
|
+
const match = hexString.match(/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);
|
|
1360
|
+
if (!match) return null;
|
|
1361
|
+
|
|
1362
|
+
const L_hex = match[1];
|
|
1363
|
+
const A_hex = match[2];
|
|
1364
|
+
const B_hex = match[3];
|
|
1365
|
+
|
|
1366
|
+
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
1367
|
+
const A = (parseInt(A_hex, 16) - 128) * 1.5;
|
|
1368
|
+
const B = (parseInt(B_hex, 16) - 128) * 1.5;
|
|
1369
|
+
|
|
1370
|
+
return preciseLabToRGB(L, A, B);
|
|
1371
|
+
},
|
|
1372
|
+
parseHexLch: function(hexString) {
|
|
1373
|
+
const match = hexString.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);
|
|
1374
|
+
if (!match) return null;
|
|
1375
|
+
|
|
1376
|
+
const L_hex = match[1];
|
|
1377
|
+
const C_hex = match[2];
|
|
1378
|
+
const H_dec = match[3];
|
|
1379
|
+
|
|
1380
|
+
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
1381
|
+
const C = parseInt(C_hex, 16) / 255 * 150;
|
|
1382
|
+
const H = parseInt(H_dec) / 100 * 360;
|
|
1383
|
+
|
|
1384
|
+
const lab = lchToLab(L, C, H);
|
|
1385
|
+
return preciseLabToRGB(lab.L, lab.a, lab.b);
|
|
1386
|
+
},
|
|
1387
|
+
/**
|
|
1388
|
+
* 生成颜色字符串(根据P3支持情况)
|
|
1389
|
+
* @param {number} L - Lab L值
|
|
1390
|
+
* @param {number} a - Lab a值
|
|
1391
|
+
* @param {number} b - Lab b值
|
|
1392
|
+
* @param {number|null} alpha - 透明度
|
|
1393
|
+
* @param {boolean} useP3 - 是否使用P3(自动检测)
|
|
1394
|
+
* @returns {string} CSS颜色字符串
|
|
1395
|
+
*/
|
|
1396
|
+
generateColor: function(L, a, b, alpha = null, useP3 = true) {
|
|
1397
|
+
return generateColorString(L, a, b, { enableP3: useP3 }, alpha);
|
|
1398
|
+
},
|
|
1399
|
+
/**
|
|
1400
|
+
* 解析CSS颜色字符串
|
|
1401
|
+
* @param {string} colorString - CSS颜色字符串
|
|
1402
|
+
* @returns {Object} 包含原始Lab值和颜色字符串的对象
|
|
1403
|
+
*/
|
|
1404
|
+
parseColor: function(colorString) {
|
|
1405
|
+
try {
|
|
1406
|
+
// 尝试解析lab()函数格式
|
|
1407
|
+
const labMatch = colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1408
|
+
if (labMatch) {
|
|
1409
|
+
let L = parseFloat(labMatch[1]);
|
|
1410
|
+
const A = parseFloat(labMatch[3]);
|
|
1411
|
+
const B = parseFloat(labMatch[4]);
|
|
1412
|
+
const alpha = labMatch[5] ?
|
|
1413
|
+
(labMatch[5].includes('%') ? parseFloat(labMatch[5]) / 100 : parseFloat(labMatch[5])) :
|
|
1414
|
+
null;
|
|
1415
|
+
|
|
1416
|
+
const colorStr = generateColorString(L, A, B, defaultConfig, alpha);
|
|
1417
|
+
|
|
1418
|
+
return {
|
|
1419
|
+
L, A, B, alpha,
|
|
1420
|
+
rgb: preciseLabToRGB(L, A, B),
|
|
1421
|
+
p3: labToP3(L, A, B),
|
|
1422
|
+
colorString: colorStr
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// 尝试解析lch()函数格式
|
|
1427
|
+
const lchMatch = colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1428
|
+
if (lchMatch) {
|
|
1429
|
+
let L = parseFloat(lchMatch[1]);
|
|
1430
|
+
const C = parseFloat(lchMatch[3]);
|
|
1431
|
+
let H = parseFloat(lchMatch[4]);
|
|
1432
|
+
const alpha = lchMatch[6] ?
|
|
1433
|
+
(lchMatch[6].includes('%') ? parseFloat(lchMatch[6]) / 100 : parseFloat(lchMatch[6])) :
|
|
1434
|
+
null;
|
|
1435
|
+
|
|
1436
|
+
const lab = lchToLab(L, C, H);
|
|
1437
|
+
const colorStr = generateColorString(lab.L, lab.a, lab.b, defaultConfig, alpha);
|
|
1438
|
+
|
|
1439
|
+
return {
|
|
1440
|
+
L, C, H, alpha,
|
|
1441
|
+
lab: lab,
|
|
1442
|
+
rgb: preciseLabToRGB(lab.L, lab.a, lab.b),
|
|
1443
|
+
p3: labToP3(lab.L, lab.a, lab.b),
|
|
1444
|
+
colorString: colorStr
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
return null;
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
console.warn('无法解析颜色:', colorString, error);
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1452
1454
|
}
|
|
1453
|
-
|
|
1454
|
-
}
|
|
1455
|
-
};
|
|
1456
|
-
|
|
1455
|
+
};
|
|
1457
1456
|
|
|
1458
|
-
// 创建一个可调用的主函数
|
|
1459
|
-
const styimat = function(...args) {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1457
|
+
// 创建一个可调用的主函数
|
|
1458
|
+
const styimat = function(...args) {
|
|
1459
|
+
// 检查是否是模板字符串调用(标签函数)
|
|
1460
|
+
if (args.length > 1 || (args[0] && args[0].raw)) {
|
|
1461
|
+
// 处理模板字符串
|
|
1462
|
+
return handleTemplateTag(...args);
|
|
1463
|
+
}
|
|
1465
1464
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1465
|
+
// 获取第一个参数
|
|
1466
|
+
const firstArg = args[0];
|
|
1468
1467
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1468
|
+
// 如果传入CSS文本,则编译并返回结果
|
|
1469
|
+
if (typeof firstArg === 'string') {
|
|
1470
|
+
return convert(firstArg, { ...defaultConfig, ...args[1] });
|
|
1471
|
+
}
|
|
1473
1472
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1473
|
+
// 如果传入配置对象,则应用配置
|
|
1474
|
+
if (typeof firstArg === 'object' && firstArg !== null) {
|
|
1475
|
+
defaultConfig = { ...defaultConfig, ...firstArg };
|
|
1476
|
+
return styimat;
|
|
1477
|
+
}
|
|
1479
1478
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1479
|
+
// 如果没有参数,执行自动处理
|
|
1480
|
+
if (args.length === 0) {
|
|
1481
|
+
return apply();
|
|
1482
|
+
}
|
|
1484
1483
|
|
|
1485
|
-
|
|
1486
|
-
};
|
|
1484
|
+
return styimat;
|
|
1485
|
+
};
|
|
1487
1486
|
|
|
1488
|
-
// 处理模板字符串的函数
|
|
1489
|
-
function handleTemplateTag(strings, ...values) {
|
|
1490
|
-
|
|
1491
|
-
|
|
1487
|
+
// 处理模板字符串的函数
|
|
1488
|
+
function handleTemplateTag(strings, ...values) {
|
|
1489
|
+
// 拼接模板字符串
|
|
1490
|
+
let cssText = strings[0];
|
|
1492
1491
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1492
|
+
for (let i = 0; i < values.length; i++) {
|
|
1493
|
+
// 处理插值(支持字符串、数字、函数等)
|
|
1494
|
+
const value = values[i];
|
|
1495
|
+
let result = '';
|
|
1497
1496
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1497
|
+
if (typeof value === 'function') {
|
|
1498
|
+
result = value();
|
|
1499
|
+
} else if (Array.isArray(value)) {
|
|
1500
|
+
result = value.join(' ');
|
|
1501
|
+
} else {
|
|
1502
|
+
result = String(value != null ? value : '');
|
|
1503
|
+
}
|
|
1505
1504
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1505
|
+
cssText += result + strings[i + 1];
|
|
1506
|
+
}
|
|
1508
1507
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
}
|
|
1508
|
+
// 使用convert函数处理
|
|
1509
|
+
return convert(cssText, defaultConfig);
|
|
1510
|
+
}
|
|
1512
1511
|
|
|
1513
|
-
// 将API的所有方法复制到主函数上
|
|
1514
|
-
Object.assign(styimat, api);
|
|
1515
|
-
Object.setPrototypeOf(styimat, Function.prototype);
|
|
1512
|
+
// 将API的所有方法复制到主函数上
|
|
1513
|
+
Object.assign(styimat, api);
|
|
1514
|
+
Object.setPrototypeOf(styimat, Function.prototype);
|
|
1516
1515
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1516
|
+
// 自动初始化
|
|
1517
|
+
if (typeof window !== 'undefined') {
|
|
1518
|
+
apply();
|
|
1519
|
+
Object.defineProperty(window.HTMLElement.prototype, 'cssVar', {
|
|
1520
|
+
get() {
|
|
1521
|
+
const element = this;
|
|
1522
|
+
return new Proxy(() => {}, {
|
|
1523
|
+
get(target, prop) {
|
|
1524
|
+
const varName = prop.startsWith('--') ? prop : `--${prop}`;
|
|
1525
|
+
return element.style.getPropertyValue(varName);
|
|
1526
|
+
},
|
|
1527
|
+
|
|
1528
|
+
set(target, prop, value) {
|
|
1529
|
+
const varName = prop.startsWith('--') ? prop : `--${prop}`;
|
|
1530
|
+
element.style.setProperty(varName, value);
|
|
1531
|
+
return true;
|
|
1532
|
+
},
|
|
1533
|
+
|
|
1534
|
+
apply(target, thisArg, argumentsList) {
|
|
1535
|
+
const prop = argumentsList[0];
|
|
1536
|
+
const value = argumentsList[1];
|
|
1537
|
+
const varName = prop.startsWith('--') ? prop : `--${prop}`;
|
|
1538
|
+
if (value === undefined) return element.style.getPropertyValue(varName);
|
|
1539
|
+
element.style.setProperty(varName, value);
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1534
1543
|
});
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
return styimat;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
return styimat;
|
|
1540
1547
|
}));
|