@ax-design/reveal-highlight 0.3.0-alpha.8 → 0.4.2
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 +8 -167
- package/build/BaseConfig.d.ts +43 -0
- package/build/BaseConfig.d.ts.map +1 -0
- package/build/CanvasConfig.d.ts +7 -92
- package/build/CanvasConfig.d.ts.map +1 -1
- package/build/CustomElements.d.ts +20 -4
- package/build/CustomElements.d.ts.map +1 -1
- package/build/RevealBoundryStore.d.ts +50 -10
- package/build/RevealBoundryStore.d.ts.map +1 -1
- package/build/RevealStateManager.d.ts +3 -0
- package/build/RevealStateManager.d.ts.map +1 -1
- package/build/SvgConfig.d.ts +17 -0
- package/build/SvgConfig.d.ts.map +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/main.js +169 -17
- package/build/utils/extractRGBValue.d.ts +2 -0
- package/build/utils/extractRGBValue.d.ts.map +1 -0
- package/build/utils/isValidateBorderDecorationType.d.ts +3 -0
- package/build/utils/isValidateBorderDecorationType.d.ts.map +1 -0
- package/build/utils/types.d.ts +103 -0
- package/build/utils/types.d.ts.map +1 -0
- package/es/BaseConfig.d.ts +43 -0
- package/es/BaseConfig.d.ts.map +1 -0
- package/es/BaseConfig.js +220 -0
- package/es/BaseConfig.js.map +1 -0
- package/es/CanvasConfig.d.ts +7 -92
- package/es/CanvasConfig.d.ts.map +1 -1
- package/es/CanvasConfig.js +35 -233
- package/es/CanvasConfig.js.map +1 -1
- package/es/CustomElements.d.ts +20 -4
- package/es/CustomElements.d.ts.map +1 -1
- package/es/CustomElements.js +115 -18
- package/es/CustomElements.js.map +1 -1
- package/es/RevealBoundryStore.d.ts +50 -10
- package/es/RevealBoundryStore.d.ts.map +1 -1
- package/es/RevealBoundryStore.js +129 -35
- package/es/RevealBoundryStore.js.map +1 -1
- package/es/RevealStateManager.d.ts +3 -0
- package/es/RevealStateManager.d.ts.map +1 -1
- package/es/RevealStateManager.js +43 -8
- package/es/RevealStateManager.js.map +1 -1
- package/es/SvgConfig.d.ts +17 -0
- package/es/SvgConfig.d.ts.map +1 -0
- package/es/SvgConfig.js +346 -0
- package/es/SvgConfig.js.map +1 -0
- package/es/index.d.ts.map +1 -1
- package/es/index.js +10 -16
- package/es/index.js.map +1 -1
- package/es/tsconfig.tsbuildinfo +1 -0
- package/es/utils/extractRGBValue.d.ts +2 -0
- package/es/utils/extractRGBValue.d.ts.map +1 -0
- package/es/utils/extractRGBValue.js +48 -0
- package/es/utils/extractRGBValue.js.map +1 -0
- package/es/utils/isValidateBorderDecorationType.d.ts +3 -0
- package/es/utils/isValidateBorderDecorationType.d.ts.map +1 -0
- package/es/utils/isValidateBorderDecorationType.js +10 -0
- package/es/utils/isValidateBorderDecorationType.js.map +1 -0
- package/es/utils/types.d.ts +103 -0
- package/es/utils/types.d.ts.map +1 -0
- package/es/utils/types.js +2 -0
- package/es/utils/types.js.map +1 -0
- package/package.json +35 -30
- package/src/BaseConfig.ts +311 -0
- package/src/CanvasConfig.ts +66 -380
- package/src/CustomElements.ts +137 -29
- package/src/RevealBoundryStore.ts +138 -39
- package/src/RevealStateManager.ts +48 -10
- package/src/SvgConfig.ts +422 -0
- package/src/index.ts +10 -16
- package/src/utils/extractRGBValue.ts +58 -0
- package/src/utils/isValidateBorderDecorationType.ts +8 -0
- package/src/utils/types.ts +106 -0
- package/.prettierrc +0 -6
- package/.travis.yml +0 -26
- package/.yarnrc.yml +0 -1
- package/docs/demo/debug.html +0 -73
- package/docs/demo/index.html +0 -1065
- package/docs/demo/performance-inspector.html +0 -60
- package/docs/demo/shapes-demo.html +0 -93
- package/docs/demo/single-unit.html +0 -39
- package/docs/logo.png +0 -0
- package/docs/logo.svg +0 -1
- package/docs/screen-record.gif +0 -0
- package/docs/src/logo.afdesign +0 -0
- package/rollup.config.js +0 -14
- package/tsconfig.json +0 -18
package/src/CustomElements.ts
CHANGED
|
@@ -2,19 +2,29 @@ import { RevealStateManager } from './RevealStateManager.js';
|
|
|
2
2
|
import { RevealBoundaryStore } from './RevealBoundryStore.js';
|
|
3
3
|
import { config } from './config.js';
|
|
4
4
|
|
|
5
|
+
function closest(dom: Element, selector: string): Element | null {
|
|
6
|
+
const r = dom.closest(selector);
|
|
7
|
+
if (r) return r;
|
|
8
|
+
const host = findParentShadowRoot(dom)?.host;
|
|
9
|
+
if (!host) return null;
|
|
10
|
+
return closest(host, selector)
|
|
11
|
+
}
|
|
12
|
+
function findParentShadowRoot(x: Node | undefined | null): ShadowRoot | null {
|
|
13
|
+
if (!x) return null;
|
|
14
|
+
if (x.parentNode && x.parentNode.nodeType === document.DOCUMENT_FRAGMENT_NODE) return x.parentNode as ShadowRoot;
|
|
15
|
+
return findParentShadowRoot(x.parentNode);
|
|
16
|
+
}
|
|
5
17
|
class ServerSideHTMLElement {
|
|
6
18
|
root = {
|
|
7
19
|
innerHTML: '',
|
|
8
20
|
querySelector: (_x: string) => null,
|
|
9
|
-
}
|
|
10
|
-
|
|
21
|
+
};
|
|
22
|
+
|
|
11
23
|
attachShadow = () => null;
|
|
12
24
|
}
|
|
13
25
|
|
|
14
|
-
const PatchedHTMLElement =
|
|
15
|
-
typeof globalThis.Window === 'undefined'
|
|
16
|
-
? ServerSideHTMLElement as unknown as typeof HTMLElement
|
|
17
|
-
: HTMLElement;
|
|
26
|
+
const PatchedHTMLElement =
|
|
27
|
+
typeof globalThis.Window === 'undefined' ? ((ServerSideHTMLElement as unknown) as typeof HTMLElement) : HTMLElement;
|
|
18
28
|
|
|
19
29
|
export class AxRevealProvider extends PatchedHTMLElement {
|
|
20
30
|
static readonly ElementName = 'ax-reveal-provider';
|
|
@@ -52,9 +62,11 @@ export class AxRevealBoundary extends PatchedHTMLElement {
|
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
if (oldStorage && newStorage) {
|
|
55
|
-
this.dispatchEvent(
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
this.dispatchEvent(
|
|
66
|
+
new CustomEvent(AxRevealBoundary.replaceStorageEvent, {
|
|
67
|
+
detail: { old: oldStorage, new: newStorage },
|
|
68
|
+
})
|
|
69
|
+
);
|
|
58
70
|
}
|
|
59
71
|
}
|
|
60
72
|
|
|
@@ -71,27 +83,38 @@ export class AxRevealBoundary extends PatchedHTMLElement {
|
|
|
71
83
|
return;
|
|
72
84
|
}
|
|
73
85
|
|
|
74
|
-
const parent =
|
|
86
|
+
const parent = closest(this, AxRevealProvider.ElementName) as AxRevealProvider;
|
|
75
87
|
const stateManager = parent ? parent.stateManager : AxRevealBoundary.stateManager;
|
|
76
88
|
|
|
77
89
|
this.storage = stateManager.newBoundary(this);
|
|
78
90
|
}
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Update the position of your pointer.
|
|
94
|
+
* @param ev Pointer event from the listener.
|
|
95
|
+
*/
|
|
96
|
+
updatePointerPosition = (ev: PointerEvent) => {
|
|
97
|
+
this.waitForStorage((storage) => {
|
|
82
98
|
storage.clientX = ev.clientX;
|
|
83
99
|
storage.clientY = ev.clientY;
|
|
84
100
|
});
|
|
85
101
|
};
|
|
86
102
|
|
|
87
|
-
handlePointerEnter = () => this.waitForStorage(storage => storage.onPointerEnterBoundary());
|
|
88
|
-
handlePointerLeave = () => this.waitForStorage(storage => storage.onPointerLeaveBoundary());
|
|
89
|
-
handlePointerMove =
|
|
90
|
-
handlePointerUp = () =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
handlePointerEnter = () => this.waitForStorage((storage) => storage.onPointerEnterBoundary());
|
|
104
|
+
handlePointerLeave = () => this.waitForStorage((storage) => storage.onPointerLeaveBoundary());
|
|
105
|
+
handlePointerMove = this.updatePointerPosition;
|
|
106
|
+
handlePointerUp = (ev: PointerEvent) => {
|
|
107
|
+
if (ev.pointerType === 'mouse') {
|
|
108
|
+
this.waitForStorage((storage) => storage.switchAnimation());
|
|
109
|
+
} else {
|
|
110
|
+
this.waitForStorage((storage) => storage.clearAll(false));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
handlePointerDown = (ev: PointerEvent) =>
|
|
114
|
+
this.waitForStorage((storage) => {
|
|
115
|
+
this.updatePointerPosition(ev);
|
|
116
|
+
storage.initializeAnimation();
|
|
117
|
+
});
|
|
95
118
|
|
|
96
119
|
connectedCallback() {
|
|
97
120
|
this.appendStorage(true);
|
|
@@ -113,7 +136,7 @@ export class AxRevealBoundary extends PatchedHTMLElement {
|
|
|
113
136
|
this.root.innerHTML = `
|
|
114
137
|
<div>
|
|
115
138
|
<slot></slot>
|
|
116
|
-
</div
|
|
139
|
+
</div>`;
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
142
|
|
|
@@ -128,27 +151,36 @@ export class AxReveal extends PatchedHTMLElement {
|
|
|
128
151
|
this.connectedCallback();
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
handlePointerDown = (ev: PointerEvent) => {
|
|
155
|
+
this.setPointerCapture(ev.pointerId);
|
|
156
|
+
};
|
|
157
|
+
|
|
131
158
|
disconnectedCallback() {
|
|
132
|
-
this.boundary && this.boundary.waitForStorage(storage => storage.removeReveal(this.canvas));
|
|
159
|
+
this.boundary && this.boundary.waitForStorage((storage) => storage.removeReveal(this.canvas));
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
connectedCallback() {
|
|
136
|
-
this.boundary =
|
|
163
|
+
this.boundary = closest(this, AxRevealBoundary.ElementName) as AxRevealBoundary;
|
|
137
164
|
if (!this.boundary)
|
|
138
|
-
throw new SyntaxError(
|
|
165
|
+
throw new SyntaxError(
|
|
166
|
+
'You must use ' + AxRevealBoundary.ElementName + ' as the boundary of reveal highlight!'
|
|
167
|
+
);
|
|
139
168
|
|
|
140
|
-
this.boundary.waitForStorage(storage => setTimeout(() => storage.addReveal(this.canvas, this), 0));
|
|
169
|
+
this.boundary.waitForStorage((storage) => setTimeout(() => storage.addReveal(this.canvas, this), 0));
|
|
170
|
+
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
141
171
|
}
|
|
142
172
|
|
|
143
173
|
constructor() {
|
|
144
174
|
super();
|
|
145
175
|
this.root.innerHTML = `
|
|
146
|
-
<div>
|
|
147
|
-
<slot></slot>
|
|
176
|
+
<div class="ax-reveal">
|
|
148
177
|
<canvas></canvas>
|
|
149
|
-
|
|
178
|
+
<div class="content"><slot></slot></div>
|
|
179
|
+
</div>
|
|
150
180
|
<style>
|
|
151
|
-
|
|
181
|
+
.ax-reveal { display: content; }
|
|
182
|
+
.ax-reveal * { user-drag: none; -webkit-user-drag: none; touch-action: none; user-select: none; }
|
|
183
|
+
.content { position: relative; }
|
|
152
184
|
canvas { top: 0; left: 0; pointer-events: none; width: 100%; height: 100%; position: absolute; }
|
|
153
185
|
:host { display: inline-block; position: relative; }
|
|
154
186
|
:host([block]) { display: block; }
|
|
@@ -157,8 +189,84 @@ export class AxReveal extends PatchedHTMLElement {
|
|
|
157
189
|
:host([inline-flex]) { display: inline-flex; }
|
|
158
190
|
:host([grid]) { display: grid; }
|
|
159
191
|
:host([inline-grid]) { display: inline-grid; }
|
|
192
|
+
::slotted(*) { user-drag: none; -webkit-user-drag: none; touch-action: none; user-select: none; }
|
|
160
193
|
::slotted(button) { outline:none; }
|
|
161
194
|
</style>`;
|
|
162
195
|
this.canvas = this.root.querySelector('canvas')!;
|
|
163
196
|
}
|
|
164
197
|
}
|
|
198
|
+
|
|
199
|
+
export class AxRevealNg extends PatchedHTMLElement {
|
|
200
|
+
static readonly ElementName = 'ax-reveal-ng';
|
|
201
|
+
private root = this.attachShadow({ mode: 'open' });
|
|
202
|
+
private svg: SVGElement;
|
|
203
|
+
private boundary!: AxRevealBoundary;
|
|
204
|
+
|
|
205
|
+
adoptedCallback() {
|
|
206
|
+
this.disconnectedCallback();
|
|
207
|
+
this.connectedCallback();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
handlePointerDown = (ev: PointerEvent) => {
|
|
211
|
+
this.setPointerCapture(ev.pointerId);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
disconnectedCallback() {
|
|
215
|
+
this.boundary && this.boundary.waitForStorage((storage) => storage.removeReveal(this.svg));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
connectedCallback() {
|
|
219
|
+
this.boundary = closest(this, AxRevealBoundary.ElementName) as AxRevealBoundary;
|
|
220
|
+
if (!this.boundary)
|
|
221
|
+
throw new SyntaxError(
|
|
222
|
+
'You must use ' + AxRevealBoundary.ElementName + ' as the boundary of reveal highlight!'
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
this.boundary.waitForStorage((storage) => setTimeout(() => storage.addReveal(this.svg, this), 0));
|
|
226
|
+
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
constructor() {
|
|
230
|
+
super();
|
|
231
|
+
this.root.innerHTML = `
|
|
232
|
+
<div class="ax-reveal">
|
|
233
|
+
<svg>
|
|
234
|
+
<defs>
|
|
235
|
+
<radialGradient id="borderGrad" gradientUnits="userSpaceOnUse" r="10">
|
|
236
|
+
<stop id="borderCenter" offset="0%" />
|
|
237
|
+
<stop id="borderOut" offset="100%" />
|
|
238
|
+
</radialGradient>
|
|
239
|
+
<radialGradient id="fillGrad" gradientUnits="userSpaceOnUse" r="10">
|
|
240
|
+
<stop id="fillCenter" offset="0%" />
|
|
241
|
+
<stop id="fillOut" offset="100%" />
|
|
242
|
+
</radialGradient>
|
|
243
|
+
<radialGradient id="rippleGrad" gradientUnits="userSpaceOnUse" r="10">
|
|
244
|
+
<stop id="rippleCenter" offset="0%" />
|
|
245
|
+
<stop id="rippleMiddle" offset="0%" />
|
|
246
|
+
<stop id="rippleOut" offset="0%" />
|
|
247
|
+
</radialGradient>
|
|
248
|
+
</defs>
|
|
249
|
+
<path id="borderPath" fill="url(#borderGrad)" />
|
|
250
|
+
<path id="fillPath" fill="url(#fillGrad)" />
|
|
251
|
+
<path id="ripplePath" fill="url(#rippleGrad)" />
|
|
252
|
+
</svg>
|
|
253
|
+
<div class="content"><slot></slot></div>
|
|
254
|
+
</div>
|
|
255
|
+
<style>
|
|
256
|
+
.ax-reveal { display: contents; }
|
|
257
|
+
.ax-reveal * { user-drag: none; -webkit-user-drag: none; touch-action: none; user-select: none; }
|
|
258
|
+
.content { position: relative; }
|
|
259
|
+
svg { top: 0; left: 0; pointer-events: none; width: 100%; height: 100%; position: absolute; }
|
|
260
|
+
:host { display: inline-block; position: relative; }
|
|
261
|
+
:host([block]) { display: block; }
|
|
262
|
+
:host([inline-block]) { display: inline-block; }
|
|
263
|
+
:host([flex]) { display: flex; }
|
|
264
|
+
:host([inline-flex]) { display: inline-flex; }
|
|
265
|
+
:host([grid]) { display: grid; }
|
|
266
|
+
:host([inline-grid]) { display: inline-grid; }
|
|
267
|
+
::slotted(*) { user-drag: none; -webkit-user-drag: none; touch-action: none; user-select: none; }
|
|
268
|
+
::slotted(button) { outline:none; }
|
|
269
|
+
</style>`;
|
|
270
|
+
this.svg = this.root.querySelector('svg')!;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -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:
|
|
19
|
+
canvasList: Config[] = [];
|
|
17
20
|
|
|
18
|
-
animationQueue:
|
|
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 = ($
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
config.cacheBoundingRect();
|
|
55
|
+
config.cacheCanvasPaintingStyle();
|
|
56
|
+
config.updateCachedBitmap();
|
|
44
57
|
|
|
45
|
-
this.canvasList.push(
|
|
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.
|
|
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
|
-
|
|
59
|
-
|
|
71
|
+
requestPaintAll = (cleanup?: boolean) => {
|
|
72
|
+
if (this.requestedAnimationFrame) return;
|
|
73
|
+
this.requestedAnimationFrame = true;
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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.
|
|
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.
|
|
120
|
+
this.requestPaintAll(true);
|
|
85
121
|
};
|
|
86
122
|
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
195
|
+
this.requestPaintAll();
|
|
134
196
|
} else {
|
|
135
197
|
this.animationFrame = null;
|
|
136
198
|
}
|
|
137
199
|
};
|
|
138
200
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,62 @@ export class RevealStateManager {
|
|
|
14
14
|
if (config.borderDetectionMode === 'experimentalAutoFit') {
|
|
15
15
|
if (typeof Window === 'undefined') return;
|
|
16
16
|
|
|
17
|
-
window.addEventListener('pointermove',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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();
|
|
29
|
+
});
|
|
30
|
+
// Handles boundary conditions such as the user scrolling
|
|
31
|
+
// the page, dragging the image, which can cause mouse events
|
|
32
|
+
// to fail, when Reveal should disappear to prevent the
|
|
33
|
+
// image from getting stuck.
|
|
34
|
+
window.addEventListener('pointercancel', (ev) => {
|
|
35
|
+
console.log('pointer cancel');
|
|
36
|
+
this.handlePointerLeave();
|
|
25
37
|
});
|
|
26
38
|
}
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
pointerLeaveScheduled = false;
|
|
42
|
+
|
|
43
|
+
handlePointerLeave = () => {
|
|
44
|
+
if (this.pointerLeaveScheduled) return;
|
|
45
|
+
|
|
46
|
+
this.pointerLeaveScheduled = true;
|
|
47
|
+
|
|
48
|
+
window.requestAnimationFrame(() => {
|
|
49
|
+
for (let i = 0; i < this._storage.length; i++) {
|
|
50
|
+
this._storage[i].onPointerLeaveBoundary();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.pointerLeaveScheduled = false;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handlePointerMove = (event: PointerEvent) => {
|
|
58
|
+
this.cachedMouseX = event.clientX;
|
|
59
|
+
this.cachedMouseY = event.clientY;
|
|
60
|
+
|
|
61
|
+
if (!this.requestedTraverseBoundaries) {
|
|
62
|
+
window.requestAnimationFrame(this.traverseBoundaries);
|
|
63
|
+
this.requestedTraverseBoundaries = true;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
29
67
|
traverseBoundaries = () => {
|
|
30
68
|
this.requestedTraverseBoundaries = false;
|
|
31
69
|
for (let i = 0; i < this._storage.length; i++) {
|
|
32
70
|
this._storage[i].onPointerMoveOnScreen(this.cachedMouseX, this.cachedMouseY);
|
|
33
71
|
}
|
|
34
|
-
}
|
|
72
|
+
};
|
|
35
73
|
|
|
36
74
|
newBoundary = ($el: HTMLElement) => {
|
|
37
75
|
const hashId = this._currentHashId++;
|
|
@@ -43,6 +81,6 @@ export class RevealStateManager {
|
|
|
43
81
|
};
|
|
44
82
|
|
|
45
83
|
removeBoundary = (store: RevealBoundaryStore) => {
|
|
46
|
-
this._storage = this._storage.filter(x => x !== store);
|
|
84
|
+
this._storage = this._storage.filter((x) => x !== store);
|
|
47
85
|
};
|
|
48
86
|
}
|