@ax-design/reveal-highlight 0.5.2 → 0.5.3

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 (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +75 -75
  3. package/build/BaseConfig.d.ts +5 -1
  4. package/build/BaseConfig.d.ts.map +1 -1
  5. package/build/CanvasConfig.d.ts +7 -1
  6. package/build/CanvasConfig.d.ts.map +1 -1
  7. package/build/ComputedStyleStorage.d.ts.map +1 -1
  8. package/build/CustomElements.d.ts +20 -4
  9. package/build/CustomElements.d.ts.map +1 -1
  10. package/build/RevealBoundryStore.d.ts +4 -0
  11. package/build/RevealBoundryStore.d.ts.map +1 -1
  12. package/build/RevealStateManager.d.ts +5 -1
  13. package/build/RevealStateManager.d.ts.map +1 -1
  14. package/build/main.js +113 -82
  15. package/build/utils/types.d.ts +2 -10
  16. package/build/utils/types.d.ts.map +1 -1
  17. package/es/BaseConfig.d.ts +46 -42
  18. package/es/BaseConfig.d.ts.map +1 -1
  19. package/es/BaseConfig.js +241 -219
  20. package/es/BaseConfig.js.map +1 -1
  21. package/es/CanvasConfig.d.ts +21 -15
  22. package/es/CanvasConfig.d.ts.map +1 -1
  23. package/es/CanvasConfig.js +332 -320
  24. package/es/CanvasConfig.js.map +1 -1
  25. package/es/ComputedStyleStorage.d.ts +8 -8
  26. package/es/ComputedStyleStorage.d.ts.map +1 -1
  27. package/es/ComputedStyleStorage.js +68 -58
  28. package/es/ComputedStyleStorage.js.map +1 -1
  29. package/es/CustomElements.d.ts +75 -59
  30. package/es/CustomElements.d.ts.map +1 -1
  31. package/es/CustomElements.js +296 -233
  32. package/es/CustomElements.js.map +1 -1
  33. package/es/Profiler.d.ts +12 -12
  34. package/es/Profiler.js +31 -31
  35. package/es/RevealBoundryStore.d.ts +80 -76
  36. package/es/RevealBoundryStore.d.ts.map +1 -1
  37. package/es/RevealBoundryStore.js +269 -248
  38. package/es/RevealBoundryStore.js.map +1 -1
  39. package/es/RevealStateManager.d.ts +19 -15
  40. package/es/RevealStateManager.d.ts.map +1 -1
  41. package/es/RevealStateManager.js +99 -73
  42. package/es/RevealStateManager.js.map +1 -1
  43. package/es/SvgConfig.d.ts +16 -16
  44. package/es/SvgConfig.js +345 -345
  45. package/es/config.d.ts +4 -4
  46. package/es/config.js +21 -21
  47. package/es/index.d.ts +5 -5
  48. package/es/index.js +205 -205
  49. package/es/tsconfig.tsbuildinfo +1 -1
  50. package/es/utils/extractRGBValue.d.ts +1 -1
  51. package/es/utils/extractRGBValue.js +47 -47
  52. package/es/utils/isValidateBorderDecorationType.d.ts +2 -2
  53. package/es/utils/isValidateBorderDecorationType.js +9 -9
  54. package/es/utils/types.d.ts +94 -102
  55. package/es/utils/types.d.ts.map +1 -1
  56. package/es/utils/types.js +1 -1
  57. package/es/variables.d.ts +1 -1
  58. package/es/variables.js +1 -1
  59. package/package.json +36 -35
  60. package/src/BaseConfig.ts +332 -310
  61. package/src/CanvasConfig.ts +420 -415
  62. package/src/ComputedStyleStorage.ts +102 -89
  63. package/src/CustomElements.ts +344 -272
  64. package/src/Profiler.ts +44 -44
  65. package/src/RevealBoundryStore.ts +315 -291
  66. package/src/RevealStateManager.ts +109 -86
  67. package/src/SvgConfig.ts +422 -422
  68. package/src/config.ts +26 -26
  69. package/src/cssTypedOM.d.ts +52 -52
  70. package/src/index.ts +219 -219
  71. package/src/utils/extractRGBValue.ts +58 -58
  72. package/src/utils/isValidateBorderDecorationType.ts +8 -8
  73. package/src/utils/types.ts +97 -105
  74. package/src/variables.ts +1 -1
@@ -1,415 +1,420 @@
1
- import { RevealBoundaryStore } from './RevealBoundryStore.js';
2
-
3
- import type { CachedRevealBitmap } from './utils/types.js';
4
-
5
- import { BaseConfig } from './BaseConfig.js';
6
-
7
- const createEmptyCanvas = () => document.createElement('canvas') as HTMLCanvasElement;
8
-
9
- export class CanvasConfig extends BaseConfig<HTMLCanvasElement> {
10
- ctx: CanvasRenderingContext2D | null;
11
-
12
- protected cachedImg: CachedRevealBitmap;
13
-
14
- constructor(store: RevealBoundaryStore, $canvas: HTMLCanvasElement, $container: HTMLElement) {
15
- super(store, $canvas, $container);
16
-
17
- this.ctx = $canvas.getContext('2d');
18
-
19
- const cachedCanvas = {
20
- borderReveal: createEmptyCanvas(),
21
- fillReveal: createEmptyCanvas(),
22
- };
23
-
24
- const cachedCtx = {
25
- borderReveal: cachedCanvas.borderReveal.getContext('2d'),
26
- fillReveal: cachedCanvas.fillReveal.getContext('2d'),
27
- };
28
-
29
- this.cachedImg = {
30
- cachedReveal: {
31
- borderReveal: {
32
- radiusFactor: -1,
33
- radius: -1,
34
- color: 'INVALID',
35
- opacity: 0,
36
- pattern: null,
37
- },
38
- fillReveal: {
39
- radiusFactor: -1,
40
- radius: -1,
41
- color: 'INVALID',
42
- opacity: 0,
43
- pattern: null,
44
- },
45
- },
46
- cachedCanvas,
47
- cachedCtx,
48
- };
49
- }
50
-
51
- updateCachedReveal = () => {
52
- if (!this.fillDirty) return;
53
-
54
- const c = this.cachedStyle;
55
- const radius = c.trueFillRadius[1] * this.pxRatio;
56
- const size = radius * 2;
57
-
58
- const lastStyle = this.cachedImg.cachedReveal;
59
-
60
- const { borderReveal, fillReveal } = this.cachedImg.cachedCanvas;
61
- const { borderReveal: borderRevealCtx, fillReveal: fillRevealCtx } = this.cachedImg.cachedCtx;
62
-
63
- this._store.updateMaxRadius(c.trueFillRadius[1] * c.borderFillRadius);
64
-
65
- for (let i = 0; i < 2; i++) {
66
- // 0 means border, 1 means hover light.
67
- //-----------------------------------------
68
- // Last rendered style.
69
- const ls = i === 0 ? lastStyle.borderReveal : lastStyle.fillReveal;
70
- const color = i === 0 ? c.borderColor : c.hoverLightColor;
71
- const fillRadiusFactor = i === 0 ? c.borderFillRadius : c.hoverLightFillRadius;
72
-
73
- const doNotNeedUpdateReveal =
74
- color === ls.color && c.opacity === ls.opacity && fillRadiusFactor === ls.radiusFactor;
75
-
76
- if (doNotNeedUpdateReveal) continue;
77
-
78
- const reveal = i === 0 ? borderReveal : fillReveal;
79
- const ctx = i === 0 ? borderRevealCtx : fillRevealCtx;
80
-
81
- this.syncSizeToRevealRadius(reveal, fillRadiusFactor);
82
-
83
- if (!ctx) continue;
84
-
85
- ctx.clearRect(0, 0, reveal.width, reveal.height);
86
-
87
- const fillAlpha = i === 0 ? c.opacity : c.opacity * 0.5;
88
- const trueRadius = radius * fillRadiusFactor;
89
- const grd = ctx.createRadialGradient(trueRadius, trueRadius, 0, trueRadius, trueRadius, trueRadius);
90
-
91
- grd.addColorStop(0, `rgba(${color}, ${fillAlpha})`);
92
- grd.addColorStop(1, `rgba(${color}, 0.0)`);
93
-
94
- const trueSize = size * fillRadiusFactor;
95
- ctx.fillStyle = grd;
96
- ctx.clearRect(0, 0, trueSize, trueSize);
97
- ctx.fillRect(0, 0, trueSize, trueSize);
98
-
99
- ls.radius = trueRadius;
100
- ls.radiusFactor = fillRadiusFactor;
101
- ls.color = color;
102
- ls.opacity = c.opacity;
103
-
104
- if (!this.ctx) continue;
105
- ls.pattern = this.ctx.createPattern(reveal, 'no-repeat');
106
- }
107
-
108
- this.fillDirty = false;
109
- };
110
-
111
- updateCachedBitmap = () => {
112
- if (!this.ctx) return;
113
- this.updateCachedReveal();
114
- };
115
-
116
- private updateAnimateGrd = (frame: number, grd: CanvasGradient) => {
117
- const { pressAnimationColor, opacity } = this.cachedStyle;
118
-
119
- const _innerAlpha = opacity * (0.2 - frame);
120
- const _outerAlpha = opacity * (0.1 - frame * 0.07);
121
- const _outerBorder = 0.1 + frame * 0.8;
122
-
123
- const innerAlpha = _innerAlpha < 0 ? 0 : _innerAlpha;
124
- const outerAlpha = _outerAlpha < 0 ? 0 : _outerAlpha;
125
- let outerBorder = 0;
126
- outerBorder = _outerBorder > 1 ? 1 : _outerBorder;
127
- outerBorder = _outerBorder < 0 ? 0 : _outerBorder;
128
-
129
- grd.addColorStop(0, `rgba(${pressAnimationColor},${innerAlpha})`);
130
- grd.addColorStop(outerBorder * 0.55, `rgba(${pressAnimationColor},${outerAlpha})`);
131
- grd.addColorStop(outerBorder, `rgba(${pressAnimationColor}, 0)`);
132
- };
133
-
134
- private drawShape = (hollow: boolean) => {
135
- if (!this.ctx) return;
136
-
137
- const b = this.cachedBoundingRect;
138
- const w = b.width * this.pxRatio;
139
- const h = b.height * this.pxRatio;
140
-
141
- const c = this.cachedStyle;
142
- const bw = c.borderWidth * this.pxRatio;
143
-
144
- const tl = c.topLeftBorderDecorationRadius * this.pxRatio;
145
- const tr = c.topRightBorderDecorationRadius * this.pxRatio;
146
- const bl = c.bottomLeftBorderDecorationRadius * this.pxRatio;
147
- const br = c.bottomRightBorderDecorationRadius * this.pxRatio;
148
-
149
- const wlf = c.withLeftBorderFactor;
150
- const wrf = c.withRightBorderFactor;
151
- const wtf = c.withTopBorderFactor;
152
- const wbf = c.withBottomBorderFactor;
153
-
154
- const tl2 = bw < tl ? tl - bw : 0;
155
- const tr2 = bw < tr ? tr - bw : 0;
156
- const bl2 = bw < bl ? bl - bw : 0;
157
- const br2 = bw < br ? br - bw : 0;
158
-
159
- this.ctx.beginPath();
160
-
161
- switch (c.borderDecorationType) {
162
- // Oh... Fuck... It's painful...
163
- case 'round':
164
- // outer
165
- this.ctx.moveTo(tl, 0);
166
- this.ctx.arcTo(w, 0, w, h, tr);
167
- this.ctx.lineTo(w, h - br);
168
- this.ctx.arcTo(w, h, br, h, br);
169
- this.ctx.lineTo(bl, h);
170
- this.ctx.arcTo(0, h, 0, br, bl);
171
- this.ctx.lineTo(0, tl);
172
- this.ctx.arcTo(0, 0, tl, 0, tl);
173
-
174
- if (hollow) {
175
- /* prettier-ignore-start */
176
- // inner
177
- /**
178
- +-①----⑧-+
179
- ② ⑦
180
- | |
181
- ③ ⑥
182
- +-④----⑤-+
183
- */
184
- // x y r p
185
- this.ctx.moveTo(tl2, bw * wtf); // ①
186
- this.ctx.arcTo(bw * wlf, bw * wtf, bw * wlf, tl2 + bw * wtf, tl2); // ②
187
- this.ctx.lineTo(bw * wlf, h - bw * wbf - bl2); // ③
188
- this.ctx.arcTo(bw * wlf, h - bw * wbf, bw * wlf + bl2, h - bw * wbf, bl2); // ④
189
- this.ctx.lineTo(w - bw * wrf - br2, h - bw * wbf); // ⑤
190
- this.ctx.arcTo(w - bw * wrf, h - bw * wbf, w - bw * wrf, h - bw * wbf - br2, br2); // ⑥
191
- this.ctx.lineTo(w - bw * wrf, bw * wtf + tr2); // ⑦
192
- this.ctx.arcTo(w - bw * wrf, bw * wtf, w - bw * wrf - tr2, bw * wtf, tr2); // ⑧
193
- this.ctx.lineTo(tl2, bw * wtf); // ①
194
- /* prettier-ignore-end */
195
- }
196
- break;
197
- case 'bevel':
198
- // outer
199
- this.ctx.moveTo(tl, 0);
200
- this.ctx.lineTo(w - tr, 0);
201
- this.ctx.lineTo(w, tr);
202
- this.ctx.lineTo(w, h - br);
203
- this.ctx.lineTo(w - br, h);
204
- this.ctx.lineTo(bl, h);
205
- this.ctx.lineTo(0, h - bl);
206
- this.ctx.lineTo(0, tl);
207
- this.ctx.lineTo(tl, 0);
208
- this.ctx.lineTo(w - tr, 0);
209
-
210
- if (hollow) {
211
- // inner
212
- /**
213
- +-①----⑧-+
214
- ② ⑦
215
- | |
216
- ③ ⑥
217
- +-④----⑤-+
218
- */
219
- // x y p
220
- this.ctx.moveTo(tl2 + bw * wlf, bw * wtf); // ①
221
- this.ctx.lineTo(bw * wlf, tl2 + bw * wtf); // ②
222
- this.ctx.lineTo(bw * wlf, h - bl2 - bw * wbf); // ③
223
- this.ctx.lineTo(bw * wlf + bl2, h - bw * wbf); // ④
224
- this.ctx.lineTo(w - br2 - bw * wrf, h - bw * wbf); // ⑤
225
- this.ctx.lineTo(w - bw * wrf, h - br2 - bw * wbf); // ⑥
226
- this.ctx.lineTo(w - bw * wrf, tr2 + bw * wtf); // ⑦
227
- this.ctx.lineTo(w - tr2 - bw * wrf, bw * wtf); // ⑧
228
- this.ctx.lineTo(tl2 + bw * wrf, bw * wtf); // ①
229
- }
230
- break;
231
- case 'miter':
232
- // outer
233
- this.ctx.moveTo(0, 0);
234
- this.ctx.lineTo(w, 0);
235
- this.ctx.lineTo(w, h);
236
- this.ctx.lineTo(0, h);
237
- this.ctx.lineTo(0, 0);
238
-
239
- if (hollow) {
240
- // inner
241
- /**
242
- ①--------④
243
- | |
244
- | |
245
- | |
246
- ②--------③
247
- */
248
- // x y p
249
- this.ctx.moveTo(bw * wlf, bw * wtf); // ①
250
- this.ctx.lineTo(bw * wlf, h - bw * wbf); // ②
251
- this.ctx.lineTo(w - bw * wrf, h - bw * wbf); // ③
252
- this.ctx.lineTo(w - bw * wrf, bw * wtf); // ④
253
- this.ctx.lineTo(bw * wlf, bw * wtf); // ①
254
- }
255
- break;
256
- default:
257
- }
258
-
259
- this.ctx.closePath();
260
- };
261
-
262
- syncSizeToElement = (x: HTMLCanvasElement) => {
263
- const b = this.cachedBoundingRect;
264
-
265
- if (x.width !== b.width || x.height !== b.height) {
266
- x.width = b.width * this.pxRatio;
267
- x.height = b.height * this.pxRatio;
268
- x.style.width = `${b.width}px`;
269
- x.style.height = `${b.height}px`;
270
- }
271
- };
272
-
273
- clear = () => {
274
- if (!this.ctx) return false;
275
- if (!this.dirty) return false;
276
-
277
- const { width, height } = this.cachedBoundingRect;
278
-
279
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
280
- this.ctx.clearRect(
281
- 0,
282
- 0,
283
- width * window.devicePixelRatio,
284
- height * window.devicePixelRatio,
285
- );
286
- this.dirty = false;
287
- return true;
288
- }
289
-
290
- paint = (skipSamePointerPositionCheck?: boolean): boolean => {
291
- const store = this._store;
292
-
293
- const animationPlaying = store.animationQueue.includes(this);
294
- const samePosition = store.clientX === store.paintedClientX && store.clientY === store.paintedClientY;
295
-
296
- if (samePosition && !skipSamePointerPositionCheck && !animationPlaying) {
297
- return false;
298
- }
299
-
300
- if (!this.ctx) return false;
301
-
302
- this.dirty = true;
303
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
304
- this.ctx.clearRect(
305
- 0, 0,
306
- this.paintedWidth,
307
- this.paintedHeight
308
- );
309
-
310
- if (!store.mouseInBoundary && !animationPlaying) {
311
- return false;
312
- }
313
-
314
- if (!this.cachedImg.cachedReveal) {
315
- return false;
316
- }
317
-
318
- this.syncSizeToElement(this.element);
319
- this.cacheBoundingRect();
320
-
321
- const b = this.cachedBoundingRect;
322
-
323
- const relativeX = store.clientX - b.left;
324
- const relativeY = store.clientY - b.top;
325
-
326
- if (Number.isNaN(relativeX) || Number.isNaN(relativeY)) {
327
- return false;
328
- }
329
-
330
- const c = this.cachedStyle;
331
- this.getTrueFillRadius(c.trueFillRadius);
332
-
333
- this.paintedWidth = b.width * this.pxRatio;
334
- this.paintedHeight = b.height * this.pxRatio;
335
-
336
- const maxRadius = c.trueFillRadius[1];
337
-
338
- const inLeftBound = relativeX + maxRadius > 0;
339
- const inRightBound = relativeX - maxRadius < b.width;
340
- const inTopBound = relativeY + maxRadius > 0;
341
- const inBottomBound = relativeY - maxRadius < b.height;
342
-
343
- const mouseInRenderArea = inLeftBound && inRightBound && inTopBound && inBottomBound;
344
-
345
- if (!mouseInRenderArea && !animationPlaying) {
346
- return false;
347
- }
348
-
349
- this.cacheCanvasPaintingStyle();
350
- this.updateCachedBitmap();
351
-
352
- if (store.mouseInBoundary) {
353
- const mouseInCanvas = relativeX > 0 && relativeY > 0 && relativeX < b.width && relativeY < b.height;
354
-
355
- const fillPattern = this.cachedImg.cachedReveal.fillReveal.pattern;
356
- if (c.hoverLight && mouseInCanvas && fillPattern) {
357
- // draw fill.
358
- const radius = this.cachedImg.cachedReveal.fillReveal.radius;
359
- const putX = Math.floor(relativeX * this.pxRatio - radius);
360
- const putY = Math.floor(relativeY * this.pxRatio - radius);
361
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
362
- this.drawShape(false);
363
- this.ctx.translate(putX, putY);
364
- this.ctx.fillStyle = fillPattern;
365
- this.ctx.fill();
366
- }
367
-
368
- const diffuse = (c.diffuse && mouseInRenderArea) || mouseInCanvas;
369
- const borderPattern = this.cachedImg.cachedReveal.borderReveal.pattern;
370
- if (c.borderWidth !== 0 && borderPattern && diffuse) {
371
- // Draw border.
372
- const radius = this.cachedImg.cachedReveal.borderReveal.radius;
373
- const putX = Math.floor(relativeX * this.pxRatio - radius);
374
- const putY = Math.floor(relativeY * this.pxRatio - radius);
375
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
376
- this.drawShape(true);
377
- this.ctx.translate(putX, putY);
378
- this.ctx.fillStyle = borderPattern;
379
- this.ctx.fill();
380
- }
381
- }
382
-
383
- if (c.pressAnimation && this.mousePressed && this.mouseDownAnimateLogicFrame) {
384
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
385
- const grdRadius =
386
- c.pressAnimationFillMode === 'constrained' ? c.trueFillRadius[1] : Math.max(b.width, b.height);
387
- // prettier-ignore
388
- const animateGrd =
389
- this.mouseReleased && this.mouseUpClientX && this.mouseUpClientY
390
- ? this.ctx.createRadialGradient(
391
- (this.mouseUpClientX - b.left) * this.pxRatio,
392
- (this.mouseUpClientY - b.top) * this.pxRatio,
393
- 0,
394
- (this.mouseUpClientX - b.left) * this.pxRatio,
395
- (this.mouseUpClientY - b.top) * this.pxRatio,
396
- grdRadius * this.pxRatio
397
- )
398
- : this.ctx.createRadialGradient(
399
- relativeX * this.pxRatio,
400
- relativeY * this.pxRatio,
401
- 0,
402
- relativeX * this.pxRatio,
403
- relativeY * this.pxRatio,
404
- grdRadius * this.pxRatio
405
- );
406
- this.updateAnimateGrd(this.mouseDownAnimateLogicFrame, animateGrd);
407
-
408
- this.drawShape(false);
409
- this.ctx.fillStyle = animateGrd;
410
- this.ctx.fill();
411
- }
412
-
413
- return true;
414
- };
415
- }
1
+ import { RevealBoundaryStore } from './RevealBoundryStore.js';
2
+
3
+ import type { CachedRevealBitmap } from './utils/types.js';
4
+
5
+ import { BaseConfig } from './BaseConfig.js';
6
+
7
+ export class CanvasConfig extends BaseConfig<HTMLCanvasElement> {
8
+ ctx: CanvasRenderingContext2D | null;
9
+
10
+ protected cachedImg: CachedRevealBitmap;
11
+
12
+ constructor(store: RevealBoundaryStore, $canvas: HTMLCanvasElement, $container: HTMLElement) {
13
+ super(store, $canvas, $container);
14
+
15
+ this.ctx = $canvas.getContext('2d');
16
+
17
+ this.cachedImg = {
18
+ cachedReveal: {
19
+ borderReveal: {
20
+ radiusFactor: -1,
21
+ radius: -1,
22
+ color: 'INVALID',
23
+ opacity: 0,
24
+ pattern: null,
25
+ },
26
+ fillReveal: {
27
+ radiusFactor: -1,
28
+ radius: -1,
29
+ color: 'INVALID',
30
+ opacity: 0,
31
+ pattern: null,
32
+ },
33
+ },
34
+ };
35
+ }
36
+
37
+ updateCachedReveal = () => {
38
+ if (!this.fillDirty) return;
39
+ if (!this.ctx) return;
40
+
41
+ const c = this.cachedStyle;
42
+ const radius = c.trueFillRadius[1] * this.pxRatio;
43
+
44
+ const lastStyle = this.cachedImg.cachedReveal;
45
+
46
+ this._store.updateMaxRadius(c.trueFillRadius[1] * c.borderFillRadius);
47
+
48
+ for (let i = 0; i < 2; i++) {
49
+ // 0 means border, 1 means hover light.
50
+ //-----------------------------------------
51
+ // Last rendered style.
52
+ const ls = i === 0 ? lastStyle.borderReveal : lastStyle.fillReveal;
53
+ const color = i === 0 ? c.borderColor : c.hoverLightColor;
54
+ const fillRadiusFactor = i === 0 ? c.borderFillRadius : c.hoverLightFillRadius;
55
+ const trueRadius = radius * fillRadiusFactor;
56
+
57
+ const doNotNeedUpdateReveal =
58
+ color === ls.color &&
59
+ c.opacity === ls.opacity &&
60
+ fillRadiusFactor === ls.radiusFactor &&
61
+ trueRadius === ls.radius;
62
+
63
+ if (doNotNeedUpdateReveal) continue;
64
+
65
+ // The gradient is centered at (trueRadius, trueRadius), painting
66
+ // translates it to the pointer. A gradient is used instead of a
67
+ // pre-rendered bitmap, which takes `(4 * radius) ^ 2` pixels per
68
+ // element and is enough to exhaust the memory of mobile browsers.
69
+ const fillAlpha = i === 0 ? c.opacity : c.opacity * 0.5;
70
+ const grd = this.ctx.createRadialGradient(trueRadius, trueRadius, 0, trueRadius, trueRadius, trueRadius);
71
+
72
+ grd.addColorStop(0, `rgba(${color}, ${fillAlpha})`);
73
+ grd.addColorStop(1, `rgba(${color}, 0.0)`);
74
+
75
+ ls.radius = trueRadius;
76
+ ls.radiusFactor = fillRadiusFactor;
77
+ ls.color = color;
78
+ ls.opacity = c.opacity;
79
+ ls.pattern = grd;
80
+ }
81
+
82
+ this.fillDirty = false;
83
+ };
84
+
85
+ updateCachedBitmap = () => {
86
+ if (!this.ctx) return;
87
+ this.updateCachedReveal();
88
+ };
89
+
90
+ private updateAnimateGrd = (frame: number, grd: CanvasGradient) => {
91
+ const { pressAnimationColor, opacity } = this.cachedStyle;
92
+
93
+ const _innerAlpha = opacity * (0.2 - frame);
94
+ const _outerAlpha = opacity * (0.1 - frame * 0.07);
95
+ const _outerBorder = 0.1 + frame * 0.8;
96
+
97
+ const innerAlpha = _innerAlpha < 0 ? 0 : _innerAlpha;
98
+ const outerAlpha = _outerAlpha < 0 ? 0 : _outerAlpha;
99
+ let outerBorder = 0;
100
+ outerBorder = _outerBorder > 1 ? 1 : _outerBorder;
101
+ outerBorder = _outerBorder < 0 ? 0 : _outerBorder;
102
+
103
+ grd.addColorStop(0, `rgba(${pressAnimationColor},${innerAlpha})`);
104
+ grd.addColorStop(outerBorder * 0.55, `rgba(${pressAnimationColor},${outerAlpha})`);
105
+ grd.addColorStop(outerBorder, `rgba(${pressAnimationColor}, 0)`);
106
+ };
107
+
108
+ private drawShape = (hollow: boolean) => {
109
+ if (!this.ctx) return;
110
+
111
+ const b = this.cachedBoundingRect;
112
+ const w = b.width * this.pxRatio;
113
+ const h = b.height * this.pxRatio;
114
+
115
+ const c = this.cachedStyle;
116
+ const bw = c.borderWidth * this.pxRatio;
117
+
118
+ const tl = c.topLeftBorderDecorationRadius * this.pxRatio;
119
+ const tr = c.topRightBorderDecorationRadius * this.pxRatio;
120
+ const bl = c.bottomLeftBorderDecorationRadius * this.pxRatio;
121
+ const br = c.bottomRightBorderDecorationRadius * this.pxRatio;
122
+
123
+ const wlf = c.withLeftBorderFactor;
124
+ const wrf = c.withRightBorderFactor;
125
+ const wtf = c.withTopBorderFactor;
126
+ const wbf = c.withBottomBorderFactor;
127
+
128
+ const tl2 = bw < tl ? tl - bw : 0;
129
+ const tr2 = bw < tr ? tr - bw : 0;
130
+ const bl2 = bw < bl ? bl - bw : 0;
131
+ const br2 = bw < br ? br - bw : 0;
132
+
133
+ this.ctx.beginPath();
134
+
135
+ switch (c.borderDecorationType) {
136
+ // Oh... Fuck... It's painful...
137
+ case 'round':
138
+ // outer
139
+ this.ctx.moveTo(tl, 0);
140
+ this.ctx.arcTo(w, 0, w, h, tr);
141
+ this.ctx.lineTo(w, h - br);
142
+ this.ctx.arcTo(w, h, br, h, br);
143
+ this.ctx.lineTo(bl, h);
144
+ this.ctx.arcTo(0, h, 0, br, bl);
145
+ this.ctx.lineTo(0, tl);
146
+ this.ctx.arcTo(0, 0, tl, 0, tl);
147
+
148
+ if (hollow) {
149
+ /* prettier-ignore-start */
150
+ // inner
151
+ /**
152
+ +-①----⑧-+
153
+ ② ⑦
154
+ | |
155
+ ③ ⑥
156
+ +-④----⑤-+
157
+ */
158
+ // x y r p
159
+ this.ctx.moveTo(tl2, bw * wtf); // ①
160
+ this.ctx.arcTo(bw * wlf, bw * wtf, bw * wlf, tl2 + bw * wtf, tl2); // ②
161
+ this.ctx.lineTo(bw * wlf, h - bw * wbf - bl2); // ③
162
+ this.ctx.arcTo(bw * wlf, h - bw * wbf, bw * wlf + bl2, h - bw * wbf, bl2); // ④
163
+ this.ctx.lineTo(w - bw * wrf - br2, h - bw * wbf); // ⑤
164
+ this.ctx.arcTo(w - bw * wrf, h - bw * wbf, w - bw * wrf, h - bw * wbf - br2, br2); // ⑥
165
+ this.ctx.lineTo(w - bw * wrf, bw * wtf + tr2); // ⑦
166
+ this.ctx.arcTo(w - bw * wrf, bw * wtf, w - bw * wrf - tr2, bw * wtf, tr2); // ⑧
167
+ this.ctx.lineTo(tl2, bw * wtf); // ①
168
+ /* prettier-ignore-end */
169
+ }
170
+ break;
171
+ case 'bevel':
172
+ // outer
173
+ this.ctx.moveTo(tl, 0);
174
+ this.ctx.lineTo(w - tr, 0);
175
+ this.ctx.lineTo(w, tr);
176
+ this.ctx.lineTo(w, h - br);
177
+ this.ctx.lineTo(w - br, h);
178
+ this.ctx.lineTo(bl, h);
179
+ this.ctx.lineTo(0, h - bl);
180
+ this.ctx.lineTo(0, tl);
181
+ this.ctx.lineTo(tl, 0);
182
+ this.ctx.lineTo(w - tr, 0);
183
+
184
+ if (hollow) {
185
+ // inner
186
+ /**
187
+ +-①----⑧-+
188
+ ② ⑦
189
+ | |
190
+ ③ ⑥
191
+ +-④----⑤-+
192
+ */
193
+ // x y p
194
+ this.ctx.moveTo(tl2 + bw * wlf, bw * wtf); // ①
195
+ this.ctx.lineTo(bw * wlf, tl2 + bw * wtf); // ②
196
+ this.ctx.lineTo(bw * wlf, h - bl2 - bw * wbf); // ③
197
+ this.ctx.lineTo(bw * wlf + bl2, h - bw * wbf); // ④
198
+ this.ctx.lineTo(w - br2 - bw * wrf, h - bw * wbf); // ⑤
199
+ this.ctx.lineTo(w - bw * wrf, h - br2 - bw * wbf); // ⑥
200
+ this.ctx.lineTo(w - bw * wrf, tr2 + bw * wtf); // ⑦
201
+ this.ctx.lineTo(w - tr2 - bw * wrf, bw * wtf); // ⑧
202
+ this.ctx.lineTo(tl2 + bw * wrf, bw * wtf); // ①
203
+ }
204
+ break;
205
+ case 'miter':
206
+ // outer
207
+ this.ctx.moveTo(0, 0);
208
+ this.ctx.lineTo(w, 0);
209
+ this.ctx.lineTo(w, h);
210
+ this.ctx.lineTo(0, h);
211
+ this.ctx.lineTo(0, 0);
212
+
213
+ if (hollow) {
214
+ // inner
215
+ /**
216
+ ①--------④
217
+ | |
218
+ | |
219
+ | |
220
+ ②--------③
221
+ */
222
+ // x y p
223
+ this.ctx.moveTo(bw * wlf, bw * wtf); // ①
224
+ this.ctx.lineTo(bw * wlf, h - bw * wbf); // ②
225
+ this.ctx.lineTo(w - bw * wrf, h - bw * wbf); // ③
226
+ this.ctx.lineTo(w - bw * wrf, bw * wtf); // ④
227
+ this.ctx.lineTo(bw * wlf, bw * wtf); // ①
228
+ }
229
+ break;
230
+ default:
231
+ }
232
+
233
+ this.ctx.closePath();
234
+ };
235
+
236
+ syncSizeToElement = (x: HTMLCanvasElement) => {
237
+ const b = this.cachedBoundingRect;
238
+
239
+ // Compare against the backing store size; assigning `width` or
240
+ // `height` resets the bitmap even when the value is unchanged.
241
+ const width = Math.floor(b.width * this.pxRatio);
242
+ const height = Math.floor(b.height * this.pxRatio);
243
+
244
+ if (x.width !== width || x.height !== height) {
245
+ x.width = width;
246
+ x.height = height;
247
+ x.style.width = `${b.width}px`;
248
+ x.style.height = `${b.height}px`;
249
+ }
250
+ };
251
+
252
+ // Inputs of the last painted frame, a frame with identical inputs is
253
+ // skipped and the canvas keeps its content.
254
+ private paintedRelativeX = NaN;
255
+ private paintedRelativeY = NaN;
256
+ private paintedRectWidth = -1;
257
+ private paintedRectHeight = -1;
258
+ private paintedStyleVersion = -1;
259
+ private paintedAnimation = false;
260
+
261
+ clear = () => {
262
+ if (!this.ctx) return false;
263
+ if (!this.dirty) return false;
264
+
265
+ const { width, height } = this.cachedBoundingRect;
266
+
267
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
268
+ this.ctx.clearRect(
269
+ 0,
270
+ 0,
271
+ width * window.devicePixelRatio,
272
+ height * window.devicePixelRatio,
273
+ );
274
+ this.dirty = false;
275
+ return true;
276
+ }
277
+
278
+ paint = (_skipSamePointerPositionCheck?: boolean): boolean => {
279
+ const store = this._store;
280
+
281
+ if (!this.ctx) return false;
282
+
283
+ const animationPlaying = store.animationQueue.includes(this);
284
+
285
+ if (!store.mouseInBoundary && !animationPlaying) {
286
+ this.clear();
287
+ return false;
288
+ }
289
+
290
+ if (!this.cachedImg.cachedReveal) {
291
+ return false;
292
+ }
293
+
294
+ // Read layout before any write to the canvas, so the read never
295
+ // forces a synchronous layout.
296
+ this.cacheBoundingRect();
297
+
298
+ const b = this.cachedBoundingRect;
299
+
300
+ const relativeX = store.clientX - b.left;
301
+ const relativeY = store.clientY - b.top;
302
+
303
+ if (Number.isNaN(relativeX) || Number.isNaN(relativeY)) {
304
+ return false;
305
+ }
306
+
307
+ const c = this.cachedStyle;
308
+ this.getTrueFillRadius(c.trueFillRadius);
309
+
310
+ const maxRadius = c.trueFillRadius[1];
311
+
312
+ const inLeftBound = relativeX + maxRadius > 0;
313
+ const inRightBound = relativeX - maxRadius < b.width;
314
+ const inTopBound = relativeY + maxRadius > 0;
315
+ const inBottomBound = relativeY - maxRadius < b.height;
316
+
317
+ const mouseInRenderArea = inLeftBound && inRightBound && inTopBound && inBottomBound;
318
+
319
+ if (!mouseInRenderArea && !animationPlaying) {
320
+ this.clear();
321
+ return false;
322
+ }
323
+
324
+ this.cacheCanvasPaintingStyle();
325
+
326
+ const unchanged =
327
+ this.dirty &&
328
+ !animationPlaying &&
329
+ !this.paintedAnimation &&
330
+ relativeX === this.paintedRelativeX &&
331
+ relativeY === this.paintedRelativeY &&
332
+ b.width === this.paintedRectWidth &&
333
+ b.height === this.paintedRectHeight &&
334
+ this.styleVersion === this.paintedStyleVersion;
335
+
336
+ if (unchanged) {
337
+ return false;
338
+ }
339
+
340
+ this.syncSizeToElement(this.element);
341
+ this.updateCachedBitmap();
342
+
343
+ this.dirty = true;
344
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
345
+ this.ctx.clearRect(0, 0, this.paintedWidth, this.paintedHeight);
346
+
347
+ this.paintedWidth = b.width * this.pxRatio;
348
+ this.paintedHeight = b.height * this.pxRatio;
349
+
350
+ this.paintedRelativeX = relativeX;
351
+ this.paintedRelativeY = relativeY;
352
+ this.paintedRectWidth = b.width;
353
+ this.paintedRectHeight = b.height;
354
+ this.paintedStyleVersion = this.styleVersion;
355
+ this.paintedAnimation = animationPlaying;
356
+
357
+ if (store.mouseInBoundary) {
358
+ const mouseInCanvas = relativeX > 0 && relativeY > 0 && relativeX < b.width && relativeY < b.height;
359
+
360
+ const fillPattern = this.cachedImg.cachedReveal.fillReveal.pattern;
361
+ if (c.hoverLight && mouseInCanvas && fillPattern) {
362
+ // draw fill.
363
+ const radius = this.cachedImg.cachedReveal.fillReveal.radius;
364
+ const putX = Math.floor(relativeX * this.pxRatio - radius);
365
+ const putY = Math.floor(relativeY * this.pxRatio - radius);
366
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
367
+ this.drawShape(false);
368
+ this.ctx.translate(putX, putY);
369
+ this.ctx.fillStyle = fillPattern;
370
+ this.ctx.fill();
371
+ }
372
+
373
+ const diffuse = (c.diffuse && mouseInRenderArea) || mouseInCanvas;
374
+ const borderPattern = this.cachedImg.cachedReveal.borderReveal.pattern;
375
+ if (c.borderWidth !== 0 && borderPattern && diffuse) {
376
+ // Draw border.
377
+ const radius = this.cachedImg.cachedReveal.borderReveal.radius;
378
+ const putX = Math.floor(relativeX * this.pxRatio - radius);
379
+ const putY = Math.floor(relativeY * this.pxRatio - radius);
380
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
381
+ this.drawShape(true);
382
+ this.ctx.translate(putX, putY);
383
+ this.ctx.fillStyle = borderPattern;
384
+ this.ctx.fill();
385
+ }
386
+ }
387
+
388
+ if (c.pressAnimation && this.mousePressed && this.mouseDownAnimateLogicFrame) {
389
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
390
+ const grdRadius =
391
+ c.pressAnimationFillMode === 'constrained' ? c.trueFillRadius[1] : Math.max(b.width, b.height);
392
+ // prettier-ignore
393
+ const animateGrd =
394
+ this.mouseReleased && this.mouseUpClientX && this.mouseUpClientY
395
+ ? this.ctx.createRadialGradient(
396
+ (this.mouseUpClientX - b.left) * this.pxRatio,
397
+ (this.mouseUpClientY - b.top) * this.pxRatio,
398
+ 0,
399
+ (this.mouseUpClientX - b.left) * this.pxRatio,
400
+ (this.mouseUpClientY - b.top) * this.pxRatio,
401
+ grdRadius * this.pxRatio
402
+ )
403
+ : this.ctx.createRadialGradient(
404
+ relativeX * this.pxRatio,
405
+ relativeY * this.pxRatio,
406
+ 0,
407
+ relativeX * this.pxRatio,
408
+ relativeY * this.pxRatio,
409
+ grdRadius * this.pxRatio
410
+ );
411
+ this.updateAnimateGrd(this.mouseDownAnimateLogicFrame, animateGrd);
412
+
413
+ this.drawShape(false);
414
+ this.ctx.fillStyle = animateGrd;
415
+ this.ctx.fill();
416
+ }
417
+
418
+ return true;
419
+ };
420
+ }