From 0688bb63532e786a033585eb3278561e3b93a81e Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Mon, 7 Sep 2020 14:52:57 +0800 Subject: [PATCH] close #51 add mouse tail feature --- src/replay/index.ts | 65 +++++++++++++++++++++++++++++++++++-- src/replay/styles/style.css | 3 ++ src/types.ts | 8 +++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/replay/index.ts b/src/replay/index.ts index e72aab82..72f0bf8e 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -40,6 +40,12 @@ const mitt = (mittProxy as any).default || mittProxy; const REPLAY_CONSOLE_PREFIX = '[replayer]'; +const defaultMouseTailConfig = { + duration: 500, + lineCap: 'round', + lineWidth: 3, + strokeStyle: 'red', +} as const; const defaultConfig: playerConfig = { speed: 1, root: document.body, @@ -52,6 +58,7 @@ const defaultConfig: playerConfig = { insertStyleRules: [], triggerFocus: true, UNSAFE_replayCanvas: false, + mouseTail: defaultMouseTailConfig, }; export class Replayer { @@ -67,6 +74,8 @@ export class Replayer { public config: playerConfig; private mouse: HTMLDivElement; + private mouseTail: HTMLCanvasElement | null = null; + private tailPositions: Array<{ x: number; y: number }> = []; private emitter: Emitter = mitt(); @@ -291,6 +300,13 @@ export class Replayer { this.mouse.classList.add('replayer-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'); const attributes = ['allow-same-origin']; if (this.config.UNSAFE_replayCanvas) { @@ -310,9 +326,14 @@ export class Replayer { } private handleResize(dimension: viewportResizeDimention) { - this.iframe.style.display = 'inherit'; - this.iframe.setAttribute('width', String(dimension.width)); - this.iframe.setAttribute('height', String(dimension.height)); + for (const el of [this.mouseTail, this.iframe]) { + if (!el) { + continue; + } + el.style.display = 'inherit'; + el.setAttribute('width', String(dimension.width)); + el.setAttribute('height', String(dimension.height)); + } } private getCastFn(event: eventWithTime, isSync = false) { @@ -992,6 +1013,8 @@ export class Replayer { private moveAndHover(d: incrementalData, x: number, y: number, id: number) { this.mouse.style.left = `${x}px`; this.mouse.style.top = `${y}px`; + this.drawMouseTail({ x, y }); + const target = mirror.getNode(id); if (!target) { return this.debugNodeNotFound(d, id); @@ -999,6 +1022,42 @@ export class Replayer { 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) { this.iframe.contentDocument ?.querySelectorAll('.\\:hover') diff --git a/src/replay/styles/style.css b/src/replay/styles/style.css index f362740a..1766ccaf 100644 --- a/src/replay/styles/style.css +++ b/src/replay/styles/style.css @@ -24,6 +24,9 @@ .replayer-mouse.active::after { animation: click 0.2s ease-in-out 1; } +.replayer-mouse-tail { + position: absolute; +} @keyframes click { 0% { diff --git a/src/types.ts b/src/types.ts index 10a141b7..26c47c6c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -382,6 +382,14 @@ export type playerConfig = { insertStyleRules: string[]; triggerFocus: boolean; UNSAFE_replayCanvas: boolean; + mouseTail: + | boolean + | { + duration?: number; + lineCap?: string; + lineWidth?: number; + strokeStyle?: string; + }; unpackFn?: UnpackFn; };