use raf to impl a more accurate timer and replay events async
This commit is contained in:
@@ -1,62 +1,131 @@
|
||||
import { rebuild } from 'rrweb-snapshot';
|
||||
import { mirror } from '../utils';
|
||||
import { event, EventType, incrementalData, IncrementalSource } from '../types';
|
||||
import later from './timer';
|
||||
import {
|
||||
EventType,
|
||||
incrementalData,
|
||||
IncrementalSource,
|
||||
fullSnapshotEvent,
|
||||
eventWithTime,
|
||||
} from '../types';
|
||||
import eventsStr from './events';
|
||||
|
||||
const events: event[] = JSON.parse(eventsStr);
|
||||
const _events: eventWithTime[] = JSON.parse(eventsStr);
|
||||
|
||||
function applyIncremental(d: incrementalData) {
|
||||
switch (d.source) {
|
||||
case IncrementalSource.Mutation:
|
||||
case IncrementalSource.MouseMove:
|
||||
case IncrementalSource.MouseInteraction:
|
||||
break;
|
||||
case IncrementalSource.Scroll:
|
||||
// TODO: maybe element
|
||||
window.scrollTo({
|
||||
top: d.y,
|
||||
left: d.x,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
break;
|
||||
case IncrementalSource.ViewportResize:
|
||||
case IncrementalSource.Input:
|
||||
default:
|
||||
class Replayer {
|
||||
private events: eventWithTime[] = [];
|
||||
private wrapper: HTMLDivElement;
|
||||
private iframe: HTMLIFrameElement;
|
||||
private mouse: HTMLDivElement;
|
||||
private startTime: number = 0;
|
||||
|
||||
constructor(events: eventWithTime[]) {
|
||||
this.events = events;
|
||||
}
|
||||
}
|
||||
|
||||
function replay() {
|
||||
const iframe = document.createElement('iframe');
|
||||
for (const event of events) {
|
||||
switch (event.type) {
|
||||
case EventType.DomContentLoaded:
|
||||
public play() {
|
||||
this.setupDom();
|
||||
for (const event of this.events) {
|
||||
switch (event.type) {
|
||||
case EventType.DomContentLoaded:
|
||||
this.startTime = event.timestamp;
|
||||
break;
|
||||
case EventType.Load:
|
||||
this.iframe.width = `${event.data.width}px`;
|
||||
this.iframe.height = `${event.data.height}px`;
|
||||
break;
|
||||
case EventType.FullSnapshot:
|
||||
later(() => {
|
||||
this.rebuildFullSnapshot(event);
|
||||
}, this.getDelay(event));
|
||||
break;
|
||||
case EventType.IncrementalSnapshot:
|
||||
later(() => {
|
||||
this.applyIncremental(event.data);
|
||||
}, this.getDelay(event));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setupDom() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.classList.add('replayer-wrapper');
|
||||
document.body.appendChild(this.wrapper);
|
||||
|
||||
this.mouse = document.createElement('div');
|
||||
this.mouse.classList.add('replayer-mouse');
|
||||
this.wrapper.appendChild(this.mouse);
|
||||
|
||||
this.iframe = document.createElement('iframe');
|
||||
this.wrapper.appendChild(this.iframe);
|
||||
}
|
||||
|
||||
private getDelay(event: eventWithTime): number {
|
||||
if (
|
||||
event.type === EventType.IncrementalSnapshot &&
|
||||
event.data.source === IncrementalSource.MouseMove
|
||||
) {
|
||||
const firstOffset = event.data.positions[0].timeOffset;
|
||||
// timeoffset is a negative offset to event.timestamp
|
||||
const firstTimestamp = event.timestamp + firstOffset;
|
||||
event.data.positions = event.data.positions.map(p => {
|
||||
return {
|
||||
...p,
|
||||
timeOffset: p.timeOffset - firstOffset,
|
||||
};
|
||||
});
|
||||
return firstTimestamp - this.startTime;
|
||||
}
|
||||
return event.timestamp - this.startTime;
|
||||
}
|
||||
|
||||
private rebuildFullSnapshot(event: fullSnapshotEvent) {
|
||||
const [doc, map] = rebuild(event.data.node);
|
||||
mirror.map = map;
|
||||
if (doc) {
|
||||
this.iframe.contentDocument!.open();
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
|
||||
this.iframe.contentDocument!.write(
|
||||
(doc as Document).documentElement.outerHTML
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>'),
|
||||
);
|
||||
this.iframe.contentDocument!.close();
|
||||
}
|
||||
}
|
||||
|
||||
private applyIncremental(d: incrementalData) {
|
||||
switch (d.source) {
|
||||
case IncrementalSource.Mutation:
|
||||
break;
|
||||
case EventType.Load:
|
||||
iframe.width = `${event.data.width}px`;
|
||||
iframe.height = `${event.data.height}px`;
|
||||
case IncrementalSource.MouseMove:
|
||||
d.positions.forEach(p => {
|
||||
later(() => {
|
||||
this.mouse.style.left = `${p.x}px`;
|
||||
this.mouse.style.top = `${p.y}px`;
|
||||
}, p.timeOffset);
|
||||
});
|
||||
break;
|
||||
case EventType.FullSnapshot:
|
||||
const [doc, map] = rebuild(event.data.node);
|
||||
mirror.map = map;
|
||||
if (doc) {
|
||||
document.body.appendChild(iframe);
|
||||
iframe.contentDocument!.open();
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
|
||||
iframe.contentDocument!.write(
|
||||
(doc as Document).documentElement.outerHTML
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>'),
|
||||
);
|
||||
iframe.contentDocument!.close();
|
||||
}
|
||||
case IncrementalSource.MouseInteraction:
|
||||
break;
|
||||
case EventType.IncrementalSnapshot:
|
||||
applyIncremental(event.data);
|
||||
case IncrementalSource.Scroll:
|
||||
// TODO: maybe element
|
||||
this.iframe.contentWindow!.scrollTo({
|
||||
top: d.y,
|
||||
left: d.x,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
break;
|
||||
case IncrementalSource.ViewportResize:
|
||||
case IncrementalSource.Input:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default replay;
|
||||
const replayer = new Replayer(_events);
|
||||
|
||||
export default replayer;
|
||||
|
||||
10
src/replay/styles/style.css
Normal file
10
src/replay/styles/style.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.replayer-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.replayer-mouse {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
background: thistle;
|
||||
}
|
||||
17
src/replay/timer.ts
Normal file
17
src/replay/timer.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
const FRAME_MS = 16;
|
||||
|
||||
function later(cb: () => void, delayMs: number) {
|
||||
const now = performance.now();
|
||||
|
||||
function check(step: number) {
|
||||
if (step - now > delayMs - FRAME_MS) {
|
||||
cb();
|
||||
} else {
|
||||
requestAnimationFrame(check);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(check);
|
||||
}
|
||||
|
||||
export default later;
|
||||
Reference in New Issue
Block a user