km-card-layout-component-miniprogram 0.1.17 → 0.1.19

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.
Files changed (36) hide show
  1. package/miniprogram_dist/components/card-layout/elements/custom-element/index.wxml +6 -4
  2. package/miniprogram_dist/components/card-layout/elements/icon-element/index.wxml +6 -4
  3. package/miniprogram_dist/components/card-layout/elements/image-element/index.wxml +10 -8
  4. package/miniprogram_dist/components/card-layout/elements/text-element/index.wxml +2 -0
  5. package/miniprogram_dist/components/card-layout/elements/text-element/index.wxss +0 -4
  6. package/miniprogram_dist/components/card-layout/index.js +83 -15
  7. package/miniprogram_dist/components/card-layout/index.json +1 -7
  8. package/miniprogram_dist/components/card-layout/index.wxml +21 -4
  9. package/miniprogram_dist/components/card-layout/index.wxss +83 -0
  10. package/miniprogram_dist/vendor/wxml2canvas-2d/canvas.js +1116 -0
  11. package/miniprogram_dist/vendor/wxml2canvas-2d/constants.js +42 -0
  12. package/miniprogram_dist/vendor/wxml2canvas-2d/element.js +420 -0
  13. package/miniprogram_dist/vendor/wxml2canvas-2d/gradient.js +634 -0
  14. package/miniprogram_dist/vendor/wxml2canvas-2d/index.js +169 -0
  15. package/miniprogram_dist/vendor/wxml2canvas-2d/index.json +4 -0
  16. package/miniprogram_dist/vendor/wxml2canvas-2d/index.wxml +7 -0
  17. package/miniprogram_dist/vendor/wxml2canvas-2d/index.wxss +5 -0
  18. package/package.json +1 -1
  19. package/src/components/card-layout/elements/custom-element/index.wxml +6 -4
  20. package/src/components/card-layout/elements/icon-element/index.wxml +6 -4
  21. package/src/components/card-layout/elements/image-element/index.wxml +10 -8
  22. package/src/components/card-layout/elements/text-element/index.wxml +2 -0
  23. package/src/components/card-layout/elements/text-element/index.wxss +0 -4
  24. package/src/components/card-layout/index.json +1 -7
  25. package/src/components/card-layout/index.ts +108 -16
  26. package/src/components/card-layout/index.wxml +21 -4
  27. package/src/components/card-layout/index.wxss +83 -0
  28. package/src/vendor/km-card-layout-core/types.d.ts +7 -1
  29. package/src/vendor/wxml2canvas-2d/canvas.js +1116 -0
  30. package/src/vendor/wxml2canvas-2d/constants.js +42 -0
  31. package/src/vendor/wxml2canvas-2d/element.js +420 -0
  32. package/src/vendor/wxml2canvas-2d/gradient.js +634 -0
  33. package/src/vendor/wxml2canvas-2d/index.js +169 -0
  34. package/src/vendor/wxml2canvas-2d/index.json +4 -0
  35. package/src/vendor/wxml2canvas-2d/index.wxml +7 -0
  36. package/src/vendor/wxml2canvas-2d/index.wxss +5 -0
@@ -0,0 +1,634 @@
1
+ import {
2
+ TRI2RAD_RATIO, SIDE2CORNER_RATIO, IS_WINDOWS,
3
+ } from './constants';
4
+
5
+ /**
6
+ * 获取 wxml 元素背景渐变类型
7
+ * @param {Element} element wxml 元素
8
+ * @returns {String} 渐变类型
9
+ */
10
+ const getGradientType = (element) => {
11
+ const backgroundImage = element['background-image'];
12
+ if (!backgroundImage || backgroundImage === 'none') return '';
13
+ if (backgroundImage.startsWith('linear-gradient')) return 'linear';
14
+ if (backgroundImage.startsWith('radial-gradient')) return 'radial';
15
+ if (backgroundImage.startsWith('conic-gradient')) return 'conic';
16
+ return '';
17
+ };
18
+
19
+ /**
20
+ * 获取 wxml 元素背景渐变内容
21
+ * @param {String} gradientType 渐变类型
22
+ * @param {String} backgroundImage wxml 元素背景
23
+ * @returns {String} 渐变内容
24
+ */
25
+ const getGradientContent = (gradientType, backgroundImage) => {
26
+ let gradientContent;
27
+ if (gradientType === 'linear') {
28
+ [gradientContent] = backgroundImage.match(
29
+ // 线性渐变语法参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/linear-gradient#%E5%BD%A2%E5%BC%8F%E8%AF%AD%E6%B3%95
30
+ /linear-gradient\(((-?\d+(\.\d+)?(turn|deg|grad|rad))|(to( (left|top|bottom|right))+))?((, )?(((rgba?\(((, )?\d+(\.\d+)?)+\)|red)( -?\d+(\.\d+)?(px|%))*)|(-?\d+(\.\d+)?(px|%))))+\)/,
31
+ ) ?? [];
32
+ } else if (gradientType === 'radial') {
33
+ [gradientContent] = backgroundImage.match(
34
+ // 径向渐变语法参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/radial-gradient#%E5%BD%A2%E5%BC%8F%E8%AF%AD%E6%B3%95
35
+ /radial-gradient\((circle|ellipse)?(( ?(closest-corner|closest-side|farthest-corner|farthest-side))|( ?\d+(\.\d+)?(px|%))+)?( ?at( (left|top|bottom|right|center|(-?\d+(\.\d+)?(px|%))))+)?((, )?(((rgba?\(((, )?\d+(\.\d+)?)+\)|red)( -?\d+(\.\d+)?(px|%))*)|(-?\d+(\.\d+)?(px|%))))+\)/,
36
+ ) ?? [];
37
+ } else if (gradientType === 'conic') {
38
+ [gradientContent] = backgroundImage.match(
39
+ // 锥形渐变语法参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/conic-gradient#%E5%BD%A2%E5%BC%8F%E8%AF%AD%E6%B3%95
40
+ /conic-gradient\((from -?\d+(\.\d+)?(turn|deg|grad|rad))?( ?at( (left|top|bottom|right|center|(-?\d+(\.\d+)?(px|%))))+)?((, )?(((rgba?\(((, )?\d+(\.\d+)?)+\)|red)( -?\d+(\.\d+)?(turn|deg|grad|rad|%))*)|(-?\d+(\.\d+)?(turn|deg|grad|rad|%))))+\)/,
41
+ );
42
+ }
43
+ return gradientContent;
44
+ };
45
+
46
+ /**
47
+ * 生成线性渐变对象
48
+ * @param {CanvasRenderingContext2D} ctx 画布上下文
49
+ * @param {Element} element wxml 元素
50
+ * @returns {CanvasGradient} 渐变对象
51
+ */
52
+ export const createLinearGradient = (ctx, element) => {
53
+ const gradientContent = getGradientContent('linear', element['background-image']);
54
+ if (!gradientContent) return undefined;
55
+ const content = element.getBoxSize('padding');
56
+
57
+ /** 矩形斜边长度 */
58
+ const rectDiagonal = Math.sqrt(content.width ** 2 + content.height ** 2);
59
+
60
+ /** 渐变角度描述内容 */
61
+ const [angleContent] = gradientContent.match(/(-?\d+(\.\d+)?(turn|deg|grad|rad))|(to( (left|top|bottom|right))+)/) ?? [];
62
+ /** 渐变角度,默认 180 度角,即从上往下 */
63
+ let gradientAngle = 180;
64
+ if (/-?\d+(\.\d+)?(turn|deg|grad|rad)/.test(angleContent)) {
65
+ /** 当前单位 的一个完整圆的数值,默认单位:度 */
66
+ let roundAngle = 360;
67
+ /** 角度单位 与 当前单位 的单位换算比例,默认单位:度 */
68
+ let angleRatio = 1;
69
+ if (/turn/.test(angleContent)) { // 圈数
70
+ roundAngle = 1;
71
+ angleRatio = 360 / roundAngle;
72
+ } else if (/grad/.test(angleContent)) { // 百分度数
73
+ roundAngle = 400;
74
+ angleRatio = 360 / roundAngle;
75
+ } else if (/rad/.test(angleContent)) { // 弧度数
76
+ roundAngle = 2 * Math.PI;
77
+ angleRatio = 180 / Math.PI;
78
+ }
79
+ gradientAngle = angleRatio * (parseFloat(angleContent) % roundAngle);
80
+ // 超过 180 度转换为 逆时针角度
81
+ if (gradientAngle > 180) gradientAngle = -(360 - gradientAngle);
82
+ } else if (angleContent === 'to left') { // 从右往左
83
+ gradientAngle = -90;
84
+ } else if (angleContent === 'to right') { // 从左往右
85
+ gradientAngle = 90;
86
+ } else if (angleContent === 'to top') { // 从下往上
87
+ gradientAngle = 0;
88
+ } else if (/( (left|top|bottom|right)){2}/.test(angleContent)) { // 斜上 或 斜下,对角方向
89
+ gradientAngle = (
90
+ /left/.test(angleContent) ? -1 : 1 // 左方向,转换为 逆时针角度
91
+ ) * (90 + (
92
+ /top/.test(angleContent) ? -1 : 1 // 上方向,锐角角度
93
+ ) * (Math.atan(content.width / content.height) / TRI2RAD_RATIO));
94
+ }
95
+
96
+ // 关于线性渐变各点位置以及各个角度的计算
97
+ // 参考官方图例:
98
+ // https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/linear-gradient#%E7%BA%BF%E6%80%A7%E6%B8%90%E5%8F%98%E7%9A%84%E5%90%88%E6%88%90
99
+
100
+ /** 渐变角度是否顺时针 */
101
+ const isClockwise = gradientAngle >= 0;
102
+ /** 渐变角度是否为钝角 */
103
+ const isObtuse = Math.abs(gradientAngle) > 90;
104
+ // 渐变角度 与 水平线 的夹角(锐角)
105
+ const gradientAngleDiffHorizon = 90 - (
106
+ isObtuse
107
+ ? (180 - Math.abs(gradientAngle))
108
+ : Math.abs(gradientAngle)
109
+ );
110
+ /** 渐变角度 与 矩形斜边 的夹角(锐角) */
111
+ const gradientAngleDiffDiagonal = Math.abs(gradientAngle) - Math.abs(
112
+ isObtuse
113
+ ? 90 + Math.atan(content.height / content.width) / TRI2RAD_RATIO
114
+ : Math.atan(content.width / content.height) / TRI2RAD_RATIO,
115
+ );
116
+ /** 渐变射线长度(标准) */
117
+ const gradientDiagonal = Math.cos(gradientAngleDiffDiagonal * TRI2RAD_RATIO) * rectDiagonal;
118
+ /** 实际渐变射线长度(拓展) */
119
+ let realGradientDiagonal = gradientDiagonal;
120
+
121
+ /** 渐变射线上代表起始颜色值的点 */
122
+ const startingPoint = [];
123
+ /** 渐变射线上代表最终颜色值的点 */
124
+ const endingPoint = [];
125
+ /** 渐变起始点的斜边长度 */
126
+ const startingDiagonal = Math.sin(gradientAngleDiffDiagonal * TRI2RAD_RATIO) * (rectDiagonal / 2);
127
+ /** 渐变起始点的 x 坐标 */
128
+ const startingPointX = Math.sin(gradientAngleDiffHorizon * TRI2RAD_RATIO) * startingDiagonal;
129
+ /** 渐变起始点的 y 坐标 */
130
+ const startingPointY = Math.cos(gradientAngleDiffHorizon * TRI2RAD_RATIO) * startingDiagonal;
131
+
132
+ /** 渐变起始点的斜边偏移长度 */
133
+ let startingPrefixDiagonal = 0;
134
+ /** 渐变起始点的 x 坐标偏移 */
135
+ let startingPrefixX = 0;
136
+ /** 渐变起始点的 y 坐标偏移 */
137
+ let startingPrefixY = 0;
138
+ /** 渐变最终点的斜边偏移长度 */
139
+ let endingAffixDiagonal = 0;
140
+ /** 渐变最终点的 x 坐标偏移 */
141
+ let endingAffixX = 0;
142
+ /** 渐变最终点的 y 坐标偏移 */
143
+ let endingAffixY = 0;
144
+
145
+ /** 渐变颜色描述内容 */
146
+ const colorsContent = gradientContent.match(/((rgba?\(((, )?\d+(\.\d+)?)+\)|red)( -?\d+(\.\d+)?(px|%))*)|(-?\d+(\.\d+)?(px|%))/g) ?? [];
147
+ /** 渐变色标位置集合 */
148
+ const colorStops = colorsContent.map((item, index) => {
149
+ /** 渐变色标颜色 */
150
+ const [color] = item.match(/rgba?\(((, )?\d+(\.\d+)?)+\)|red/) ?? [];
151
+ /** 渐变色标位置 */
152
+ const stops = (
153
+ item.match(/-?\d+(\.\d+)?(px|%)/g) ?? []
154
+ ).map((stopItem) => (
155
+ parseFloat(stopItem) * (/%/.test(stopItem) ? gradientDiagonal / 100 : 1)
156
+ ));
157
+ /** 渐变色标信息 */
158
+ const colorStop = { stops };
159
+ if (color) { Object.assign(colorStop, { color }); }
160
+ if (index === 0 && color && stops.length > 0) {
161
+ const length = -stops[0];
162
+ if (length > 0) { // 渐变起始点是否位于渐变射线上
163
+ startingPrefixDiagonal = length;
164
+ realGradientDiagonal += startingPrefixDiagonal;
165
+ startingPrefixX = Math.sin(gradientAngle * TRI2RAD_RATIO) * startingPrefixDiagonal;
166
+ startingPrefixY = Math.cos(gradientAngle * TRI2RAD_RATIO) * startingPrefixDiagonal;
167
+ }
168
+ } else if (index === colorsContent.length - 1 && color && stops.length > 0) {
169
+ const length = stops[stops.length - 1];
170
+ if (length > gradientDiagonal) { // 渐变终止点是否位于渐变射线上
171
+ endingAffixDiagonal = length;
172
+ realGradientDiagonal += endingAffixDiagonal;
173
+ endingAffixX = Math.sin(gradientAngle * TRI2RAD_RATIO) * endingAffixDiagonal;
174
+ endingAffixY = Math.cos(gradientAngle * TRI2RAD_RATIO) * endingAffixDiagonal;
175
+ }
176
+ }
177
+ return colorStop;
178
+ });
179
+
180
+ startingPoint.push(
181
+ (isClockwise ? content.left : content.right)
182
+ + (isObtuse ? 1 : -1) * (isClockwise ? 1 : -1) * startingPointX - startingPrefixX,
183
+ (isObtuse ? content.top : content.bottom) - startingPointY + startingPrefixY,
184
+ );
185
+ endingPoint.push(
186
+ (isClockwise ? content.right : content.left)
187
+ + (isObtuse ? -1 : 1) * (isClockwise ? 1 : -1) * startingPointX + endingAffixX,
188
+ (isObtuse ? content.bottom : content.top) + startingPointY - endingAffixY,
189
+ );
190
+ /** 线性渐变对象 */
191
+ const gradient = ctx.createLinearGradient(...startingPoint, ...endingPoint);
192
+
193
+ for (let index = 0; index < colorStops.length; index++) {
194
+ const item = colorStops[index];
195
+ // 暂不支持控制渐变进程(插值提示)
196
+ if (!item.color) continue;
197
+ if (item.stops.length === 0) {
198
+ if (index === 0) {
199
+ item.stops.push(0); // 渐变起始点默认位置:0
200
+ } else if (index === colorStops.length - 1) {
201
+ item.stops.push(gradientDiagonal); // 渐变终止点默认位置:100%
202
+ } else {
203
+ /** 两个已声明位置信息的色标间,未声明位置信息的色标数量 */
204
+ let stopInter = 1;
205
+ let stopIndex = index;
206
+ /** 上一个渐变色标位置 */
207
+ const prevStop = colorStops[stopIndex - 1].stops.slice(-1)[0];
208
+ /** 下一个渐变色标位置 */
209
+ let nextStop;
210
+ while (++stopIndex < colorStops.length) {
211
+ stopInter += 1;
212
+ if (colorStops[stopIndex].stops.length > 0) {
213
+ [nextStop] = colorStops[stopIndex].stops;
214
+ break;
215
+ }
216
+ }
217
+ /** 当前渐变色标位置 */
218
+ const currentStop = prevStop + ((nextStop ?? 1) - prevStop) / stopInter;
219
+ item.stops.push(currentStop);
220
+ }
221
+ }
222
+ let stopIndex = 0;
223
+ for (; stopIndex < item.stops.length; stopIndex++) {
224
+ const stopItem = item.stops[stopIndex];
225
+ /** 色标位置偏移值 */
226
+ const stopOffset = +Number(
227
+ (startingPrefixDiagonal + stopItem) / realGradientDiagonal,
228
+ ).toFixed(4);
229
+ if (stopOffset > 1 || stopOffset < 0) break;
230
+ gradient.addColorStop(stopOffset, item.color);
231
+ }
232
+ }
233
+ return gradient;
234
+ };
235
+
236
+ /**
237
+ * 生成径向渐变对象
238
+ * @param {CanvasRenderingContext2D} ctx 画布上下文
239
+ * @param {Element} element wxml 元素
240
+ * @returns {CanvasGradient} 渐变对象
241
+ */
242
+ export const createRadialGradient = (ctx, element) => {
243
+ const gradientContent = getGradientContent('radial', element['background-image']);
244
+ if (!gradientContent) return undefined;
245
+ const content = element.getBoxSize('padding');
246
+
247
+ /** 渐变的位置 */
248
+ const [position] = gradientContent.match(/at( (left|top|bottom|right|center|(-?\d+(\.\d+)?(px|%))))+/) ?? [];
249
+ /** 渐变的形状 */
250
+ let [endingShape] = gradientContent.match(/ellipse|circle/) ?? ['ellipse'];
251
+ /** 渐变结束形状的大小描述 */
252
+ const [sizeExtent] = gradientContent.match(/closest-corner|closest-side|farthest-corner|farthest-side/) ?? ['farthest-corner'];
253
+ /** 渐变结束形状的大小数值 */
254
+ const [radialSize] = gradientContent.match(/\(( ?\d+(\.\d+)?(px|%))+/) ?? [];
255
+
256
+ /** 渐变起始位置的 x 坐标 */
257
+ let positionX;
258
+ /** 渐变起始位置的 y 坐标 */
259
+ let positionY;
260
+ if (position) {
261
+ [, positionX, positionY] = position.split(' ');
262
+ if (positionX === 'left') {
263
+ positionX = 0;
264
+ } else if (positionX === 'right') {
265
+ positionX = content.width;
266
+ } else if (positionX === 'center') {
267
+ positionX = content.width / 2;
268
+ } else if (/%/.test(positionX)) {
269
+ positionX = content.width * (parseFloat(positionX) / 100);
270
+ } else {
271
+ positionX = parseFloat(positionX);
272
+ }
273
+ if (positionY === 'top') {
274
+ positionY = 0;
275
+ } else if (positionY === 'bottom') {
276
+ positionY = content.height;
277
+ } else if (positionY === 'center') {
278
+ positionY = content.height / 2;
279
+ } else if (/%/.test(positionY)) {
280
+ positionY = content.height * (parseFloat(positionY) / 100);
281
+ } else {
282
+ positionY = parseFloat(positionY);
283
+ }
284
+ } else {
285
+ positionX = content.width / 2;
286
+ positionY = content.height / 2;
287
+ }
288
+
289
+ /** 渐变形状的 x 轴长度 */
290
+ let radiusX;
291
+ /** 渐变形状的 y 轴长度 */
292
+ let radiusY;
293
+ if (radialSize) {
294
+ [radiusX, radiusY] = radialSize.split(' ');
295
+ radiusX = radiusX.slice(1);
296
+ if (/%/.test(radiusX)) {
297
+ radiusX = content.width * (parseFloat(radiusX) / 100);
298
+ } else {
299
+ radiusX = parseFloat(radiusX);
300
+ }
301
+ if (!radiusY) {
302
+ radiusY = radiusX;
303
+ } else if (/%/.test(radiusY)) {
304
+ radiusY = content.height * (parseFloat(radiusY) / 100);
305
+ } else {
306
+ radiusY = parseFloat(radiusY);
307
+ }
308
+ if (radiusX === radiusY) endingShape = 'circle';
309
+ } else if (sizeExtent === 'closest-side') {
310
+ radiusX = Math.min(Math.abs(positionX), Math.abs(positionX - content.width));
311
+ radiusY = Math.min(Math.abs(positionY), Math.abs(positionY - content.height));
312
+ if (endingShape === 'circle') {
313
+ radiusX = Math.min(radiusX, radiusY);
314
+ radiusY = radiusX;
315
+ }
316
+ } else if (sizeExtent === 'farthest-side') {
317
+ radiusX = Math.max(Math.abs(positionX), Math.abs(positionX - content.width));
318
+ radiusY = Math.max(Math.abs(positionY), Math.abs(positionY - content.height));
319
+ if (endingShape === 'circle') {
320
+ radiusX = Math.max(radiusX, radiusY);
321
+ radiusY = radiusX;
322
+ }
323
+ } else if (sizeExtent === 'closest-corner') {
324
+ radiusX = Math.min(Math.abs(positionX), Math.abs(positionX - content.width));
325
+ radiusY = Math.min(Math.abs(positionY), Math.abs(positionY - content.height));
326
+ if (endingShape === 'circle') {
327
+ radiusX = Math.sqrt(radiusX ** 2 + radiusY ** 2);
328
+ radiusY = radiusX;
329
+ } else {
330
+ radiusX *= SIDE2CORNER_RATIO;
331
+ radiusY *= SIDE2CORNER_RATIO;
332
+ }
333
+ } else if (sizeExtent === 'farthest-corner') {
334
+ radiusX = Math.max(Math.abs(positionX), Math.abs(positionX - content.width));
335
+ radiusY = Math.max(Math.abs(positionY), Math.abs(positionY - content.height));
336
+ if (endingShape === 'circle') {
337
+ radiusX = Math.sqrt(radiusX ** 2 + radiusY ** 2);
338
+ radiusY = radiusX;
339
+ } else {
340
+ radiusX *= SIDE2CORNER_RATIO;
341
+ radiusY *= SIDE2CORNER_RATIO;
342
+ }
343
+ }
344
+
345
+ /** 渐变形状的短轴长度 */
346
+ const radius = Math.min(radiusX, radiusY);
347
+ /** 实际径向渐变射线长度 */
348
+ let realRadius = radius;
349
+
350
+ /** 渐变颜色描述内容 */
351
+ // 正则还存在问题,会把前面位置描述匹配到,但暂不影响结果
352
+ const colorsContent = gradientContent.match(/((rgba?\(((, )?\d+(\.\d+)?)+\)|red)( -?\d+(\.\d+)?(px|%))*)|(-?\d+(\.\d+)?(px|%))/g) ?? [];
353
+ /** 渐变色标位置集合 */
354
+ const colorStops = colorsContent.map((item) => {
355
+ /** 渐变色标颜色 */
356
+ const [color] = item.match(/rgba?\(((, )?\d+(\.\d+)?)+\)|red/) ?? [];
357
+ /** 渐变色标位置 */
358
+ const stops = (
359
+ item.match(/-?\d+(\.\d+)?(px|%)/g) ?? []
360
+ ).map((stopItem) => (
361
+ parseFloat(stopItem) * (/%/.test(stopItem) ? radius / 100 : 1)
362
+ ));
363
+ /** 渐变色标信息 */
364
+ const colorStop = { stops };
365
+ if (color) { Object.assign(colorStop, { color }); }
366
+ if (colorStop.stops[colorStop.stops.length - 1] > radius) {
367
+ realRadius = colorStop.stops[colorStop.stops.length - 1];
368
+ }
369
+ return colorStop;
370
+ });
371
+
372
+ // 由于 Canvas 径向渐变只允许生成圆形径向渐变
373
+ // 所以设置对应轴缩放比例,生成椭圆形径向渐变
374
+ /** x 轴缩放比例 */
375
+ const scaleX = radiusX / radius;
376
+ /** y 轴缩放比例 */
377
+ const scaleY = radiusY / radius;
378
+ /** 缩放后渐变起始位置的 x 坐标 */
379
+ const scaledLeft = (content.left + positionX) / scaleX;
380
+ /** 缩放后渐变起始位置的 y 坐标 */
381
+ const scaledTop = (content.top + positionY) / scaleY;
382
+ /** 径向渐变对象 */
383
+ const gradient = ctx.createRadialGradient(
384
+ scaledLeft, scaledTop, 0,
385
+ scaledLeft, scaledTop, realRadius,
386
+ );
387
+
388
+ for (let index = 0; index < colorStops.length; index++) {
389
+ const item = colorStops[index];
390
+ // 暂不支持控制渐变进程(插值提示)
391
+ if (!item.color) continue;
392
+ if (item.stops.length === 0) {
393
+ if (index === 0) {
394
+ item.stops.push(0); // 渐变起始点默认位置:0
395
+ } else if (index === colorStops.length - 1) {
396
+ item.stops.push(realRadius); // 渐变终止点默认位置:100%
397
+ } else {
398
+ /** 两个已声明位置信息的色标间,未声明位置信息的色标数量 */
399
+ let stopInter = 1;
400
+ let stopIndex = index;
401
+ /** 上一个渐变色标位置 */
402
+ const prevStop = colorStops[stopIndex - 1].stops.slice(-1)[0];
403
+ /** 下一个渐变色标位置 */
404
+ let nextStop;
405
+ while (++stopIndex < colorStops.length) {
406
+ stopInter += 1;
407
+ if (colorStops[stopIndex].stops.length > 0) {
408
+ [nextStop] = colorStops[stopIndex].stops;
409
+ break;
410
+ }
411
+ }
412
+ /** 当前渐变色标位置 */
413
+ const currentStop = prevStop + ((nextStop ?? 1) - prevStop) / stopInter;
414
+ item.stops.push(currentStop);
415
+ }
416
+ }
417
+ let stopIndex = 0;
418
+ for (; stopIndex < item.stops.length; stopIndex++) {
419
+ const stopItem = item.stops[stopIndex];
420
+ /** 色标位置偏移值 */
421
+ const stopOffset = +Number(
422
+ stopItem / realRadius,
423
+ ).toFixed(4);
424
+ if (stopOffset > 1 || stopOffset < 0) break;
425
+ gradient.addColorStop(stopOffset, item.color);
426
+ }
427
+ }
428
+ return {
429
+ gradient,
430
+ scale: {
431
+ x: scaleX,
432
+ y: scaleY,
433
+ },
434
+ };
435
+ };
436
+
437
+ /**
438
+ * 生成锥形渐变对象
439
+ * @param {CanvasRenderingContext2D} ctx 画布上下文
440
+ * @param {Element} element wxml 元素
441
+ * @returns {CanvasGradient} 渐变对象
442
+ */
443
+ export const createConicGradient = (ctx, element) => {
444
+ const gradientContent = getGradientContent('conic', element['background-image']);
445
+ if (!gradientContent) return undefined;
446
+ const content = element.getBoxSize('padding');
447
+
448
+ /** 渐变的角度 */
449
+ const [angle] = gradientContent.match(/from -?\d+(\.\d+)?(turn|deg|grad|rad)/) ?? [];
450
+ /** 渐变的位置 */
451
+ const [position] = gradientContent.match(/at( (left|top|bottom|right|center|(-?\d+(\.\d+)?(px|%))))+/) ?? [];
452
+
453
+ /** 渐变的起始角度 */
454
+ let startAngle = 0;
455
+ if (angle) {
456
+ [, startAngle] = angle.split(' ');
457
+ if (/deg/.test(startAngle)) {
458
+ startAngle = parseFloat(startAngle) * TRI2RAD_RATIO;
459
+ } else if (/turn/.test(startAngle)) {
460
+ startAngle = parseFloat(startAngle) * 360 * TRI2RAD_RATIO;
461
+ } else if (/grad/.test(startAngle)) {
462
+ startAngle = parseFloat(startAngle) * 0.9 * TRI2RAD_RATIO;
463
+ } else {
464
+ startAngle = parseFloat(startAngle);
465
+ }
466
+ }
467
+
468
+ // Windows 渐变起始角度校准
469
+ if (IS_WINDOWS) {
470
+ startAngle -= 0.5 * Math.PI;
471
+ }
472
+
473
+ /** 渐变起始位置的 x 坐标 */
474
+ let positionX;
475
+ /** 渐变起始位置的 y 坐标 */
476
+ let positionY;
477
+ if (position) {
478
+ [, positionX, positionY] = position.split(' ');
479
+ if (positionX === 'left') {
480
+ positionX = 0;
481
+ } else if (positionX === 'right') {
482
+ positionX = content.width;
483
+ } else if (positionX === 'center') {
484
+ positionX = content.width / 2;
485
+ } else if (/%/.test(positionX)) {
486
+ positionX = content.width * (parseFloat(positionX) / 100);
487
+ } else {
488
+ positionX = parseFloat(positionX);
489
+ }
490
+ if (positionY === 'top') {
491
+ positionY = 0;
492
+ } else if (positionY === 'bottom') {
493
+ positionY = content.height;
494
+ } else if (positionY === 'center') {
495
+ positionY = content.height / 2;
496
+ } else if (/%/.test(positionY)) {
497
+ positionY = content.height * (parseFloat(positionY) / 100);
498
+ } else {
499
+ positionY = parseFloat(positionY);
500
+ }
501
+ } else {
502
+ positionX = content.width / 2;
503
+ positionY = content.height / 2;
504
+ }
505
+
506
+ /** 渐变颜色描述内容 */
507
+ // 正则还存在问题,会把前面位置描述匹配到,但暂不影响结果
508
+ const colorsContent = gradientContent.match(/((rgba?\(((, )?\d+(\.\d+)?)+\)|red)( -?\d+(\.\d+)?(turn|deg|grad|rad|%))*)|(-?\d+(\.\d+)?(turn|deg|grad|rad|%))/g) ?? [];
509
+ /** 渐变起始角度校准 */
510
+ let angleOffset = 0;
511
+ /** 渐变色标位置集合 */
512
+ const colorStops = colorsContent.map((item) => {
513
+ /** 渐变色标颜色 */
514
+ const [color] = item.match(/rgba?\(((, )?\d+(\.\d+)?)+\)|red/) ?? [];
515
+ /** 渐变色标位置 */
516
+ const stops = (
517
+ item.match(/-?\d+(\.\d+)?(turn|deg|grad|rad|%)/g) ?? []
518
+ ).map((stopItem) => {
519
+ let stopAngle;
520
+ if (/deg/.test(stopItem)) {
521
+ stopAngle = parseFloat(stopItem) * TRI2RAD_RATIO;
522
+ } else if (/turn/.test(stopItem)) {
523
+ stopAngle = parseFloat(stopItem) * 360 * TRI2RAD_RATIO;
524
+ } else if (/grad/.test(stopItem)) {
525
+ stopAngle = parseFloat(stopItem) * 0.9 * TRI2RAD_RATIO;
526
+ } else if (/%/.test(stopItem)) {
527
+ stopAngle = (parseFloat(stopItem) / 100) * 2 * Math.PI;
528
+ } else {
529
+ stopAngle = parseFloat(stopItem);
530
+ }
531
+ if (angleOffset === 0 && color && stopAngle < 0) {
532
+ angleOffset = -stopAngle;
533
+ }
534
+ return stopAngle + angleOffset;
535
+ });
536
+ /** 渐变色标信息 */
537
+ const colorStop = { stops };
538
+ if (color) { Object.assign(colorStop, { color }); }
539
+ return colorStop;
540
+ });
541
+
542
+ /** 锥形渐变对象 */
543
+ // 起始角度的单位:Windows 角度、开发工具 弧度
544
+ const gradient = ctx.createConicGradient(
545
+ IS_WINDOWS ? startAngle : startAngle / TRI2RAD_RATIO,
546
+ content.left + positionX, content.top + positionY,
547
+ );
548
+
549
+ for (let index = 0; index < colorStops.length; index++) {
550
+ const item = colorStops[index];
551
+ // 暂不支持控制渐变进程(插值提示)
552
+ if (!item.color) continue;
553
+ if (item.stops.length === 0) {
554
+ if (index === 0) {
555
+ item.stops.push(0); // 渐变起始点默认位置:0
556
+ } else if (index === colorStops.length - 1) {
557
+ item.stops.push(2 * Math.PI); // 渐变终止点默认位置:100%
558
+ } else {
559
+ /** 两个已声明位置信息的色标间,未声明位置信息的色标数量 */
560
+ let stopInter = 1;
561
+ let stopIndex = index;
562
+ /** 上一个渐变色标位置 */
563
+ const prevStop = colorStops[stopIndex - 1].stops.slice(-1)[0];
564
+ /** 下一个渐变色标位置 */
565
+ let nextStop;
566
+ while (++stopIndex < colorStops.length) {
567
+ stopInter += 1;
568
+ if (colorStops[stopIndex].stops.length > 0) {
569
+ [nextStop] = colorStops[stopIndex].stops;
570
+ break;
571
+ }
572
+ }
573
+ /** 当前渐变色标位置 */
574
+ const currentStop = prevStop + ((nextStop ?? 1) - prevStop) / stopInter;
575
+ item.stops.push(currentStop);
576
+ }
577
+ }
578
+ let stopIndex = 0;
579
+ for (; stopIndex < item.stops.length; stopIndex++) {
580
+ const stopItem = item.stops[stopIndex];
581
+ /** 色标位置偏移值 */
582
+ const stopOffset = +Number(
583
+ stopItem / (2 * Math.PI),
584
+ ).toFixed(4);
585
+ if (stopOffset > 1 || stopOffset < 0) break;
586
+ gradient.addColorStop(stopOffset, item.color);
587
+ }
588
+ }
589
+ return gradient;
590
+ };
591
+
592
+ /**
593
+ * 生成并绘制渐变对象
594
+ * @param {CanvasRenderingContext2D} ctx 画布上下文
595
+ * @param {Element} element wxml 元素
596
+ * @returns {CanvasGradient} 渐变对象
597
+ */
598
+ export const drawGradient = (ctx, element) => {
599
+ const gradientType = getGradientType(element);
600
+ let gradient;
601
+ let scaleX = 1;
602
+ let scaleY = 1;
603
+
604
+ if (gradientType === 'linear') {
605
+ if (!ctx.createLinearGradient) return;
606
+ gradient = createLinearGradient(ctx, element);
607
+ } else if (gradientType === 'radial') {
608
+ if (!ctx.createRadialGradient) return;
609
+ const radial = createRadialGradient(ctx, element);
610
+ if (!radial) return;
611
+ gradient = radial.gradient;
612
+ scaleX = radial.scale.x;
613
+ scaleY = radial.scale.y;
614
+ } else if (gradientType === 'conic') {
615
+ if (!ctx.createConicGradient) return;
616
+ gradient = createConicGradient(ctx, element);
617
+ }
618
+
619
+ if (!gradient) return;
620
+ ctx.fillStyle = gradient;
621
+ ctx.scale(scaleX, scaleY);
622
+ ctx.fillRect(
623
+ scaleX > 1
624
+ ? element.left / scaleX - element.width / 2 + element.width / scaleX / 2
625
+ : element.left,
626
+ scaleY > 1
627
+ ? element.top / scaleY - element.height / 2 + element.height / scaleY / 2
628
+ : element.top,
629
+ element.width,
630
+ element.height,
631
+ );
632
+ // 恢复画布比例
633
+ ctx.scale(1 / scaleX, 1 / scaleY);
634
+ };