@ax-design/reveal-highlight 0.3.0-alpha.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +0 -8
  2. package/build/BaseConfig.d.ts +43 -0
  3. package/build/BaseConfig.d.ts.map +1 -0
  4. package/build/CanvasConfig.d.ts +7 -92
  5. package/build/CanvasConfig.d.ts.map +1 -1
  6. package/build/CustomElements.d.ts +18 -4
  7. package/build/CustomElements.d.ts.map +1 -1
  8. package/build/Profiler.d.ts.map +1 -1
  9. package/build/RevealBoundryStore.d.ts +50 -10
  10. package/build/RevealBoundryStore.d.ts.map +1 -1
  11. package/build/RevealStateManager.d.ts +3 -0
  12. package/build/RevealStateManager.d.ts.map +1 -1
  13. package/build/SvgConfig.d.ts +17 -0
  14. package/build/SvgConfig.d.ts.map +1 -0
  15. package/build/index.d.ts.map +1 -1
  16. package/build/main.js +169 -17
  17. package/build/utils/extractRGBValue.d.ts +2 -0
  18. package/build/utils/extractRGBValue.d.ts.map +1 -0
  19. package/build/utils/isValidateBorderDecorationType.d.ts +3 -0
  20. package/build/utils/isValidateBorderDecorationType.d.ts.map +1 -0
  21. package/build/utils/types.d.ts +103 -0
  22. package/build/utils/types.d.ts.map +1 -0
  23. package/build/variables.d.ts +1 -1
  24. package/build/variables.d.ts.map +1 -1
  25. package/docs/demo/index.html +70 -143
  26. package/docs/demo/ng.html +992 -0
  27. package/es/BaseConfig.d.ts +43 -0
  28. package/es/BaseConfig.d.ts.map +1 -0
  29. package/es/BaseConfig.js +220 -0
  30. package/es/BaseConfig.js.map +1 -0
  31. package/es/CanvasConfig.d.ts +7 -92
  32. package/es/CanvasConfig.d.ts.map +1 -1
  33. package/es/CanvasConfig.js +35 -233
  34. package/es/CanvasConfig.js.map +1 -1
  35. package/es/CustomElements.d.ts +18 -4
  36. package/es/CustomElements.d.ts.map +1 -1
  37. package/es/CustomElements.js +82 -7
  38. package/es/CustomElements.js.map +1 -1
  39. package/es/Profiler.d.ts.map +1 -1
  40. package/es/Profiler.js +0 -2
  41. package/es/Profiler.js.map +1 -1
  42. package/es/RevealBoundryStore.d.ts +50 -10
  43. package/es/RevealBoundryStore.d.ts.map +1 -1
  44. package/es/RevealBoundryStore.js +129 -35
  45. package/es/RevealBoundryStore.js.map +1 -1
  46. package/es/RevealStateManager.d.ts +3 -0
  47. package/es/RevealStateManager.d.ts.map +1 -1
  48. package/es/RevealStateManager.js +35 -8
  49. package/es/RevealStateManager.js.map +1 -1
  50. package/es/SvgConfig.d.ts +17 -0
  51. package/es/SvgConfig.d.ts.map +1 -0
  52. package/es/SvgConfig.js +346 -0
  53. package/es/SvgConfig.js.map +1 -0
  54. package/es/index.d.ts.map +1 -1
  55. package/es/index.js +15 -21
  56. package/es/index.js.map +1 -1
  57. package/es/utils/extractRGBValue.d.ts +2 -0
  58. package/es/utils/extractRGBValue.d.ts.map +1 -0
  59. package/es/utils/extractRGBValue.js +48 -0
  60. package/es/utils/extractRGBValue.js.map +1 -0
  61. package/es/utils/isValidateBorderDecorationType.d.ts +3 -0
  62. package/es/utils/isValidateBorderDecorationType.d.ts.map +1 -0
  63. package/es/utils/isValidateBorderDecorationType.js +10 -0
  64. package/es/utils/isValidateBorderDecorationType.js.map +1 -0
  65. package/es/utils/types.d.ts +103 -0
  66. package/es/utils/types.d.ts.map +1 -0
  67. package/es/utils/types.js +2 -0
  68. package/es/utils/types.js.map +1 -0
  69. package/es/variables.d.ts +1 -1
  70. package/es/variables.d.ts.map +1 -1
  71. package/es/variables.js +1 -1
  72. package/es/variables.js.map +1 -1
  73. package/package.json +30 -30
  74. package/src/BaseConfig.ts +311 -0
  75. package/src/CanvasConfig.ts +66 -377
  76. package/src/CustomElements.ts +97 -14
  77. package/src/Profiler.ts +0 -3
  78. package/src/RevealBoundryStore.ts +138 -39
  79. package/src/RevealStateManager.ts +40 -10
  80. package/src/SvgConfig.ts +422 -0
  81. package/src/index.ts +15 -21
  82. package/src/utils/extractRGBValue.ts +58 -0
  83. package/src/utils/isValidateBorderDecorationType.ts +8 -0
  84. package/src/utils/types.ts +106 -0
  85. package/src/variables.ts +1 -1
  86. package/.yarnrc.yml +0 -1
@@ -7,14 +7,14 @@ class ServerSideHTMLElement {
7
7
  innerHTML: '',
8
8
  querySelector: (_x: string) => null,
9
9
  }
10
-
10
+
11
11
  attachShadow = () => null;
12
12
  }
13
13
 
14
- const PatchedHTMLElement =
15
- typeof globalThis.Window === 'undefined'
16
- ? ServerSideHTMLElement as unknown as typeof HTMLElement
17
- : HTMLElement;
14
+ const PatchedHTMLElement =
15
+ typeof globalThis.Window === 'undefined'
16
+ ? ServerSideHTMLElement as unknown as typeof HTMLElement
17
+ : HTMLElement;
18
18
 
19
19
  export class AxRevealProvider extends PatchedHTMLElement {
20
20
  static readonly ElementName = 'ax-reveal-provider';
@@ -77,7 +77,11 @@ export class AxRevealBoundary extends PatchedHTMLElement {
77
77
  this.storage = stateManager.newBoundary(this);
78
78
  }
79
79
 
80
- updatePointerPosition = (ev: MouseEvent) => {
80
+ /**
81
+ * Update the position of your pointer.
82
+ * @param ev Pointer event from the listener.
83
+ */
84
+ updatePointerPosition = (ev: PointerEvent) => {
81
85
  this.waitForStorage(storage => {
82
86
  storage.clientX = ev.clientX;
83
87
  storage.clientY = ev.clientY;
@@ -86,9 +90,16 @@ export class AxRevealBoundary extends PatchedHTMLElement {
86
90
 
87
91
  handlePointerEnter = () => this.waitForStorage(storage => storage.onPointerEnterBoundary());
88
92
  handlePointerLeave = () => this.waitForStorage(storage => storage.onPointerLeaveBoundary());
89
- handlePointerMove = (ev: MouseEvent) => this.updatePointerPosition(ev);
90
- handlePointerUp = () => this.waitForStorage(storage => storage.switchAnimation());
91
- handlePointerDown = (ev: MouseEvent) => this.waitForStorage(storage => {
93
+ handlePointerMove = this.updatePointerPosition;
94
+ handlePointerUp = (ev: PointerEvent) => {
95
+ if (ev.pointerType === 'mouse') {
96
+ this.waitForStorage(storage => storage.switchAnimation());
97
+ } else {
98
+ this.waitForStorage(storage => storage.clearAll(false));
99
+ }
100
+ };
101
+ handlePointerDown = (ev: PointerEvent) => this.waitForStorage(storage => {
102
+ this.setPointerCapture(ev.pointerId);
92
103
  this.updatePointerPosition(ev);
93
104
  storage.initializeAnimation();
94
105
  });
@@ -135,7 +146,7 @@ export class AxReveal extends PatchedHTMLElement {
135
146
  connectedCallback() {
136
147
  this.boundary = this.closest(AxRevealBoundary.ElementName) as AxRevealBoundary;
137
148
  if (!this.boundary)
138
- throw new SyntaxError('You must use ' + AxRevealBoundary.ElementName + ' as the boundary of acrylic!');
149
+ throw new SyntaxError('You must use ' + AxRevealBoundary.ElementName + ' as the boundary of reveal highlight!');
139
150
 
140
151
  this.boundary.waitForStorage(storage => setTimeout(() => storage.addReveal(this.canvas, this), 0));
141
152
  }
@@ -143,12 +154,14 @@ export class AxReveal extends PatchedHTMLElement {
143
154
  constructor() {
144
155
  super();
145
156
  this.root.innerHTML = `
146
- <div>
147
- <slot></slot>
157
+ <div class="ax-reveal">
148
158
  <canvas></canvas>
149
- </div>
159
+ <div class="content"><slot></slot></div>
160
+ </div>
150
161
  <style>
151
- div { display: content; }
162
+ .ax-reveal { display: content; }
163
+ .ax-reveal * { user-drag: none; touch-action: none; user-select: none; }
164
+ .content { position: relative; }
152
165
  canvas { top: 0; left: 0; pointer-events: none; width: 100%; height: 100%; position: absolute; }
153
166
  :host { display: inline-block; position: relative; }
154
167
  :host([block]) { display: block; }
@@ -157,8 +170,78 @@ export class AxReveal extends PatchedHTMLElement {
157
170
  :host([inline-flex]) { display: inline-flex; }
158
171
  :host([grid]) { display: grid; }
159
172
  :host([inline-grid]) { display: inline-grid; }
173
+ ::slotted(*) { user-drag: none; touch-action: none; }
160
174
  ::slotted(button) { outline:none; }
161
175
  </style>`;
162
176
  this.canvas = this.root.querySelector('canvas')!;
163
177
  }
164
178
  }
179
+
180
+ export class AxRevealNg extends PatchedHTMLElement {
181
+ static readonly ElementName = 'ax-reveal-ng';
182
+ private root = this.attachShadow({ mode: 'open' });
183
+ private svg: SVGElement;
184
+ private boundary!: AxRevealBoundary;
185
+
186
+ adoptedCallback() {
187
+ this.disconnectedCallback();
188
+ this.connectedCallback();
189
+ }
190
+
191
+ disconnectedCallback() {
192
+ this.boundary && this.boundary.waitForStorage(storage => storage.removeReveal(this.svg));
193
+ }
194
+
195
+ connectedCallback() {
196
+ this.boundary = this.closest(AxRevealBoundary.ElementName) as AxRevealBoundary;
197
+ if (!this.boundary)
198
+ throw new SyntaxError('You must use ' + AxRevealBoundary.ElementName + ' as the boundary of reveal highlight!');
199
+
200
+ this.boundary.waitForStorage(storage => setTimeout(() => storage.addReveal(this.svg, this), 0));
201
+ }
202
+
203
+ constructor() {
204
+ super();
205
+ this.root.innerHTML = `
206
+ <div class="ax-reveal">
207
+ <svg>
208
+ <defs>
209
+ <radialGradient id="borderGrad" gradientUnits="userSpaceOnUse" r="10">
210
+ <stop id="borderCenter" offset="0%" />
211
+ <stop id="borderOut" offset="100%" />
212
+ </radialGradient>
213
+ <radialGradient id="fillGrad" gradientUnits="userSpaceOnUse" r="10">
214
+ <stop id="fillCenter" offset="0%" />
215
+ <stop id="fillOut" offset="100%" />
216
+ </radialGradient>
217
+ <radialGradient id="rippleGrad" gradientUnits="userSpaceOnUse" r="10">
218
+ <stop id="rippleCenter" offset="0%" />
219
+ <stop id="rippleMiddle" offset="0%" />
220
+ <stop id="rippleOut" offset="0%" />
221
+ </radialGradient>
222
+ </defs>
223
+ <path id="borderPath" fill="url(#borderGrad)" />
224
+ <path id="fillPath" fill="url(#fillGrad)" />
225
+ <path id="ripplePath" fill="url(#rippleGrad)" />
226
+ </svg>
227
+ <div class="content"><slot></slot></div>
228
+ </div>
229
+ <style>
230
+ .ax-reveal { display: content; }
231
+ .ax-reveal * { user-drag: none; touch-action: none; user-select: none; }
232
+ .content { position: relative; }
233
+ svg { top: 0; left: 0; pointer-events: none; width: 100%; height: 100%; position: absolute; }
234
+ :host { display: inline-block; position: relative; }
235
+ :host([block]) { display: block; }
236
+ :host([inline-block]) { display: inline-block; }
237
+ :host([flex]) { display: flex; }
238
+ :host([inline-flex]) { display: inline-flex; }
239
+ :host([grid]) { display: grid; }
240
+ :host([inline-grid]) { display: inline-grid; }
241
+ ::slotted(*) { user-drag: none; touch-action: none; }
242
+ ::slotted(button) { outline:none; }
243
+ </style>`;
244
+ this.svg = this.root.querySelector('svg')!;
245
+ }
246
+ }
247
+
package/src/Profiler.ts CHANGED
@@ -9,11 +9,8 @@ const downloadText = (filename: string, text: string) => {
9
9
  element.setAttribute('download', filename);
10
10
 
11
11
  element.style.display = 'none';
12
- document.body.appendChild(element);
13
12
 
14
13
  element.click();
15
-
16
- document.body.removeChild(element);
17
14
  };
18
15
 
19
16
  export class Profiler {
@@ -1,4 +1,7 @@
1
1
  import { CanvasConfig } from './CanvasConfig.js';
2
+ import { SvgConfig } from './SvgConfig.js';
3
+
4
+ type Config = CanvasConfig | SvgConfig;
2
5
 
3
6
  export class RevealBoundaryStore {
4
7
  id: number;
@@ -13,11 +16,10 @@ export class RevealBoundaryStore {
13
16
  paintedClientY = -1000;
14
17
 
15
18
  mouseInBoundary = false;
16
- canvasList: CanvasConfig[] = [];
19
+ canvasList: Config[] = [];
17
20
 
18
- animationQueue: CanvasConfig[] = [];
21
+ animationQueue: Config[] = [];
19
22
  animationFrame: number | null = null;
20
- dirty = false;
21
23
 
22
24
  mouseUpClientX = null;
23
25
  mouseUpClientY = null;
@@ -30,84 +32,131 @@ export class RevealBoundaryStore {
30
32
 
31
33
  maxRadius = -1;
32
34
 
35
+ requestedAnimationFrame = false;
36
+
33
37
  constructor(id: number, $el: HTMLElement) {
34
38
  this.id = id;
35
39
  this.container = $el;
36
40
  }
37
41
 
38
- addReveal = ($canvas: HTMLCanvasElement, $container: HTMLElement) => {
39
- const canvasConfig = new CanvasConfig(this, $canvas, $container);
42
+ addReveal = ($element: HTMLCanvasElement | SVGElement, $container: HTMLElement) => {
43
+ let config: Config;
44
+ const elementType = Object.prototype.toString.call($element);
45
+
46
+ if (elementType === '[object HTMLCanvasElement]') {
47
+ config = new CanvasConfig(this, $element as HTMLCanvasElement, $container);
48
+ } else if (elementType === '[object SVGSVGElement]') {
49
+ config = new SvgConfig(this, $element as SVGSVGElement, $container);
50
+ } else {
51
+ throw new TypeError(`Unknown element type, got: ${elementType}, Canvas or SVG element expected.`)
52
+ }
40
53
 
41
- canvasConfig.cacheBoundingRect();
42
- canvasConfig.cacheCanvasPaintingStyle();
43
- canvasConfig.updateCachedBitmap();
54
+ config.cacheBoundingRect();
55
+ config.cacheCanvasPaintingStyle();
56
+ config.updateCachedBitmap();
44
57
 
45
- this.canvasList.push(canvasConfig);
58
+ this.canvasList.push(config);
46
59
  };
47
60
 
48
- removeReveal = ($el: HTMLCanvasElement) => {
61
+ removeReveal = ($el: HTMLCanvasElement | SVGElement) => {
49
62
  this.canvasList = this.canvasList.filter((config) => {
50
- return config && config.canvas === $el;
63
+ return config && config.element !== $el;
51
64
  });
52
65
  };
53
66
 
54
67
  updateMaxRadius = (x: number) => {
55
68
  this.maxRadius = Math.max(x, this.maxRadius);
56
- }
69
+ };
57
70
 
58
- onPointerMoveOnScreen = (x: number, y: number) => {
59
- const boundingBox = this.container.getBoundingClientRect();
71
+ requestPaintAll = (cleanup?: boolean) => {
72
+ if (this.requestedAnimationFrame) return;
73
+ this.requestedAnimationFrame = true;
60
74
 
61
- if (x < boundingBox.left - this.maxRadius) return false;
62
- if (x > boundingBox.right + this.maxRadius) return false;
63
- if (y < boundingBox.top - this.maxRadius) return false;
64
- if (y > boundingBox.bottom + this.maxRadius) return false;
65
- this.mouseInBoundary = true;
75
+ this.animationFrame = window.requestAnimationFrame((frame) => {
76
+ this.requestedAnimationFrame = false;
77
+ this.paintAll(cleanup ? -1 : frame, true);
78
+ });
79
+ };
66
80
 
81
+ updatePointerPosition = (x: number, y: number) => {
67
82
  this.clientX = x;
68
83
  this.clientY = y;
69
84
 
70
- this.animationFrame = window.requestAnimationFrame(this.paintAll);
71
- return true
85
+ const boundingBox = this.container.getBoundingClientRect();
86
+
87
+ let mouseInBoundary = true;
88
+
89
+ if (x < boundingBox.left - this.maxRadius) mouseInBoundary = false;
90
+ if (x > boundingBox.right + this.maxRadius) mouseInBoundary = false;
91
+ if (y < boundingBox.top - this.maxRadius) mouseInBoundary = false;
92
+ if (y > boundingBox.bottom + this.maxRadius) mouseInBoundary = false;
93
+
94
+ this.mouseInBoundary = mouseInBoundary;
72
95
  }
73
96
 
97
+ onPointerMoveOnScreen = (x: number, y: number) => {
98
+ this.updatePointerPosition(x, y);
99
+ if (!this.mouseInBoundary) return;
100
+ this.requestPaintAll();
101
+ return true;
102
+ };
103
+
104
+ /**
105
+ * Repaint all canvas while user's pointer has entered the boundary.
106
+ */
74
107
  onPointerEnterBoundary = () => {
75
108
  this.mouseInBoundary = true;
76
109
 
77
110
  if (!this.animationFrame) {
78
- this.animationFrame = window.requestAnimationFrame(this.paintAll);
111
+ this.requestPaintAll();
79
112
  }
80
113
  };
81
114
 
115
+ /**
116
+ * Clear all canvas while user's pointer leave the bounary.
117
+ */
82
118
  onPointerLeaveBoundary = () => {
83
119
  this.mouseInBoundary = false;
84
- this.paintAll(0, true);
120
+ this.requestPaintAll(true);
85
121
  };
86
122
 
87
- paintAll = (frame: number, force?: boolean) => {
88
- if (!force && !this.mouseInBoundary && this.animationQueue.length === 0) {
123
+ /**
124
+ * The painting method that draw everything on your screen.
125
+ * @param frame current frame index, if this value is 0, it means all canvas
126
+ * should be cleaned.
127
+ * @param skipSamePointerPositionCheck Literally.
128
+ */
129
+ private paintAll = (frame: number, skipSamePointerPositionCheck?: boolean) => {
130
+ if (!skipSamePointerPositionCheck && !this.mouseInBoundary && this.animationQueue.length === 0) {
89
131
  return;
90
132
  }
91
133
 
134
+ // Animation duration checkup
92
135
  for (let i = 0; i < this.animationQueue.length; i++) {
93
136
  const config = this.animationQueue[i];
94
137
 
95
138
  if (!frame || config.currentFrameId === frame) {
96
- return;
139
+ continue;
97
140
  }
98
141
 
99
142
  config.currentFrameId = frame;
100
143
 
144
+ // Initialize the animation parameter
101
145
  if (config.mouseDownAnimateStartFrame === null) {
102
146
  config.mouseDownAnimateStartFrame = frame;
103
147
  }
104
148
 
149
+ // Calculate how long the animation has been playing
105
150
  const relativeFrame = frame - config.mouseDownAnimateStartFrame;
106
151
  config.mouseDownAnimateCurrentFrame = relativeFrame;
107
152
 
108
153
  const speed = config.cachedStyle.pressAnimationSpeed;
109
154
  const accelerateRate = config.cachedStyle.releaseAnimationAccelerateRate;
110
155
 
156
+ // We assume that Ripple is a one-way linear process from
157
+ // no diffusion to fully diffused,
158
+ // and Logic Frame represents the progress of the animation
159
+ // in this process.
111
160
  let unitLogicFrame = relativeFrame;
112
161
  if (config.mouseReleased && config.mouseDownAnimateReleasedFrame) {
113
162
  unitLogicFrame += (relativeFrame - config.mouseDownAnimateReleasedFrame) * accelerateRate;
@@ -115,34 +164,51 @@ export class RevealBoundaryStore {
115
164
  config.mouseDownAnimateLogicFrame = unitLogicFrame / speed;
116
165
 
117
166
  if (config.mouseDownAnimateLogicFrame > 1) {
118
- this.cleanUpAnimation(config);
167
+ // It will be cleaned later
168
+ config.cleanedUpForAnimation = true;
119
169
  }
120
170
  }
121
171
 
172
+ // Paint the canvas one by one
122
173
  for (let i = 0; i < this.canvasList.length; i++) {
123
174
  const config = this.canvasList[i];
124
175
  config.currentFrameId = frame;
125
- config.paint(force);
176
+
177
+ if (config.cleanedUpForAnimation) {
178
+ this.cleanUpAnimationUnit(config, false);
179
+ config.cleanedUpForAnimation = false;
180
+ } else {
181
+ const isPlayingAnimation = this.animationQueue.indexOf(config) === -1;
182
+
183
+ if (this.mouseInBoundary || isPlayingAnimation) {
184
+ config.paint(skipSamePointerPositionCheck);
185
+ } else {
186
+ config.clear(false);
187
+ }
188
+ }
126
189
  }
127
190
 
128
- this.dirty = true;
129
191
  this.paintedClientX = this.clientX;
130
192
  this.paintedClientY = this.clientY;
131
193
 
132
194
  if (this.mouseInBoundary || this.animationQueue.length !== 0) {
133
- this.animationFrame = window.requestAnimationFrame(this.paintAll);
195
+ this.requestPaintAll();
134
196
  } else {
135
197
  this.animationFrame = null;
136
198
  }
137
199
  };
138
200
 
139
- resetAll = () => {
140
- for (let i = 0; i < this.canvasList.length; i++) {
141
- const config = this.canvasList[i];
142
- config.paint();
143
- }
201
+ /**
202
+ * Clear all canvas.
203
+ */
204
+ clearAll = (forAnimation: boolean) => {
205
+ this.cleanupAnimation(forAnimation, true);
144
206
  };
145
207
 
208
+ /**
209
+ * This is the effect while user pressed on the reveal highlight,
210
+ * the ripple start to spread slowly.
211
+ */
146
212
  initializeAnimation = () => {
147
213
  const config = this.canvasList.find((x) => x.mouseInCanvas());
148
214
 
@@ -157,18 +223,43 @@ export class RevealBoundaryStore {
157
223
  config.mouseReleased = false;
158
224
  };
159
225
 
226
+ /**
227
+ * This is the effect while user released it's pointer, the spreading
228
+ * ripple animation will start to accelerate.
229
+ */
160
230
  switchAnimation = () => {
161
- this.animationQueue.forEach((config) => {
231
+ for (let i = 0; i < this.animationQueue.length; i++) {
232
+ const config = this.animationQueue[i];
233
+
162
234
  if (!config.mouseReleased) {
163
235
  config.mouseReleased = true;
164
236
  config.mouseDownAnimateReleasedFrame = config.mouseDownAnimateCurrentFrame;
165
237
  config.mouseUpClientX = this.clientX;
166
238
  config.mouseUpClientY = this.clientY;
167
239
  }
168
- });
240
+ }
169
241
  };
170
242
 
171
- cleanUpAnimation = (config: CanvasConfig) => {
243
+ /**
244
+ * Cleanup all animations.
245
+ * @param skipPaint Only cleanup data or also clean up the canvas
246
+ * @param forceClean Clean the canvas or not
247
+ */
248
+ cleanupAnimation = (skipPaint?: boolean, forceClean?: boolean) => {
249
+ for (let i = 0; i < this.animationQueue.length; i++) {
250
+ const config = this.animationQueue[i];
251
+
252
+ this.cleanUpAnimationUnit(config, skipPaint, forceClean);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Clean up the config of an animation, but the canvas won't cleaned up.
258
+ * @param config The config to be cleaned up.
259
+ * @param skipPaint Skip painting.
260
+ * @param forceClean Force clear the painting.
261
+ */
262
+ cleanUpAnimationUnit = (config: Config, skipPaint?: boolean, forceClean?: boolean) => {
172
263
  config.mouseUpClientX = null;
173
264
  config.mouseUpClientY = null;
174
265
  config.mouseDownAnimateStartFrame = null;
@@ -183,7 +274,15 @@ export class RevealBoundaryStore {
183
274
  this.animationQueue.splice(configIdx, 1);
184
275
  }
185
276
 
186
- config.paint(true);
277
+ if (!skipPaint) {
278
+ if (forceClean) {
279
+ config.clear(false);
280
+ } else if (this.mouseInBoundary) {
281
+ config.paint(true);
282
+ } else {
283
+ config.clear(true);
284
+ }
285
+ }
187
286
  };
188
287
 
189
288
  getRevealAnimationConfig = () => {
@@ -14,24 +14,54 @@ export class RevealStateManager {
14
14
  if (config.borderDetectionMode === 'experimentalAutoFit') {
15
15
  if (typeof Window === 'undefined') return;
16
16
 
17
- window.addEventListener('pointermove', (event) => {
18
- this.cachedMouseX = event.clientX;
19
- this.cachedMouseY = event.clientY;
20
-
21
- if (!this.requestedTraverseBoundaries) {
22
- window.requestAnimationFrame(this.traverseBoundaries);
23
- this.requestedTraverseBoundaries = true;
24
- }
17
+ window.addEventListener('pointermove', this.handlePointerMove);
18
+ // This is for touch screen
19
+ // Window.pointerleave won't be triggered if the finger
20
+ // leave the screen, so we use pointer up here.
21
+ window.addEventListener('pointerup', (ev) => {
22
+ if (ev.pointerType !== 'mouse') this.handlePointerLeave();
23
+ });
24
+ // This is for desktop
25
+ // If the mouse leave the window, we need to terminate
26
+ // all animations and clean up the canvas.
27
+ window.addEventListener('pointerleave', (ev) => {
28
+ if (ev.pointerType === 'mouse') this.handlePointerLeave();
25
29
  });
26
30
  }
27
31
  }
28
32
 
33
+ pointerLeaveScheduled = false;
34
+
35
+ handlePointerLeave = () => {
36
+ if (this.pointerLeaveScheduled) return;
37
+
38
+ this.pointerLeaveScheduled = true;
39
+
40
+ window.requestAnimationFrame(() => {
41
+ for (let i = 0; i < this._storage.length; i++) {
42
+ this._storage[i].onPointerLeaveBoundary();
43
+ }
44
+
45
+ this.pointerLeaveScheduled = false;
46
+ });
47
+ }
48
+
49
+ handlePointerMove = (event: PointerEvent) => {
50
+ this.cachedMouseX = event.clientX;
51
+ this.cachedMouseY = event.clientY;
52
+
53
+ if (!this.requestedTraverseBoundaries) {
54
+ window.requestAnimationFrame(this.traverseBoundaries);
55
+ this.requestedTraverseBoundaries = true;
56
+ }
57
+ };
58
+
29
59
  traverseBoundaries = () => {
30
60
  this.requestedTraverseBoundaries = false;
31
61
  for (let i = 0; i < this._storage.length; i++) {
32
62
  this._storage[i].onPointerMoveOnScreen(this.cachedMouseX, this.cachedMouseY);
33
63
  }
34
- }
64
+ };
35
65
 
36
66
  newBoundary = ($el: HTMLElement) => {
37
67
  const hashId = this._currentHashId++;
@@ -43,6 +73,6 @@ export class RevealStateManager {
43
73
  };
44
74
 
45
75
  removeBoundary = (store: RevealBoundaryStore) => {
46
- this._storage = this._storage.filter(x => x !== store);
76
+ this._storage = this._storage.filter((x) => x !== store);
47
77
  };
48
78
  }