feat(player): added mice trail
This commit is contained in:
parent
e6bf281adb
commit
f57cc13cd1
4 changed files with 187 additions and 5 deletions
|
|
@ -62,6 +62,7 @@ export default class Screen {
|
|||
private readonly iframe: HTMLIFrameElement;
|
||||
private readonly screen: HTMLDivElement;
|
||||
private parentElement: HTMLElement | null = null
|
||||
private onUpdateHook: (w: number, h: number) => void
|
||||
|
||||
constructor(isMobile: boolean, private scaleMode: ScaleMode = ScaleMode.Embed) {
|
||||
const iframe = document.createElement('iframe');
|
||||
|
|
@ -132,7 +133,7 @@ export default class Screen {
|
|||
return this.iframe.style
|
||||
}
|
||||
|
||||
private boundingRect: DOMRect | null = null;
|
||||
public boundingRect: DOMRect | null = null;
|
||||
private getBoundingClientRect(): DOMRect {
|
||||
if (this.boundingRect === null) {
|
||||
// TODO: use this.screen instead in order to separate overlay functionality
|
||||
|
|
@ -246,6 +247,10 @@ export default class Screen {
|
|||
})
|
||||
|
||||
this.boundingRect = this.overlay.getBoundingClientRect();
|
||||
this.onUpdateHook(width, height)
|
||||
}
|
||||
|
||||
setOnUpdate(cb: any) {
|
||||
this.onUpdateHook = cb
|
||||
}
|
||||
}
|
||||
|
|
|
|||
149
frontend/app/player/web/addons/MouseTrail.ts
Normal file
149
frontend/app/player/web/addons/MouseTrail.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* Inspired by Bryan C (@bryjch at codepen)
|
||||
* */
|
||||
|
||||
const LINE_DURATION = 3;
|
||||
const LINE_WIDTH_START = 5;
|
||||
|
||||
export default class MouseTrail {
|
||||
public isActive = true;
|
||||
public context: CanvasRenderingContext2D;
|
||||
private dimensions = { width: 0, height: 0 };
|
||||
/**
|
||||
* 1 - every frame,
|
||||
* 2 - every 2nd frame
|
||||
* and so on, doesn't always work properly
|
||||
* but 1 doesnt affect performance so we fine
|
||||
* */
|
||||
private drawOnFrame = 1;
|
||||
private currentFrame = 0;
|
||||
private points: Point[] = [];
|
||||
|
||||
constructor(private readonly canvas: HTMLCanvasElement) {
|
||||
// @ts-ignore patching window
|
||||
window.requestAnimFrame =
|
||||
window.requestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.webkitRequestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.mozRequestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.oRequestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.msRequestAnimationFrame ||
|
||||
function (callback: any) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
}
|
||||
|
||||
resizeCanvas = (w: number, h: number) => {
|
||||
if (this.context !== undefined) {
|
||||
this.context.canvas.width = w;
|
||||
this.context.canvas.height = h;
|
||||
this.canvas.width = w;
|
||||
this.canvas.height = h;
|
||||
|
||||
this.dimensions.width = w;
|
||||
this.dimensions.height = h;
|
||||
}
|
||||
};
|
||||
|
||||
createContext = () => {
|
||||
if (this.canvas) {
|
||||
this.context = this.canvas.getContext('2d')!;
|
||||
this.init();
|
||||
} else {
|
||||
console.error('Canvas element not found');
|
||||
}
|
||||
};
|
||||
|
||||
leaveTrail = (x: number, y: number) => {
|
||||
if (this.currentFrame === this.drawOnFrame) {
|
||||
this.addPoint(x + 7, y + 7);
|
||||
this.currentFrame = 0;
|
||||
}
|
||||
this.currentFrame++;
|
||||
};
|
||||
|
||||
init = () => {
|
||||
if (this.isActive) {
|
||||
this.animatePoints();
|
||||
// @ts-ignore patched
|
||||
window.requestAnimFrame(this.init);
|
||||
}
|
||||
};
|
||||
|
||||
animatePoints = () => {
|
||||
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||
|
||||
const duration = (LINE_DURATION * 1000) / 60;
|
||||
let point, lastPoint;
|
||||
|
||||
for (let i = 0; i < this.points.length; i++) {
|
||||
point = this.points[i];
|
||||
|
||||
if (this.points[i - 1] !== undefined) {
|
||||
lastPoint = this.points[i - 1];
|
||||
} else {
|
||||
lastPoint = this.points[i];
|
||||
}
|
||||
|
||||
point.lifetime! += 1;
|
||||
|
||||
if (point.lifetime! > duration) {
|
||||
this.points.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const inc = point.lifetime! / duration; // 0 to 1 over lineDuration
|
||||
const dec = 1 - inc;
|
||||
|
||||
const spreadRate = LINE_WIDTH_START * (1 - inc);
|
||||
this.context.lineJoin = 'round';
|
||||
this.context.lineWidth = spreadRate;
|
||||
this.context.strokeStyle = `rgb(255, ${Math.floor(200 - 255 * dec)}, ${Math.floor(200 - 255 * inc)})`
|
||||
|
||||
this.context.beginPath();
|
||||
this.context.moveTo(lastPoint.x, lastPoint.y);
|
||||
this.context.lineTo(point.x, point.y);
|
||||
this.context.stroke();
|
||||
this.context.closePath();
|
||||
}
|
||||
};
|
||||
|
||||
addPoint = (x: number, y: number) => {
|
||||
const point = new Point(x, y, 0);
|
||||
this.points.push(point);
|
||||
};
|
||||
}
|
||||
|
||||
type Coords = { x: number; y: number };
|
||||
|
||||
class Point {
|
||||
constructor(public x: number, public y: number, public lifetime?: number) {}
|
||||
|
||||
static distance(a: Coords, b: Coords) {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
static midPoint(a: Coords, b: Coords) {
|
||||
const mx = a.x + (b.x - a.x) * 0.5;
|
||||
const my = a.y + (b.y - a.y) * 0.5;
|
||||
|
||||
return new Point(mx, my);
|
||||
}
|
||||
|
||||
static angle(a: Coords, b: Coords) {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
|
||||
return Math.atan2(dy, dx);
|
||||
}
|
||||
|
||||
get pos() {
|
||||
return this.x + ',' + this.y;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,33 @@ import type Screen from '../Screen/Screen'
|
|||
import type { MouseMove } from "../messages";
|
||||
import { HOVER_CLASSNAME } from '../messages/rewriter/constants'
|
||||
import ListWalker from '../../common/ListWalker'
|
||||
|
||||
import MouseTrail from '../addons/MouseTrail'
|
||||
import styles from './trail.module.css'
|
||||
|
||||
export default class MouseMoveManager extends ListWalker<MouseMove> {
|
||||
private hoverElements: Array<Element> = []
|
||||
private mouseTrail: MouseTrail
|
||||
|
||||
constructor(private screen: Screen) {super()}
|
||||
constructor(private screen: Screen) {
|
||||
super()
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.id = 'openreplay-mouse-trail'
|
||||
canvas.className = styles.canvas
|
||||
|
||||
this.mouseTrail = new MouseTrail(canvas)
|
||||
|
||||
this.screen.overlay.appendChild(canvas)
|
||||
this.mouseTrail.createContext()
|
||||
|
||||
const updateSize = (w: number, h: number) => {
|
||||
return this.mouseTrail.resizeCanvas(w, h)
|
||||
}
|
||||
|
||||
this.screen.setOnUpdate(updateSize)
|
||||
}
|
||||
|
||||
private getCursorTargets() {
|
||||
return this.screen.getElementsFromInternalPoint(this.current)
|
||||
return this.screen.getElementsFromInternalPoint(this.current!)
|
||||
}
|
||||
|
||||
private updateHover(): void {
|
||||
|
|
@ -34,8 +52,9 @@ export default class MouseMoveManager extends ListWalker<MouseMove> {
|
|||
const lastMouseMove = this.moveGetLast(t)
|
||||
if (!!lastMouseMove) {
|
||||
this.screen.cursor.move(lastMouseMove)
|
||||
//window.getComputedStyle(this.screen.getCursorTarget()).cursor === 'pointer' // might nfluence performance though
|
||||
//window.getComputedStyle(this.screen.getCursorTarget()).cursor === 'pointer' // might influence performance though
|
||||
this.updateHover()
|
||||
this.mouseTrail.leaveTrail(lastMouseMove.x, lastMouseMove.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
frontend/app/player/web/managers/trail.module.css
Normal file
9
frontend/app/player/web/managers/trail.module.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 11;
|
||||
pointer-events: none;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue