close #51 add mouse tail feature
This commit is contained in:
@@ -40,6 +40,12 @@ const mitt = (mittProxy as any).default || mittProxy;
|
|||||||
|
|
||||||
const REPLAY_CONSOLE_PREFIX = '[replayer]';
|
const REPLAY_CONSOLE_PREFIX = '[replayer]';
|
||||||
|
|
||||||
|
const defaultMouseTailConfig = {
|
||||||
|
duration: 500,
|
||||||
|
lineCap: 'round',
|
||||||
|
lineWidth: 3,
|
||||||
|
strokeStyle: 'red',
|
||||||
|
} as const;
|
||||||
const defaultConfig: playerConfig = {
|
const defaultConfig: playerConfig = {
|
||||||
speed: 1,
|
speed: 1,
|
||||||
root: document.body,
|
root: document.body,
|
||||||
@@ -52,6 +58,7 @@ const defaultConfig: playerConfig = {
|
|||||||
insertStyleRules: [],
|
insertStyleRules: [],
|
||||||
triggerFocus: true,
|
triggerFocus: true,
|
||||||
UNSAFE_replayCanvas: false,
|
UNSAFE_replayCanvas: false,
|
||||||
|
mouseTail: defaultMouseTailConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Replayer {
|
export class Replayer {
|
||||||
@@ -67,6 +74,8 @@ export class Replayer {
|
|||||||
public config: playerConfig;
|
public config: playerConfig;
|
||||||
|
|
||||||
private mouse: HTMLDivElement;
|
private mouse: HTMLDivElement;
|
||||||
|
private mouseTail: HTMLCanvasElement | null = null;
|
||||||
|
private tailPositions: Array<{ x: number; y: number }> = [];
|
||||||
|
|
||||||
private emitter: Emitter = mitt();
|
private emitter: Emitter = mitt();
|
||||||
|
|
||||||
@@ -291,6 +300,13 @@ export class Replayer {
|
|||||||
this.mouse.classList.add('replayer-mouse');
|
this.mouse.classList.add('replayer-mouse');
|
||||||
this.wrapper.appendChild(this.mouse);
|
this.wrapper.appendChild(this.mouse);
|
||||||
|
|
||||||
|
if (this.config.mouseTail !== false) {
|
||||||
|
this.mouseTail = document.createElement('canvas');
|
||||||
|
this.mouseTail.classList.add('replayer-mouse-tail');
|
||||||
|
this.mouseTail.style.display = 'none';
|
||||||
|
this.wrapper.appendChild(this.mouseTail);
|
||||||
|
}
|
||||||
|
|
||||||
this.iframe = document.createElement('iframe');
|
this.iframe = document.createElement('iframe');
|
||||||
const attributes = ['allow-same-origin'];
|
const attributes = ['allow-same-origin'];
|
||||||
if (this.config.UNSAFE_replayCanvas) {
|
if (this.config.UNSAFE_replayCanvas) {
|
||||||
@@ -310,9 +326,14 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleResize(dimension: viewportResizeDimention) {
|
private handleResize(dimension: viewportResizeDimention) {
|
||||||
this.iframe.style.display = 'inherit';
|
for (const el of [this.mouseTail, this.iframe]) {
|
||||||
this.iframe.setAttribute('width', String(dimension.width));
|
if (!el) {
|
||||||
this.iframe.setAttribute('height', String(dimension.height));
|
continue;
|
||||||
|
}
|
||||||
|
el.style.display = 'inherit';
|
||||||
|
el.setAttribute('width', String(dimension.width));
|
||||||
|
el.setAttribute('height', String(dimension.height));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCastFn(event: eventWithTime, isSync = false) {
|
private getCastFn(event: eventWithTime, isSync = false) {
|
||||||
@@ -992,6 +1013,8 @@ export class Replayer {
|
|||||||
private moveAndHover(d: incrementalData, x: number, y: number, id: number) {
|
private moveAndHover(d: incrementalData, x: number, y: number, id: number) {
|
||||||
this.mouse.style.left = `${x}px`;
|
this.mouse.style.left = `${x}px`;
|
||||||
this.mouse.style.top = `${y}px`;
|
this.mouse.style.top = `${y}px`;
|
||||||
|
this.drawMouseTail({ x, y });
|
||||||
|
|
||||||
const target = mirror.getNode(id);
|
const target = mirror.getNode(id);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return this.debugNodeNotFound(d, id);
|
return this.debugNodeNotFound(d, id);
|
||||||
@@ -999,6 +1022,42 @@ export class Replayer {
|
|||||||
this.hoverElements((target as Node) as Element);
|
this.hoverElements((target as Node) as Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private drawMouseTail(position: { x: number; y: number }) {
|
||||||
|
if (!this.mouseTail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lineCap, lineWidth, strokeStyle, duration } =
|
||||||
|
this.config.mouseTail === true
|
||||||
|
? defaultMouseTailConfig
|
||||||
|
: Object.assign({}, defaultMouseTailConfig, this.config.mouseTail);
|
||||||
|
|
||||||
|
const draw = () => {
|
||||||
|
if (!this.mouseTail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ctx = this.mouseTail.getContext('2d');
|
||||||
|
if (!ctx || !this.tailPositions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.clearRect(0, 0, this.mouseTail.width, this.mouseTail.height);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
ctx.lineCap = lineCap;
|
||||||
|
ctx.strokeStyle = strokeStyle;
|
||||||
|
ctx.moveTo(this.tailPositions[0].x, this.tailPositions[0].y);
|
||||||
|
this.tailPositions.forEach((p) => ctx.lineTo(p.x, p.y));
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tailPositions.push(position);
|
||||||
|
draw();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.tailPositions = this.tailPositions.filter((p) => p !== position);
|
||||||
|
draw();
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
private hoverElements(el: Element) {
|
private hoverElements(el: Element) {
|
||||||
this.iframe.contentDocument
|
this.iframe.contentDocument
|
||||||
?.querySelectorAll('.\\:hover')
|
?.querySelectorAll('.\\:hover')
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
.replayer-mouse.active::after {
|
.replayer-mouse.active::after {
|
||||||
animation: click 0.2s ease-in-out 1;
|
animation: click 0.2s ease-in-out 1;
|
||||||
}
|
}
|
||||||
|
.replayer-mouse-tail {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes click {
|
@keyframes click {
|
||||||
0% {
|
0% {
|
||||||
|
|||||||
@@ -382,6 +382,14 @@ export type playerConfig = {
|
|||||||
insertStyleRules: string[];
|
insertStyleRules: string[];
|
||||||
triggerFocus: boolean;
|
triggerFocus: boolean;
|
||||||
UNSAFE_replayCanvas: boolean;
|
UNSAFE_replayCanvas: boolean;
|
||||||
|
mouseTail:
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
duration?: number;
|
||||||
|
lineCap?: string;
|
||||||
|
lineWidth?: number;
|
||||||
|
strokeStyle?: string;
|
||||||
|
};
|
||||||
unpackFn?: UnpackFn;
|
unpackFn?: UnpackFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user